iOS Developer计算机技术一锅炖架构设计与重构

说说view与model解耦

2016-10-28  本文已影响813人  熊妹妹

在开发应用时,一般我们都是一个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分离做到解耦。

上一篇下一篇

猜你喜欢

热点阅读