阅读 734

如何避免UITableView重写大量delegate以及n多if-else判断和Block

前言

近来换工作,接手一项目,不知如何评价,有优点(一些新技术都要加上去,估计多半只是前前同事拿来练手),缺点也格外明显。最明显的缺点是90%的代码堆积在UIViewController中,相信不用多说,各位大佬就秒懂。这样的代码中更多的是if-else堆积,每个if-else判断中 200~1000行代码不等,一个UIViewController中 均值 15个if-else判断 , 先替自己默哀一分钟

为了避免n多if-else看的晕头转向,针对使用频率比较高和容易产生大量if-else的UITableView,用Adapter模式+ResonderChain+Strategy模式 来避免每次都要重写UITableView的delegate和dataSource 和在点击cell以及点击cell中控件时造成n个if-else。(Adapter模式是之前一直用的写法,因为我是个“懒人”,代码一行完成的不写两行,所以前期一直用Adapter模式。至于ResonderChain和Strategy模式 是参考了casa的文章一种基于ResponderChain的对象交互方式后与Adapter结合)。Example已经托管到全球最大男性交友网站Github点击cloneOrdown(也是最近才听说这个别称,好吧,已经被曾经虐我成渣渣的微软收购),基于更好的理解这三种组合的综合使用,代码中没有按照传统的MVC或MVVM来写,希望大家能更好的理解这些模式和组合,因为任何一个都可以单独用于不同的场景。

一、Adapter模式 适配UITableView、UITableViewDelegate、UITableViewDataSource

Adapter模式也是常用的设计模式之一,主要是在两个类或协议之间起到桥梁的作用,具体的官方定义大家可以查阅其它资料。Adapter模式主要有三块内容:AdapterAdapteeTarget,Adapter就是适配器,起到中间桥接作用;Adaptee被适配者,Target是适配对象,Adapter的桥接作用就是将把Adaptee适配到Target。如果只是针对单一Adaptee类将其适配到Target称为类对象适配,针对适配多个Adaptee以及其子类,称之为对象适配器。 之前有看《Objective-C编程之道》书中提到了该模式,但是中文翻译版本估计是外行翻译的,读起来不太通顺,建议看英文原版。

接下来简单用图示来说明AdapterUITableView以及其delegatedatasource 之间的关系

Adapter三者关系

简单来讲Adapter起到的作用就是将UITableDelegate和UITableView原来两个不同的目标能和谐的在一起工作,这一步就是将原来我们可能会在View、自定义的TableView 或者像接手项目中ViewController中进行的设置都交给了Adapter来适配。在此关系中,UITableView就是Adaptee(被适配者),我们通过Adapter 将UITableViewDelegate(在此关系中是Target)两者结合起来能够顺利地运行。

到此可以简单理解Adapter将UITableViewDelegate和UITableView结合的这种做法,那么如何给我们的Adaptee 即我们的UITableView都被适配上这一Target呢 ,就通过OC中的类目进行设置

- (void)setAdapter:(UITableViewAdapter *)adapter
{
    [self setDelegate:adapter];
    [self setDataSource:adapter];
    [adapter setView:self];
    [self reloadData];
}
复制代码

这样,在项目中只要将创建的UITableView与Adapter关联便可避免每次重写大量代理方法。

任何一种模式的应用都有其局限性,Adapter模式下有些方法还是不可避免的要重写,如果复杂的页面需要重写的代理方法会增多,所以如果大家要用这种模式,Adapter中尽量考虑多种情况。

另外,Adapter模式下仍旧不可避免会声明出一些Block以供外界调用,如何解耦呢?

二、基于ResponderChain传递点击事件 在此,解耦的出发点是考虑如何消除类与类之间的相互引用即可让事件传递出去。iOS中继承自UIResponse的控件会形成一棵响应树,顶层是UIApplication。所有在这棵响应树上的控件事件可以通过其中的一条线路传递到顶层。结合这一特性,我们便可将原来声明的Block砍掉,换成响应事件传递。要想事件沿着整个Responder传递,我们同样需要给UIResponse统一设置

- (void)rountEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo
{
    [[self nextResponder] rountEvent:eventName userInfo:userInfo];
}
复制代码

在原来需要传递Block或设置代理的地方添加rountEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo,比如在cell的点击事件中添加

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    
    [_tableView rountEvent:kCCellSeletedEventName userInfo:@{@"indexPath":indexPath}];
    
}
复制代码

这种传递模式可以应用到我们平常使用的其它模式或场景中。

三、Strategy模式避免if-else

策略模式通常把一个系列的算法包装到一系列的策略类里面,这里我们包装的不是算法,而是NSInvocation 提到NSInvocation研究过消息转发机制的同学应该对此并不陌生,为什么选择包装NSInvocation,因为我们最终会回归到方法事件调用。如果没有研究过消息转发机制,那么大家对这行代码相比熟悉的不能再熟悉了

[button addTarget:self action:@selector(<#selector#>) forControlEvents:UIControlEventTouchUpInside]
复制代码

这行代码实际上最终会创建一NSInvocaion,明白了为何我们要包装的是NSInvocation之后,接下来可以选择根据不同的eventName创建不同的NSInvocation

- (NSDictionary *)strategyDictionary{
    
    NSDictionary *strategyDictionary = @{kCCellSeletedEventName:[self createInvocationWithSeletor:@selector(jumpToController:)]};
    return strategyDictionary;
    
}
复制代码

将点击Cell对应的NSInvocation放在一个字典中,在控制器处理事件时再根据不同的事件名称取出对应的NSInvocation分别执行

- (void)rountEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo
{
    NSInvocation *invocation = [self strategyDictionary][eventName];
    [invocation setArgument:&userInfo atIndex:2];
    [invocation invoke];
}
复制代码

这样在点击Cell的时候我们定义的方法就会执行了,如果事件处理增多,那么可以在字典中增加相应的NSInvocation

至于创建NSInvocation,可以单独声明出去

- (NSInvocation *)createInvocationWithSeletor:(SEL)seletorname
{
    
    NSInvocation *invocaion = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:seletorname]];
    [invocaion setTarget:self];
    [invocaion setSelector:seletorname];
    return invocaion;
    
}
复制代码

基于ResponserChain的交互方式还可以结合结合其它模式,大家可以去casa的博客中看。

得益于这两种模式,一种传递方式,改了接手项目很多冗余的代码和大量的if-else,写成博文,希望对大家有所帮助。

文章分类
iOS