在開(kāi)發(fā)應(yīng)用時(shí),一般我們都是一個(gè)view對(duì)應(yīng)一個(gè)model作為它的數(shù)據(jù)源,model的實(shí)例變量值通過(guò)一定的對(duì)應(yīng)關(guān)系顯示到view上,這就造成了view依賴model。一個(gè)view的存在必須依賴指定類(lèi)型的model。
比如有個(gè)view
@interface CCView : UIView
@property (nonatomic,strong) UILabel *titleLabel;
@property (nonatomic,strong) UILabel *subtitleLabel;
@property (nonatomic,strong) CCAModel *aModel;
@end
它的數(shù)據(jù)源model
@interface CCAModel : NSObject
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *age;
@end
我們一定會(huì)有如下代碼,view內(nèi)容的顯示邏輯
self.titleLabel.text = aModel.name;
self.subtitleLabel.text = aModel.age;
而有的時(shí)候兩種類(lèi)型model都顯示成同一種類(lèi)型的view,現(xiàn)在又多了一個(gè)數(shù)據(jù)源類(lèi)型。
@interface CCBModel : NSObject
@property (nonatomic,strong) NSString *story;
@property (nonatomic,strong) NSString *description;
@end
我猜你馬上會(huì)想到兩種非常簡(jiǎn)單的方法解決。一種是讓view去兼容這兩個(gè)model,在view顯示的時(shí)候去辨別當(dāng)前數(shù)據(jù)源是哪個(gè)類(lèi)型的model我要顯示它的哪個(gè)實(shí)例變量,也就是要給view添加多個(gè)類(lèi)型model的實(shí)例變量;一種重新生成一個(gè)新的model來(lái)作兼容,把其他類(lèi)型的model都轉(zhuǎn)為同一種類(lèi)型。那么問(wèn)題來(lái)了,如果使用第一種方法我一個(gè)view會(huì)有多個(gè)類(lèi)型model做數(shù)據(jù)源時(shí)代碼會(huì)寫(xiě)的非常復(fù)雜,每次新增都會(huì)引起很多代碼變動(dòng),代碼會(huì)變得非常龐大并包含很多if else。如果使用第二種方法那每新增一個(gè)model類(lèi)型都要做一次轉(zhuǎn)化。下面來(lái)說(shuō)說(shuō)最近用到的兩個(gè)方法。
類(lèi)簇形式
類(lèi)簇,在iOS中大量的被使用,它將一些私有的、具體的子類(lèi)組合在一個(gè)公共的、抽象的超類(lèi)下面,由超類(lèi)做一個(gè)所有對(duì)外接口的承載。NSString,NSArray,NSDictionary,NSNumber都是類(lèi)簇的實(shí)現(xiàn)。在背后它隱藏了很多實(shí)現(xiàn)類(lèi)卻只暴露最簡(jiǎn)單的接口。有時(shí)你會(huì)發(fā)現(xiàn),變量聲明的NSString類(lèi)型,打印出來(lái)發(fā)現(xiàn)是__NSCFString類(lèi)型,說(shuō)明NSString類(lèi)型的對(duì)象實(shí)際是用__NSCFString類(lèi)型實(shí)例化的。
接著分析上面的問(wèn)題,當(dāng)一個(gè)類(lèi)型的view有多個(gè)類(lèi)型model作為數(shù)據(jù)源時(shí),這些model具有相似的行為就是給view上顯示的元素提供對(duì)應(yīng)的數(shù)據(jù),僅僅他們的實(shí)現(xiàn)形式可能會(huì)有不同,我們就可以抽象出超類(lèi)。view并不關(guān)心是哪個(gè)類(lèi)型model對(duì)它提供數(shù)據(jù),只關(guān)心要顯示的內(nèi)容,model的差異都在工廠內(nèi)處理,view需要的數(shù)據(jù)用協(xié)議進(jìn)行聲明。
所以在view增加對(duì)數(shù)據(jù)源獲取的協(xié)議
@protocol CCViewModelProtocol <NSObject>
- (NSString *)title;
- (NSString *)subtitle;
@end
@interface CCView : UIView
@property (nonatomic,strong) UILabel *titleLabel;
@property (nonatomic,strong) UILabel *subtitleLabel;
@property (nonatomic,strong) id<CCViewModelProtocol> model;
@end
model就是view指定的數(shù)據(jù)源。然后我們創(chuàng)建一個(gè)承載對(duì)接view接口的類(lèi)。這個(gè)類(lèi)可以不作為超類(lèi),因?yàn)樵趯?shí)際開(kāi)發(fā)當(dāng)中model的來(lái)源可能很多。并不一定都能繼承子同一個(gè)類(lèi),所以只要它滿足協(xié)議就可以了,這樣更為靈活。
@interface CCModel : NSObject <CCViewModelProtocol>
- (id)modelWithType:(NSInteger)type;
@end
@implementation CCModel
- (id)modelWithType:(NSInteger)type {
switch (type) {
case 0:
return [CCAModel new];
break;
case 1:
return [CCBModel new];
break;
default:
return [super init];
break;
}
}
- (NSString *)title {
return @"";
}
- (NSString *)subtitle {
return @"";
}
@end
之后在修改各個(gè)model增加CCViewModelProtocol協(xié)議即可。必要的時(shí)候可以新建一個(gè)子類(lèi)繼承原有的數(shù)據(jù)類(lèi)型,再添加協(xié)議??傊@里的核心就是定義了一個(gè)view需要數(shù)據(jù)源接口的協(xié)議,然后通過(guò)一個(gè)抽象的超類(lèi)包裝所有具體實(shí)現(xiàn)的那些model數(shù)據(jù)類(lèi)型。
代理形式
NSProxy用來(lái)實(shí)現(xiàn)消息轉(zhuǎn)發(fā),給了我們一個(gè)思路。view對(duì)顯示數(shù)據(jù)的獲取,從一個(gè)代理獲取。代理里面做的事情,就是把消息轉(zhuǎn)發(fā)給對(duì)應(yīng)的model數(shù)據(jù)類(lèi)型。
我們同樣需要一個(gè)協(xié)議,這個(gè)協(xié)議定義view所需要的數(shù)據(jù)接口。
@protocol CCViewModelProtocol <NSObject>
- (NSString *)title;
- (NSString *)subtitle;
@end
@interface CCView : UIView
@property (nonatomic,strong) UILabel *titleLabel;
@property (nonatomic,strong) UILabel *subtitleLabel;
@property (nonatomic,strong) id<CCViewModelProtocol> proxy;
@end
差別只是在于中間接口的承載著,用代理來(lái)實(shí)現(xiàn)消息的轉(zhuǎn)發(fā)。把具體實(shí)現(xiàn)接口的實(shí)例注入到代理中,然后由代理進(jìn)行消息轉(zhuǎn)發(fā)。
@interface CCProxy : NSProxy
- (void)injectObject:(id)object;
@end
@implementation CCProxy {
id _object;
}
- (void)injectObject:(id)object{
_object = object;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
if ([_object respondsToSelector:sel]) {
return [_object methodSignatureForSelector:sel];
}
return [super methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
if ([_object respondsToSelector:invocation.selector]) {
[invocation invokeWithTarget:_object];
return;
}
[super forwardInvocation:invocation];
}
@end
調(diào)用時(shí)的代碼如下
CCAModel *aModel = [[CCAModel alloc] init];
CCProxy *proxy = [CCProxy alloc];
[proxy injectObject:aModel];
view.proxy = (id)proxy;
view.titleLabel.text = [view.proxy title];
view.subtitleLabel.text = [view.proxy subtitle];
這只是一個(gè)簡(jiǎn)單代理的使用方式,同時(shí)我們可以用它支持更多協(xié)議。只要在注入對(duì)象時(shí),同時(shí)注入對(duì)應(yīng)的協(xié)議。用一個(gè)字典來(lái)保存協(xié)議和對(duì)象的對(duì)應(yīng)關(guān)系,消息轉(zhuǎn)發(fā)時(shí)再用respondsToSelector來(lái)判斷,這樣可以代理就會(huì)變得更加靈活。
總結(jié)一下,view與model解耦的核心在于view獲取對(duì)應(yīng)數(shù)據(jù)內(nèi)容時(shí)抽象出的協(xié)議接口。這些接口的實(shí)現(xiàn)可以通過(guò)一個(gè)類(lèi)簇(其實(shí)就是一個(gè)抽象工廠)或代理來(lái)實(shí)現(xiàn)。類(lèi)簇的方式返回真正執(zhí)行接口的對(duì)象,代理的方式把消息轉(zhuǎn)發(fā)給真正實(shí)現(xiàn)接口的對(duì)象。通過(guò)中間這一層將view于model分離做到解耦。