说说view与model解耦

在开发应用时,一般我们都是一个view对应一个model作为它的数据源,model的实例变量值通过一定的对应关系显示到view上,这就造成了view依赖model。一个view的存在必须依赖指定类型的model。

比如有个view

@interface CCView : UIView

@property (nonatomic,strong) UILabel *titleLabel;
@property (nonatomic,strong) UILabel *subtitleLabel;

@property (nonatomic,strong) CCAModel *aModel;

@end

它的数据源model

@interface CCAModel : NSObject

@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *age;

@end

我们一定会有如下代码,view内容的显示逻辑

self.titleLabel.text = aModel.name;
self.subtitleLabel.text = aModel.age;

而有的时候两种类型model都显示成同一种类型的view,现在又多了一个数据源类型。

@interface CCBModel : NSObject

@property (nonatomic,strong) NSString *story;
@property (nonatomic,strong) NSString *description;

@end

我猜你马上会想到两种非常简单的方法解决。一种是让view去兼容这两个model,在view显示的时候去辨别当前数据源是哪个类型的model我要显示它的哪个实例变量,也就是要给view添加多个类型model的实例变量;一种重新生成一个新的model来作兼容,把其他类型的model都转为同一种类型。那么问题来了,如果使用第一种方法我一个view会有多个类型model做数据源时代码会写的非常复杂,每次新增都会引起很多代码变动,代码会变得非常庞大并包含很多if else。如果使用第二种方法那每新增一个model类型都要做一次转化。下面来说说最近用到的两个方法。

类簇形式

类簇,在iOS中大量的被使用,它将一些私有的、具体的子类组合在一个公共的、抽象的超类下面,由超类做一个所有对外接口的承载。NSString,NSArray,NSDictionary,NSNumber都是类簇的实现。在背后它隐藏了很多实现类却只暴露最简单的接口。有时你会发现,变量声明的NSString类型,打印出来发现是__NSCFString类型,说明NSString类型的对象实际是用__NSCFString类型实例化的。

接着分析上面的问题,当一个类型的view有多个类型model作为数据源时,这些model具有相似的行为就是给view上显示的元素提供对应的数据,仅仅他们的实现形式可能会有不同,我们就可以抽象出超类。view并不关心是哪个类型model对它提供数据,只关心要显示的内容,model的差异都在工厂内处理,view需要的数据用协议进行声明。

所以在view增加对数据源获取的协议

@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指定的数据源。然后我们创建一个承载对接view接口的类。这个类可以不作为超类,因为在实际开发当中model的来源可能很多。并不一定都能继承子同一个类,所以只要它满足协议就可以了,这样更为灵活。

@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

之后在修改各个model增加CCViewModelProtocol协议即可。必要的时候可以新建一个子类继承原有的数据类型,再添加协议。总之这里的核心就是定义了一个view需要数据源接口的协议,然后通过一个抽象的超类包装所有具体实现的那些model数据类型。

代理形式

NSProxy用来实现消息转发,给了我们一个思路。view对显示数据的获取,从一个代理获取。代理里面做的事情,就是把消息转发给对应的model数据类型。

我们同样需要一个协议,这个协议定义view所需要的数据接口。

@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

差别只是在于中间接口的承载着,用代理来实现消息的转发。把具体实现接口的实例注入到代理中,然后由代理进行消息转发。

@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

调用时的代码如下

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];

这只是一个简单代理的使用方式,同时我们可以用它支持更多协议。只要在注入对象时,同时注入对应的协议。用一个字典来保存协议和对象的对应关系,消息转发时再用respondsToSelector来判断,这样可以代理就会变得更加灵活。

总结一下,view与model解耦的核心在于view获取对应数据内容时抽象出的协议接口。这些接口的实现可以通过一个类簇(其实就是一个抽象工厂)或代理来实现。类簇的方式返回真正执行接口的对象,代理的方式把消息转发给真正实现接口的对象。通过中间这一层将view于model分离做到解耦。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容