美文网首页iOS高级开发iOS面试资料程序员
iOS架构:Proxy实现局部模块化(附Demo)

iOS架构:Proxy实现局部模块化(附Demo)

作者: 波儿菜 | 来源:发表于2018-03-30 16:49 被阅读2241次

博客更新记录:首先感谢 Casa Taloyum 前辈对该篇博客错误的指出(或者说打脸😭哈哈),笔者对 AOP 理解错误影响到各位读者的阅读体验,在此深表歉意。Casa Taloyum 前辈指出该玩儿法应该叫做 Proxy 模式,笔者对其了解了一番,深以为然。 ———— 2018-4-24

一、写在前面

Proxy 设计模式:为其他对象提供一种代理以控制对这个对象的访问。
在 iOS 开发中,Proxy 设计模式的体现更多的是以单个代理的方式,笔者为了优雅的达到更深层次的模块化,基于消息转发间接的实现多代理。

喜欢看代码的可直接看 DEMO

Proxy 局部模块化 DEMO

二、更深层次模块化

在 iOS App 中,MVC 和 MVVM 是比较流行的架构模式,而当某个界面业务量达到一个程度过后,MVVM 甚至是 VIPER 模式都显得有些力不从心。

为了达到更高层次的模块化,往往会做其他方面的工作,比如将UITableView等代理方法的实现独立出,然而代理方法里面的逻辑太多会导致独立出来的类仍然臃肿;更细化一点的法子是在代理方法里面将具体实现分离出去,由其他类完成。

不过,这些方式都不够优雅。

所以,这就是本文的目的,提供一种更深层次的模块化的方案。

三、实际应用

Proxy设计模式,在笔者之前的框架中已经有了应用,实现该框架难点就是:利用方法重定向实现多接收者的消息转发。

详情可看这篇文章,文章中间部分有对消息转发流程的简述:
iOS解决方案:文本输入控制(献上框架)

本文就不讲解消息发送机制了,在 Demo 中有封装 —— YBProxyManager,我们将利用它来做局部模块化。

在实际业务需求中,出场率很高的是UITalbeViewUICollecitonView等需要用大量代理方法配置的视图,当然这是苹果程序设计的惯例。当UI界面很复杂,业务逻辑相当多的时候,虽然把网络请求、数据处理、视图封装等都模块分离出去了,但是配置代理里面的逻辑太多,我们想要每一个类处理一部分代理方法。

Demo 以 UITableView 为例。

首先,创建实现 UITableView 代理的三个类:

@implementation TestTableViewDigitConfig
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 20;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 80;
}
@end
@implementation TestTableViewClickConfig
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"click -- section:%ld, row:%ld", indexPath.section, indexPath.row);
}
@end
@implementation TestTableViewCellConfig
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(UITableViewCell.class)];
    if (!cell) {
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:NSStringFromClass(UITableViewCell.class)];
    }
    cell.textLabel.text = [NSString stringWithFormat:@"第%ld行", indexPath.row];
    return cell;
}
@end

如代码所见,这里将 tableView 的代理用三个类来分别实现,然后在 UIViewController 里面只需要写这些代码:

@interface TestVC ()

@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) YBProxyManager *proxyManager;
@property (nonatomic, strong) TestTableViewDigitConfig *digitConfig;
@property (nonatomic, strong) TestTableViewClickConfig *clickConfig;
@property (nonatomic, strong) TestTableViewCellConfig *cellConfig;

@end

@implementation TestVC

#pragma mark life cycle
- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.tableView];
}

#pragma mark getter
- (UITableView *)tableView {
    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStylePlain];
        _tableView.tableFooterView = [UIView new];
        
        _digitConfig = [TestTableViewDigitConfig new];
        _clickConfig = [TestTableViewClickConfig new];
        _cellConfig = [TestTableViewCellConfig new];
        
        _proxyManager = [YBProxyManager new];
        [_proxyManager addTarget:_digitConfig];
        [_proxyManager addTarget:_clickConfig];
        [_proxyManager addTarget:_cellConfig];
        
        _tableView.delegate = _proxyManager;
        _tableView.dataSource = _proxyManager;
    }
    return _tableView;
}
@end

核心代码就是将 YBProxyManager 类的使用:

当你需要使用多个对象(target)来承接一些方法的实现,初始化 YBProxyManager 实例,将这些对象实例添加到
YBProxyManager 实例中(addTarget),最后将 YBProxyManager 实例作为这些方法的第一承接者。剩下的方法分发工作就交给该类了。

代码请看 DEMO,不复杂。

Proxy 局部模块化 DEMO

相关文章

网友评论

  • 董朋远:targets多的时候,每次都去遍历好吗?加个cache是不是好点
    波儿菜:@indulge_in 用hashmap应该可以
    波儿菜:@董朋远 嗯嗯 之前一直想替换成hash 没有抽时间去搞
  • 董朋远:为什么用NSPointerArray呢, NSHashTable 不可以?
    波儿菜:@董朋远 NSHashTable的是根据value的hash值存储的,可能会存在相同的hash不同的内存,因为不可能每次都去写hash算法
    董朋远:@indulge_in 两个指针比较,跟hash有什么关系
    波儿菜:@董朋远 实际上我考虑了一下 用hash存在一个潜在的问题,若两个响应者hash值一样会出现覆盖
  • 波儿菜:你可以这么想,其实只是实现一个多代理,达到模块细化的目的,它就是proxy设计模式。
    目的是达到模块细化,而不是复用,而复用部分就是消息转发的处理,既YBProxyManager。
    你说的cell与tableview密切关系,在所有代理里面就有反tableivew,数据源的处理方面确实要麻烦一点,但是在逻辑超多的情况下,模块细化带来的收益远大于多处理一个数据源。其实经常考虑架构方面的东西,这些设计方式都是灵活的,只是这里提出的多代理方式模块划分比较优雅
  • 老坛酸菜鱼:_tableView.delegate = _aopManager;
    _tableView.dataSource = _aopManager;

    这两行会有警告,强迫症不会考虑。而且有可能漏掉 @required 的方法。
    波儿菜:@老坛酸菜鱼 哈哈 你可以写两个宏忽略警告
  • LV大树:那么要写好多manager?
    波儿菜:@FiredPeter 这个并不是全局都用 在需要更细节的模块划分的时候用
  • litt1err:想问下 在这种模式下 我们的事件处理 不需要回调给vc来处理吗
    litt1err:@indulge_in 有点感觉更像代理 z
    波儿菜:@litt1err 回调这些就自己根据业务设计了 这只是一种细分模块的方案
  • d8a217c0c020:大神 demo中有两个编译警告 需要让YBAOPManager声明代理 有一个地方想跟您探讨一下 这个config是不是可以抽出基类 然后这个cofig又可以复用
    波儿菜:@d8a217c0c020 个人感觉没必要 实际上如此细化模块在项目里面也不会用太多
  • 590bf6c23733:你这不叫aop。是proxy模式而已。你所谓的aopmanager事实上就是个proxymanager。
  • 天下无贼_:请问一下,为什么要使用NSPointerArray这个类呢,直接使用NSArray会有问题吗
    波儿菜:@天下无贼_ 为了不引用 百度一下
  • bigCatloveFish:感觉像是 协议拆分 。
  • 资料库:有个问题想请教一下,当cell里面有好多button可以点击的时候是咋处理的?
    波儿菜:@资料库 若这些事件必须在controller处理,则给cell写代理将委托者设为controller;若可以在该代理细分类处理,随你用代理还是闭包
    资料库:@indulge_in 是的用了这个方法之后cell里面有很多button点击该怎么转发处理???
    波儿菜:@资料库 是在使用了本文方法之后么?
  • CoderXLL:不错的👍
    波儿菜:@CoderXLL :grin:
  • tztTzt:实际上还是消息转发嘛对吧,把对象加到一个数组里面 代理调用协议方法的时候 去数组里面找对象是否可以相应这个方法,你们项目中实用场景多吗
    波儿菜:@wryXX 还是比较多,复杂界面可以用
  • MrJ的杂货铺:耦合度好像并没有降低,只是VC的东西少了
    波儿菜:@我喝多了 额 这个功能都能实现,重在优雅与否,况且这种思想是可以应用于各个场景
    我喝多了:如果只是想从VC中拆分出TableView的协议,直接添加一个类继承UITableViewDelegate ,UITableViewDataSource这两个协议,在里面写好实现,然后在datasource和delegate中指定这个类不就行了么
    波儿菜:感谢兄弟提出,确实懵逼,不知道之前怎么就写了解耦字眼😭。已更改文章用词问题,以及github描述,忘海涵。再次感谢:+1:
  • zl520k:消息转发,发现要调用很多函数,有没有效率问题?
    590bf6c23733:@zl520k strategy模式了解一下。
    波儿菜:@zl520k 调用这几个方法的消耗还是可以接受的吧,就像我们习惯写属性调用setter、getter方法而不全用成员变量。
  • minjing_lin:赞一个,比较新颖,mark,以后可以尝试着用起来,不知道Model数据好传吗?
    d8a217c0c020:我感觉数据可以在config里面配置
    波儿菜:@minjing_lin 哈哈 这个问题就自己考虑怎么设计了 只是提出一种玩儿法:smile:
  • 牵着蜗牛走的我:此人必火 大腿抱住
  • c1482b09ec8a:前排占位

本文标题:iOS架构:Proxy实现局部模块化(附Demo)

本文链接:https://www.haomeiwen.com/subject/xzpbcftx.html