說(shuō)說(shuō)view與model解耦

在開(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分離做到解耦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容