iOS开发基础:消息传递机制的不同与缺陷

作者: 溪石iOS | 来源:发表于2016-09-20 09:50 被阅读849次
    iOS开发基础:消息传递机制的不同与缺陷

    iOS中的消息传递机制有以下几种:

    1. 代理(Delegation)
    2. 通知(NSNotification)
    3. BLOCK
    4. KVO(key-value observing)
    5. Target-Action
      这么多的消息传递机制,我们该如何选择呢?

    最最基本的消息传递机制

    其实,除了以上列举的5种以外,还有一类,往往被我们忽视,那就是

    方法调用本身就是一种消息传递机制

    方法调用可以代入参数,执行完成后,会有返回值,告诉调用者方法执行的结果。
    由于返回值只能有一个,并且只在方法执行完成后才“返回”,因此对于需要反映执行过程的任务,比如下载进度,方法调用的局限就十分明显了,另外,从线程角度来看,方法调用是在调用者当前线程开辟的,属于一种同步方法,调用者会被“卡死”,直到方法执行完成。

    代理(Delegation)

    代理机制就是为了解决任务执行中,需要与调用者“交互”而出现的,UITableView的“三大问题”就是典型的“交互式”调用:

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    

    每次 reloadData ,以上方法都会被“自动”调用一次或多次,因此给了调用者一个机会,可以改变行数或是Cell的样式,给用户一种“动态变化”的效果。
    代理机制有以下缺点:

    1. 定义繁琐
      代理机制使用的是OC语言的协议, 首先需要定义协议:
    @protocol MyDelegate
    - (int)funWithArg:(int)arg1;
    @end
    
    @interface My : NSObject { 
        id<MyDelegate> deleage;
    }
    @property(assign,nonatomic) id<MyDelegate> delegate;
    @end
    

    调用者需要实现该协议:

    @interface Caller : NSObject<MyDelegate>
    @end
    
    #import "Caller.h"
    @implementation Business
    - (int)funWithArg:(int)arg1 {
        return arg1+1'
    }
    @end
    

    被调用者需要调用代理对象:

    // in my.m
    
    int n = [deleage funWithArg:1];
    
    1. 释放代理对象时,要将被调用者的delegate属性设为nil
    2. 当使用多个同类对象时,代理机制本身没有提供区分的办法
      设想一个controller中添加了两个tableview,controller本身作为代理,代理方法只能使用同一套,要么使用tag作为区分,要么将delegate单独成类,编程都比较繁琐,因此,我们需要一种一对一的便捷方法。

    Block

    回顾一下方法调用,调用时可以指定参数值,并获取对应的返回值,是很单纯的一对一调用模式,如果我们将一些执行方法,“打包”成参数传入,是对这种一对一模式很自然的补充,这就是Block的本质:

    Block允许将方法作为参数传递给方法

    因此Block在执行那些一对一的交互任务时,显得得心应手,比如AFNetworking中处理网络请求的返回值和失败信息,无需担心多次网络请求会导致信息“互串”。
    但由于Block解决了代理定义繁琐,一对多的问题,自然也带来了副作用:

    Block本身是匿名函数,因此复用的办法只能是copy,这是编程大忌

    另外Block的调用是有延迟的,这点往往会被初学者误解,如下面的代码:

    Record *record = [[Record alloc] init];
    [drivingRecord save:^{
        record.createTime = [NSDate date];
    }];
    NSLog(@"%@", record.createTime)
    

    输出的createTime是nil !因为Block可能是使用异步线程调用的。

    通知

    NSNotification是系统Foundation提供的消息机制,使用起来是很直接的:

    • 观察者注册
    • 被观察者发消息
    • 观察者注销
      这是一种对消息发送者来说很“轻松”的消息机制,除了调用
      - postNotification:外,几乎不用做任何事情。

    因为使用简单,因此通知也有一些天生的缺点:

    1. 一对多传递消息,消息发送者不能指定接收者。
    2. 通知发出后,被观察者不能从观察者获得任何的反馈信息,所以这不是一个双向传输的消息机制。
    3. 对观察者来说,调用和得到的结果并没有明显的对应关系,甚至不在同一个类中,调用关系不直观。
    4. - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;被调用多少次,回调就会被执行多少次,该特性往往导致一次post,多次调用;在ViewController中添加注销时,要注意添加和注销是否一一对应,错误往往在ViewController没有被正确释放时出现!

    另外,还有些语法上带来的缺陷:

    1. 消息的主要由消息名称来区分,没有良好的编程习惯,很容易导致消息混淆,使观察者处理了不是发给自己的消息。
    2. 如果没有使用
      - postNotificationName:object: 的object参数告知消息发送者,观察者无法确定消息来源。

    KVO

    添加监听者:
    - addObserver:forKeyPath:options:context:
    所有监听值的变化都会由一个方法获取,必须重写父类的同名方法:

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    

    主要缺陷:

    1. 所有返回结果都由一个方法处理,导致一连串的if判断,修改和阅读都不是十分清晰。如果父类也实现了observeValueForKeyPath...会进一步导致这种混乱。
    2. 由于addObserver... 无法指定单独的回调方法,单独为每次调用指定方法是不可能的。
    3. 事实上,正如KVO的名称所暗示的——键-值-监听——该机制只能适用于值的变化,尝试处理一些没有值(比如按钮事件),或一连串的相关事务时(比如网络下载时,先获取头,再根据头确定下载),用KVO实现就会变得非常臃肿,逻辑不清。
    4. 从代码清晰度角度看,KVO声明的接口是隐性的,与普通属性没有明显的区分(当然,你应该添加注释说明),除非编码尝试,调用者无法获知一个属性是否能够被监听。

    Target-Action

    最典型的就是UIButton的
    - (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;

    SEL中指定的方法是固定格式的
    -(IBAction)doSometing:(id)sender
    可以自己实现一个Target-Action机制,如下声明一个SEL:

    SEL aSelector = @selector(methodName);
    

    执行一个SEL:

    SEL aSelector = @selector(run);
    [aDog performSelector:aSelector];
    [anAthlete performSelector:aSelector];
    

    根据实现原理不难发现,Target-Action机制有以下缺陷:

    1. selector只能查找指定类(含子类)的方法。
    2. SEL 查找的方法不支持类方法
    3. 最大的缺陷就是不能传参数,唯一参数用于传递发消息的控件对象,因此所有传递的值必须放在指定对象中,也因为这个参数是可选的,较容易出错。
    4. 这也是一种单向传输的机制,执行SEL无法获得返回值。

    综上所述,每种消息机制各有自己的适用情形,不存在谁比谁优秀先进的问题,一个复杂系统中往往是各种机制协同工作。

    相关文章

      网友评论

      本文标题:iOS开发基础:消息传递机制的不同与缺陷

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