設計模式,這是一個可以持續(xù)投入研究的問題,當初我一直不能理解學長們口中談論的設計模式到底是什么意思,什么是MVC、MVP、MVVM甚至CDD呢?以及現(xiàn)在層出不窮的MVX等等??。有人這么跟我說,“架構,其實是一個設計上的東西,它可以小到類與類之間的一個交互,可以大到不同的模塊之間,或者說不同的業(yè)務部門之間的交互都可以從架構的層面去理解它。”
好了,說完后我更加懵逼了,這還是沒說明白啊。也就一直拖著。隨后我開始了第一個自己所謂的“項目”——“大學+”,咱們實話實說,開始大學+之前時間上我有在幫一個學長做他的個人項目一部分,跟我說這個項目整體的架構是MVC,但是當時我哪知道啥是MVC啊,剛開始他丟給我做一個用戶登陸模塊,我只能依葫蘆畫瓢,當時根本就不知道啥叫Model,啥叫block,可是當時項目中卻充滿著大量的Model和block以及各種delegate。??。迷茫了好幾天,最后不管怎么說也是瞎做完了,給學長review的時候居然被他發(fā)現(xiàn)了我沒用二次封裝的AFNetworking網絡請求manager,而是自己又搞了一個賊差勁的破東西,被數(shù)落了一番后,我當時還是沒啥概念,還是不知道為啥要這么做,怎么做。
開始“大學+”項目后,剛開始我同樣還是沒有拎清楚到底什么是設計模式,導致在項目開展過程中很多模塊的實現(xiàn)方式都是亂七八糟,數(shù)據(jù)源都是瞎給的,甚至有些頁面的數(shù)據(jù)源都重復獲取了好幾次,但是神奇的地方就在于居然能夠把這個項目做完了?。?!??
在前年暑假的重構期間,我在習得了一些設計模式的思想以及大量的實踐之后,慢慢的發(fā)現(xiàn)!原來我當初的設計是在趨向于MVC的,只不過當時實在是無法hold得住到底什么是MVC才會導致在View中不但做了邏輯還做了model的事情。
隨后展開了艱難的重構之路,在重構期間,我又對設計模式有了一個新的認識,開始發(fā)現(xiàn)不是某個項目去迎合設計模式、架構,而是設計模式、架構來迎合項目的實際,也就因為是這種情況的出現(xiàn),最開始軟件行業(yè)基本上都套用MVC,但是在越來越多的實際開發(fā)過程中發(fā)現(xiàn),一昧的死守MVC實際上還有破壞項目實際的耦合,隨后才慢慢的衍生出根據(jù)不同的開發(fā)平臺適用的MVP、MVX、MVVM、CDD等。
在我日后的學習和工作中,運用到最多的就是MVC,甚至說基本上都是MVC,畢竟MVC是軟件行業(yè)的“常青樹”,基本上都能夠用MVC來構建每一個軟件產品,而且MVP、MVVM等可以說都是的MVC的變種,本質上也都還是MVC。
在這篇文章中,我將結合以往學習和工作經驗梳理一遍關于耦合、MVC、MVP、MVVM的核心知識點,并編寫對應實例進行講解,也作為自己在設計模式上的理解與總結。
架構基礎
在講解三大設計模式之前,我們先來做一些架構基礎的工作。之所以要對項目做整體的分層架構設計是因為隨著項目進度的展開,日益增長業(yè)務邏輯和代碼數(shù)量遠遠超出了開發(fā)人員所能精確分析掌握的能力。一旦嗅探出項目中有即將“腐爛”的部分,如果再不加以維護日后就一定會變得更加腐爛。??
為了防止以上問題的發(fā)生,慢慢的萌生出了“軟件工程”的科學指導軟件開發(fā)方法,以工程的思路去規(guī)范、進行軟件開發(fā)工作,同時其衍生品——“設計模式”的思路也慢慢的被廣大軟件從業(yè)者所接受,從此軟件行業(yè)走進了有科學思想指導的春天!??。
架構核心是耦合,簡單來說耦合可以是兩個類之間的交互,當然也可以是三個類甚至更多的類,從大的角度來說,可以是不同的業(yè)務模塊之間的交互,如何讓這些的模塊之間的聯(lián)系或者影響更少,這就我們所說的解耦的概念。
處理好項目中各個類甚至各個模塊之間的耦合關系是長久以來軟件工程專家甚至開發(fā)人員所追求的“至上寶典”,因為產品的不同,其業(yè)務流程模型也不同,需要解決的核心問題也不同,圍繞其做的架構設計也不能一概而論,而在iOS中解決耦合關系,可以分為三個層次。
- 直接耦合。雙方都知道對方的存在。
- delegate。只有一方知道對方的存在。
- notification。雙方均不知道對方的存在。
以上三種為架構設計所采用的基本耦合方式,當然還有一些其它的方式,不過這些方式都牽扯到了平臺差異性,非iOS端做不到,比如KVO等。以上列舉的三種方式具備通俗性,各大平臺均可實現(xiàn)。
直接耦合
直接耦合做法是最差的一種耦合方式,甚至可以說耦合度最高的一種,類與類或者模塊與模塊之間互相都知道了雙方的存在。當然,這種直接耦合的方式不能說很差,只能說它的用處體現(xiàn)的地方非常局限,不過,大部分同學(包括我自己)在最開始寫東西的時候都是“直接耦合”的實踐者,它的“簡單粗暴”是最吸引人的地方(當然這也是它的致命缺點)
實現(xiàn)“直接耦合”模式需要用到一下場景,Manager發(fā)布Task,Worker執(zhí)行Task,執(zhí)行Task完成后告訴Manager,Manager慶祝Task完成。因此我們的文件目錄結構如下所示,
|____Worker.m
|____Manager.h
|____Worker.h
|____Manager.m
|____decoupleViewController.h
|____decoupleViewController.m
// ----- manager -----
#import "Manager.h"
#import "Worker.h"
@implementation Manager
// 慶祝Task完成
- (void)celebratePrintTask {
NSLog(@"celebrate Task!");
}
// 發(fā)布Task給Worker
- (void)beginPrintTask {
Worker *woker = [[Worker alloc] init];
[woker doPrintTask];
}
@end
// ----- worker -----
#import "Worker.h"
#import "Manager.h"
@implementation Worker
// 執(zhí)行Task
- (void)doPrintTask {
NSLog(@"finish work!");
Manager *manager = [[Manager alloc] init];
[manager celebratePrintTask];
}
@end
而想要把Manage和Worker聯(lián)系起來,我們得通過decoupleViewController,
#import "decoupleViewController.h"
#import "Manager.h"
@implementation decoupleViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
// 創(chuàng)建Manage,讓Manage制作Task
Manager *manager = [[Manager alloc] init];
[manager beginPrintTask];
}
從以上代碼中我們可以看到這種直接耦合的寫法根本算不上是設計模式,就是一種“隨用隨寫”的風格,缺點大家應該也能看得清楚,以上邊代碼來說,如果要完成一個Task,Manager是要知道Worker的存在,而且也只能用Worker去完成,而不能讓比如說Student去完成。(除非你要再生成一個Student)當然,跟產品的實際設計還是有很大關系的。如果我們要做的東西非常小,或者某個模塊比較小,使用這種模式或風格去完成會大大縮小開發(fā)成本。
delegate
delegate,代理設計模式,主要用于反向傳值。關于代理的細節(jié)在上一篇文章中已經做了講解,如果還是套用Manager和Worker的思路去講解,使用delega后Worker可以不用管是Manager還是Student甚至是Father去發(fā)布的Task,它只管完成。(反過來也可以),因此實際上Worker是不知道m(xù)anager的存在的,只有manager才知道到底是誰去給他完成了任務。
映射到生活中,這個例子就相當于“我”這個程序員屌絲根本就不管甲方是誰,來活我就做,我相當于worker,甲方可以是BAT,可以是山西煤老板,也可以是美少女戰(zhàn)士,這些相當于manager,manager來找到我這個worker,指定我去完成他們的Task。
創(chuàng)建好的文件目錄結構為:(跟之前并無區(qū)別)
|____delegateViewController.h
|____delelgateManager.h
|____delegateWoker.h
|____delegateViewController.m
|____delegateWoker.m
|____delelgateManager.m
worker的改動為:(相當于指定了工作協(xié)議)
// ----- delegateWorker.h -----
#import <Foundation/Foundation.h>
@protocol delegateWorkerDelegate <NSObject>
- (void)donePrintTask;
@end
@interface delegateWoker : NSObject
@property (nonatomic, weak) id<delegateWorkerDelegate> workerDelegate;
- (void)doPrintTask;
@end
// ----- delegateWorker.m -----
#import "delegateWoker.h"
@implementation delegateWoker
- (void)doPrintTask {
NSLog(@"finish work!");
[_workerDelegate donePrintTask];
}
@end
worker變得簡單一些,它只管做東西。而manager變?yōu)榱耍?/p>
// ----- delegateManager.h -----
#import <Foundation/Foundation.h>
@interface delelgateManager : NSObject
- (void)beginPrintTask;
@end
// ----- delegateManager.m -----
#import "delelgateManager.h"
#import "delegateWoker.h"
@interface delelgateManager () <delegateWorkerDelegate>
@end
@implementation delelgateManager
- (void)beginPrintTask {
delegateWoker *woker = [[delegateWoker alloc] init];
woker.workerDelegate = self;
[woker doPrintTask];
}
- (void)donePrintTask {
NSLog(@"celebrate Task!");
}
從上以上代碼中我們可以看到,manager遵守了worker的delegate(相當于給worker的工作協(xié)議簽了名)并實現(xiàn)了delegate的代理方法(相當于work的工作成果),在donePrintTask的代理方法中可以慶祝Task完成。delegateViewController的代碼跟之前是一樣的。
#import "delegateViewController.h"
#import "delelgateManager.h"
@implementation delegateViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
delelgateManager *manager = [[delelgateManager alloc] init];
[manager beginPrintTask];
}
@end
notification
通知,這部分內容統(tǒng)一也在上一篇文章中做了較為詳細的講解。
使用通知進行架構耦合的文件目錄為:
|____notification.h
|____notifyWorker.h
|____notifyManager.m
|____notificationViewController.m
|____notifyWorker.m
|____notificationViewController.h
|____notifyManager.h
因為很多東西在上一篇文章中都已經一一討論過了,在此不做過多贅述,我們來看使用通知的核心代碼,
// ----- Manager -----
#import "notifyManager.h"
#import "notification.h"
@implementation notifyManager
- (instancetype)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(celebrateWork) name:NOTIFICATION_PRINTTASKDONE object:nil];
}
return self;
}
- (void)beginPrintTask {
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_BEGINPRINTTASK object:nil userInfo:nil];
}
- (void)celebrateWork {
NSLog(@"celebrate work!");
}
@end
// ----- worker -----
#import "notifyWorker.h"
#import "notification.h"
@implementation notifyWorker
- (instancetype)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doPrintTask) name:NOTIFICATION_BEGINPRINTTASK object:nil];
}
return self;
}
- (void)doPrintTask {
NSLog(@"finish work!");
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_PRINTTASKDONE object:nil];
}
@end
從以上兩段代碼中我們可以看出,manager不知道發(fā)布Task后誰去完成,Work不知道完成的Task是誰發(fā)布的,也就是雙方都不知道是誰,它們只知道如果有人發(fā)送了NOTIFICATION_BEGINPRINTTASK(開始工作)和NOTIFICATION_PRINTTASKDONE(工作結束)的通知后就調用各自的處理方法。
雖然通知看上去好像是解決兩個類或模塊之間耦合度最低的方法,但同時也是風險較高的一個方法,如果通知管理得不好,debug起來可是一件異常痛苦的事情。??
以上就是需要大家提前了解的一些架構基礎知識,其它的比如MVC、MVP、MVVM等設計模式都需要用到對應的知識,在后續(xù)的設計模式講解過程中會大量保留此類做法。
MVC
MVC為蘋果官方推薦的設計模式,其為Model-View-Controller的縮寫。簡單來說Model就是數(shù)據(jù)源,訪問Model中的屬性或者方法即可拿到相應的數(shù)據(jù)源;View為展示給用戶的視圖,上邊可以堆積入一些button、label或者ImageView等等,并且還負責把從Model中獲取到的數(shù)據(jù)渲染出來;Controller主要做的事情就是搞定Model何時去拉取數(shù)據(jù),View何時去加載拉取到的Model以及View的操作何時響應給Model重新拉取數(shù)據(jù),需要注意的是,View和Model并無直接聯(lián)系,View只是有一個Model的屬性,View利用該Model屬性解析給對應控件進行賦值,它們之間并不能直接操作,如下所示,
<img src="http://7xszq8.com1.z0.glb.clouddn.com/ww%20%289%29.png" width = "60%" height = "60%" align=center />
綜上所述就是MVC的核心思想,但實際上不會有人嚴格遵守這么做的,都是給你瞎搞,這都是擺我大天朝產品經理所賜,某些奇葩需求還真能讓你寫出來“四不像”(也有可能實力不足???)
舉個?????。?!以下為MVC架構的最小集文件目錄,
|____MVCView.m
|____MVCModel.h
|____MVCModel.m
|____MVCView.h
|____MVCViewController.m
|____MVCViewController.h
在MVCView中我們要寫明需要加載的控件,其中我們加載了一個UILabel和UIButton,
- (void)initView {
self.backgroundColor = [UIColor darkGrayColor];
self.tipsLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 20)];
[self addSubview:self.tipsLabel];
self.tipsLabel.font = [UIFont systemFontOfSize:25];
self.tipsLabel.textAlignment = NSTextAlignmentCenter;
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 300, 200, 30)];
[self addSubview:btn];
[btn addTarget:self action:@selector(btnClick) forControlEvents:1<<6];
[btn setTitle:@"點我??!" forState:UIControlStateNormal];
}
因為View只負責做視圖和數(shù)據(jù)的展示,其中涉及到數(shù)據(jù)的邏輯交互都盡量少甚至不要在View的處理,因此我們要把View中的UIButton的點擊事件代理出去給Controller進行處理,并且我們的View也是不能自己去拉取數(shù)據(jù)的,而是應該暴露出一個Model屬性供Controller自行調配,因此我們的MVCView.h中可以這么寫,
#import <UIKit/UIKit.h>
#import "MVCModel.h"
@protocol MVCViewDelegete <NSObject>
- (void)MVCViewBtnClick;
@end
@interface MVCView : UIView
@property (nonatomic, strong) MVCModel *model;
@property (nonatomic, weak) id<MVCViewDelegete> viewDelegate;
@end
而完整的MVCView.m我們可以把對應的邏輯補充完整,
#import "MVCView.h"
@interface MVCView ()
@property (nonatomic, strong) UILabel* tipsLabel;
@end
@implementation MVCView
- (id)init {
self = [super init];
if (self) {
[self initView];
}
return self;
}
- (void)initView {
self.backgroundColor = [UIColor darkGrayColor];
self.tipsLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 20)];
[self addSubview:self.tipsLabel];
self.tipsLabel.font = [UIFont systemFontOfSize:25];
self.tipsLabel.textAlignment = NSTextAlignmentCenter;
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 300, 200, 30)];
[self addSubview:btn];
[btn addTarget:self action:@selector(btnClick) forControlEvents:1<<6];
[btn setTitle:@"點我?。? forState:UIControlStateNormal];
}
- (void)setModel:(MVCModel *)model {
_model = model;
self.tipsLabel.text = model.contentString;
}
- (void)btnClick {
if (_viewDelegate) {
[_viewDelegate MVCViewBtnClick];
}
}
@end
從以上MVCView.m代碼中我們可以看到,重寫了Model的setter方法,拿到model后我們再接著給對應的label賦值數(shù)據(jù)源即可,在button對應的點擊事件中代理出去,
在MVCController部分,我們不但要把MVCView和MVCModel的關系都確認聯(lián)系起來,還要明確這兩者何時進行交互(例子可能不夠復雜,并不能體現(xiàn)出何時進行交互??)
#import "MVCViewController.h"
#import "MVCView.h"
#import "MVCModel.h"
@interface MVCViewController () <MVCViewDelegete>
@property (nonatomic, strong) MVCModel* model;
@property (nonatomic, strong) MVCView* MVCView;
@end
@implementation MVCViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
self.model = [[MVCModel alloc] init];
self.model.contentString = @"MVC model";
self.MVCView = [[MVCView alloc] init];
self.MVCView.frame = self.view.bounds;
self.MVCView.model = self.model;
self.MVCView.viewDelegate = self;
[self.view addSubview:self.MVCView];
}
- (void)MVCViewBtnClick {
NSInteger interger = random() % 10;
self.model.contentString = [NSString stringWithFormat:@"%ld", (long)interger];
self.MVCView.model = self.model;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
而我們的MCVModel,因為我們只需要的對MVCView上的Label進行文本的替換,因此我們的model實體只需要一個NSString屬性即可。
// ----- MVCModel.h -----
#import <Foundation/Foundation.h>
@interface MVCModel : NSObject
@property (nonatomic, copy) NSString* contentString;
@end
// ----- MVCModel.m -----
#import "MVCModel.h"
@implementation MVCModel
@end
通過以上操作,我們即可完成MVC設計模式的最小集設計,大家應該能夠對MVC有一個初步的認識,MVC架構做到最后會導致C層非常龐大,甚至四五千行代碼都是有可能的。
<img src="http://7xszq8.com1.z0.glb.clouddn.com/Feb-03-2018%2011-07-24.gif" width = "40%" height = "40%" align=center />
MVP
MVP的全稱為Model-View-Presenter,可以看到缺少了Controller,替換成了Presenter。但是不管怎么說其本質還是MVC的分層架構的思想,只不過把Controller的要做的事情降低了。
不過在iOS中并不推薦使用MVP用于項目架構,因為在iOS中是“原生支持”MVC,導致如果我們硬是要用上MVP,整體的項目文件目錄就變成了:
|____MVPModel.h
|____MVPModel.m
|____MVPView.h
|____MVPView.m
|____Presenter.h
|____Presenter.m
|____MVPViewController.h
|____MVPViewController.m
我們還是要創(chuàng)建出來Controller,只不過這里的Controller可以認為是當前該MVP模塊的容器(因為在iOS中就是用各種Controller聯(lián)系起來的??。),我們先來看看此時的Controller做了哪些事情,
#import "MVPViewController.h"
#import "Presenter.h"
#import "MVPView.h"
#import "MVPModel.h"
@interface MVPViewController ()
@property (nonatomic, strong) MVPView* mvpView;
@property (nonatomic, strong) MVPModel* mvpModel;
@property (nonatomic, strong) Presenter* presenter;
@end
@implementation MVPViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self initView];
}
- (void)initView {
self.view.backgroundColor = [UIColor lightGrayColor];
self.presenter = [Presenter new];
self.mvpView = [MVPView new];
self.mvpView.frame = self.view.bounds;
[self.view addSubview:self.mvpView];
self.mvpView.viewDelegate = self.presenter;
self.mvpModel = [MVPModel new];
self.presenter.mvpModel = self.mvpModel;
self.presenter.mvpView = self.mvpView;
self.mvpModel.contentString = @"2333";
[self.presenter doPrintWork];
}
@end
可以看到,實際上Controller的用處只是把Model-View-Presenter這三個東西聯(lián)系起來而已,邏輯都在Presenter里,
// ----- Presenter.h -----
#import <Foundation/Foundation.h>
#import "MVPModel.h"
#import "MVPView.h"
@interface Presenter : NSObject <MVPViewDelegete>
@property (nonatomic, strong) MVPModel* mvpModel;
@property (nonatomic, strong) MVPView* mvpView;
- (void)doPrintWork;
@end
// ----- Presenter.m -----
#import "Presenter.h"
@implementation Presenter
- (void)doPrintWork {
NSString *content = self.mvpModel.contentString;
self.mvpView.content = content;
}
- (void)MVPViewBtnClick {
NSInteger interger = random() % 10;
self.mvpModel.contentString = [NSString stringWithFormat:@"%ld", (long)interger];
self.mvpView.content = self.mvpModel.contentString;
}
@end
MVPView和MVCView有一個不一樣的地方,
// ----- MVPView.h -----
#import <UIKit/UIKit.h>
#import "MVPModel.h"
@protocol MVPViewDelegete <NSObject>
- (void)MVPViewBtnClick;
@end
@interface MVPView : UIView
@property (nonatomic, strong) NSString* content;
@property (nonatomic, weak) id<MVPViewDelegete> viewDelegate;
@end
// ----- MVCView.h -----
#import <UIKit/UIKit.h>
#import "MVCModel.h"
@protocol MVCViewDelegete <NSObject>
- (void)MVCViewBtnClick;
@end
@interface MVCView : UIView
@property (nonatomic, strong) MVCModel *model;
@property (nonatomic, weak) id<MVCViewDelegete> viewDelegate;
@end
我們可以看到,在MVC模式中的View的數(shù)據(jù)源是Model類型,而在MVP中的View是不知道Model的類型,只知道View需要什么數(shù)據(jù)(可以是任意基本數(shù)據(jù)類型NSString、NSDictionary等),而不管Model。因此可以有個初步的感受,MVC中的View和Model有跟隱含的虛線連接著,View是知道Model的,而在MVP中除了Presenter外,View和Model都是互相不知道的,可以說這是又進一步的把耦合度減低了。
綜上所述,實際上MVP在iOS中并不適用,也可以說我不喜歡,可能寫的實例還沒體現(xiàn)出來我為什么不喜歡MVP的實際原因,因為不管怎么搞你總是會拉著一個拖油瓶Controller,MVP的核心思想是用Presenter去替代Controller,讓View和Model之間的聯(lián)系完全取消,但是我們無法改變Controller在iOS中的地位??,反而MVP在Android中會大放異彩,因為在Android中沒有像在iOS中“萬事皆需Controller”的概念。
MVVM
終于到了MVVM這個我最喜歡的架構了??。MVVM全稱為Model-View-ViewModel,同時也是基于MVC的延伸品,只不過它沒MVP那般強硬,使用MVVM我們只需要記住一個思想——“雙向綁定”,我們只要達到View和ViewModel、Model和ViewModel的雙向綁定即可。
不需要管是否有Controller的存在,而且MVVM也不允許View和Model直接聯(lián)系,而是通過一個ViewModel實例去聯(lián)系起來,而且這個ViewModel還是和View與Model進行了雙向綁定的,只要Model中的數(shù)據(jù)發(fā)生了改變,View就會監(jiān)聽到這個改變,從而賦值達到重新渲染數(shù)據(jù)刷新UI。
所以我們要解決的就是如何進行“雙向綁定”,而這個“雙向綁定”只是個指導思想,我們完全可以用前文“架構基礎”中講述的三個方法完成,而之前我一直覺得使用蘋果自己提供的KVO(key——value-Observe)寫起來太累了就只用了delegate去實現(xiàn),當然也可能是因為團隊小伙伴們對MVVM跟我當初一樣比較迷茫,再加上我偷懶把ViewModel揉在Controller里,使用了delegate來實現(xiàn)“雙向綁定”,就導致了大家看得云里霧里。??。
剛好在這段時間中有網友推薦使用Facebook開源的KVOController能夠有效降低手擼原生KVO API的痛苦(我是覺得很痛苦),借此機會我們來舉個KVO實現(xiàn)MVVM最小集的????。
MVVMController
同樣Controller也是要完成Model和View關系建立
#import "MVVMViewController.h"
#import "MVVMViewModel.h"
#import "MVVMModel.h"
#import "MVVMView.h"
@interface MVVMViewController ()
@property (nonatomic, strong) MVVMViewModel* viewModel;
@property (nonatomic, strong) MVVMView* mvvmView;
@property (nonatomic, strong) MVVMModel* mvvmModel;
@end
@implementation MVVMViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.mvvmView = [[MVVMView alloc] init];
self.mvvmView.frame = self.view.bounds;
[self.view addSubview:self.mvvmView];
self.mvvmModel = [[MVVMModel alloc] init];
self.mvvmModel.content = @"2333";
self.viewModel = [[MVVMViewModel alloc] init];
self.viewModel.contentString = self.mvvmModel.content;
[self.mvvmView setWithViewModel:self.viewModel];
[self.viewModel setWithModel:self.mvvmModel];
}
@end
MVVMView
// ----- MVVMView.h -----
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "MVVMViewModel.h"
@interface MVVMView : UIView
@property (nonatomic, strong) NSString* content;
- (void)setWithViewModel:(MVVMViewModel *)vm;
@end
// ----- MVVMView.m -----
#import "MVVMView.h"
#import "FBKVOController.h"
#import "MVVMViewModel.h"
#import "NSObject+FBKVOController.h"
@interface MVVMView ()
@property (nonatomic, strong) UILabel* tipsLabel;
@property (nonatomic, strong) MVVMViewModel* vm;
@end
@implementation MVVMView
- (instancetype)init {
self = [super init];
if (self) {
[self initView];
}
return self;
}
- (void)initView {
self.backgroundColor = [UIColor lightGrayColor];
self.tipsLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 20)];
[self addSubview:self.tipsLabel];
self.tipsLabel.font = [UIFont systemFontOfSize:25];
self.tipsLabel.textAlignment = NSTextAlignmentCenter;
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 300, 200, 30)];
[self addSubview:btn];
[btn addTarget:self action:@selector(btnClick) forControlEvents:1<<6];
[btn setTitle:@"點我??!" forState:UIControlStateNormal];
}
- (void)setContent:(NSString *)content {
_content = content;
self.tipsLabel.text = content;
}
- (void)btnClick {
[self.vm doPrintWork];
}
- (void)setWithViewModel:(MVVMViewModel *)vm {
self.vm = vm;
[self.KVOController observe:vm keyPath:@"contentString" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
NSString *newContent = change[NSKeyValueChangeNewKey];
self.tipsLabel.text = newContent;
}];
}
MVVMView中,我們使用了Facebook開源的KVOController封裝好的蘋果提供的原生KVO API,MVVMView的其它東西跟之前一樣,只不過它的數(shù)據(jù)源獲取方法變成了,
- (void)setWithViewModel:(MVVMViewModel *)vm {
self.vm = vm;
[self.KVOController observe:vm keyPath:@"contentString" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
NSString *newContent = change[NSKeyValueChangeNewKey];
self.tipsLabel.text = newContent;
}];
}
它在此使用KVO監(jiān)聽了vm對象的contentString屬性,只要我們把MVVMView和MVVMViewModel的對應關系都確定了,當contentString發(fā)生變化時,能夠實時的修改數(shù)據(jù)刷新UI。當然,如果不想達到這種自動的效果,那就跟我當初一樣用delegate去手動實現(xiàn)“雙向綁定”吧??。
MVVMViewModel
// ----- MVVMViewModel -----
#import <Foundation/Foundation.h>
#import "MVVMModel.h"
@interface MVVMViewModel : NSObject
@property (nonatomic, strong) NSString* contentString;
- (void)setWithModel:(MVVMModel *)model;
- (void)doPrintWork;
@end
// ----- MVVMViewModel -----
#import "MVVMViewModel.h"
@interface MVVMViewModel ()
@property (nonatomic, strong) MVVMModel* mvvmModel;
@end
@implementation MVVMViewModel
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
-(void)setWithModel:(MVVMModel *)model {
self.mvvmModel = model;
self.contentString = model.content;
}
- (void)doPrintWork {
NSInteger interger = random() % 10;
self.mvvmModel.content = [NSString stringWithFormat:@"%ld", (long)interger];
self.contentString = self.mvvmModel.content;
}
@end
其它類都是一樣的。
原文鏈接:pjhubs.com