Objective-C回调

1.回调机制
所谓回调就是讲一段可执行的代码与特定的一个事件绑定起来,当事件发生时就会调用这段代码。
Objective-C的回调有四种途径实现

  1. 目标-动作对(target-action): 事件发生时,向特定的对象发送特定的消息。接收消息的对象为目标,消息的选择器(selector)是动作。
  2. 辅助对象(helper objects):事件发生时,向遵守特定协议的辅助对象发送消息。委托对象(delegate)和数据源(data source)是常见的辅助对象。
  3. 通知(notification): 苹果公司添加了一种称之为通知中心(notification center)的对象。程序开始前,可以告知消息中心“某个对象正在等待某些特定的通知。当通知出现时,向指定的对象发送特定的消息”。即事件发生时,相关对象向通知中心发布通知,然后由通知中心将通知发生给等待通知的对象。
  4. Block对象(Blocks): Block是一段可执行代码,声明一个Block对象,在事件发生时,调用该Block对象。
事件驱动的程序需要一个等待事件发生的负责,OS X和iOS系统用NSRunloop的类(运行循环)来等待事件的发生,示例代码如下:
#import int main(int argc, const char * argv[]) { @autoreleasepool{ [[NSRunLoop currentRunLoop] run]; } return 0; }

Objective-C回调
文章图片
Runloop runloop是一种闲时循环,等待事件的发生,runloop会有一个autorelease pool,runloop更新时[pool drain],向池中的对象发送release消息。
2.具体实现
(1)目标-动作对(target-action) 以NSTimer对象每隔2S,让一个TSLogger对象设置时间和打印时间。
//TSLogger.h #import @interface TSLogger : NSObject@property (nonatomic) NSDate * lastTime; -(NSString *)lastTimeString; -(void)updateLastTime:(NSTimer *)t; @end

//TSLogger.h #import "TSLogger.h"@implementation TSLogger-(NSString *)lastTimeString { static NSDateFormatter *dateFormater = nil; if(!dateFormater) { dateFormater = [[NSDateFormatter alloc] init]; [dateFormater setTimeStyle:NSDateFormatterMediumStyle]; [dateFormater setDateStyle:NSDateFormatterMediumStyle]; NSLog(@"create dateFormater"); } return [dateFormater stringFromDate:self.lastTime]; }-(void)updateLastTime:(NSTimer *)t { NSDate *now = [NSDate date]; [self setLastTime:now]; NSLog(@"just set time to %@", self.lastTimeString); }@end

//main.m #import #import "TSLogger.h"int main(int argc, const char * argv[]) { @autoreleasepool{ TSLogger *logger = [[TSLogger alloc] init]; __unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger selector:@selector(updateLastTime:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] run]; } return 0; }

先看main.m中在NSTimer对象timer中设置了目标-动作对的目标为TSLogger对象logger,动作为updateLastTime:,时间是每隔2S,重复进行。而在TSLogger对象中进行了具体的动作方法的实现。
这就是目标-动作对的大体用法,即在指定的时刻触发事件,情况比较简单,而且只做了一件事。
(2)辅助对象(helper objects) 辅助对象有委托对象和数据源两种,我们先看委托对象
<1>委托对象 委托(delegate)就是将一件属于委托者做的事情交给被委托者来处理。受委托完成任务的对象称之为委托对象。
创建一个委托协议;
对于委托者:
  1. 委托者声明要委托的属性,该属性遵守协议;
  2. 委托者在自己实现的方法通过遵守协议的属性,调用协议内的方法;
  3. 委托者设置好需要委托对象维护的属性
对于委托对象:
  1. 委托对象实现委托协议所定义的方法。
举例如下
有个人想要开公交赚钱,但是只有自己一个人忙不过来,需要找个售票的帮他卖票和告知情况。这里我们假定每2S来一个乘客。
  1. 创建一个Bus协议,描述需求是需要会卖票和报告卖票信息。对应第一步
  2. 在公交车类里声明一个卖票者属性,这个属性应由外界赋值。对应A.1
  3. 公交车上路后(startRun),代码内部的sellTicket由准守该协议的onePerson执行。对应A.2
//公交车的使用协议 @protocol BusProtocol -(void)sellTicket; -(void)reportSituation; @end//公交车类的声明 @interface Bus : NSObject@property(nonatomic,strong)idonePerson; -(void)startRun; @end//公交车类的实现 @implementation Bus-(void)startRun { if(self.onePerson) { [self.onePerson sellTicket]; [self.onePerson reportSituation]; } }@end

我们需要的卖票对象如下,它遵守协议实现了卖票的方法。对应B.1
//委托对象seller类的声明 @interface Seller : NSObject @end//委托对象seller类的实现 @implementation Seller-(void)sellTicket { NSLog(@"开始售票!"); } -(void)reportSituation { NSLog(@"完成售票!"); } @end

【Objective-C回调】最后在main中,设置委托者里需要委托对象维护的属性,即onePerson是委托对象seller。对应A.3
#import #import "Bus.h" #import "Seller.h"int main(int argc, const char * argv[]) { @autoreleasepool{ Bus * busone = [[Bus alloc] init]; Seller * sell = [[Seller alloc] init]; busone.onePerson = (id) sell; __unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:busone selector:@selector(startRun) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] run]; } return 0; }

结果每来一个客人(每2S),Bus都会委托Seller对象进行售票和汇报。
2018-04-08 16:51:40.768918+0800 test[1612:289822] 开始售票! 2018-04-08 16:51:40.768946+0800 test[1612:289822] 完成售票! 2018-04-08 16:51:42.769788+0800 test[1612:289822] 开始售票! 2018-04-08 16:51:42.769820+0800 test[1612:289822] 完成售票!

委托可以向一个对象发送多个回调。
(3)通知(notification) 一个对象发生变化时,多个对象想要获得这个变化的通知,比如我们修改了系统的时区,很多对象会想要知晓这一变化。系统的时区发生变化时,会向通知中心发送NSSystemTimeZoneDidChangeNotification通知,然后通知中心会将该通知转发给相应的观察者们。
实例代码如下,将airlineHostess(空姐)和airlineBoy作为观察者,修改时区后,airlineHostess和airlineBoy广播换时区了,一个中文,一个英文。
//airlineHostess.h #import @interface airlineHostess : NSObject -(void) report; @end//airlineHostess.m #import "airlineHostess.h"@implementation airlineHostess -(void) report { NSLog(@"亲爱的乘客们,我们换时区啦!"); } @end

main.m中注册观察者
#import #import "airlineHostess.h"int main(int argc, const char * argv[]) { @autoreleasepool{ airlineHostess * beauty = [[airlineHostess alloc] init]; [[NSNotificationCenter defaultCenter] addObserver:beauty selector:@selector(report) name:NSSystemTimeZoneDidChangeNotification object:nil]; airlineBoy * handsome = [[airlineBoy alloc] init]; [[NSNotificationCenter defaultCenter] addObserver:handsome selector:@selector(reportEnglish) name:NSSystemTimeZoneDidChangeNotification object:nil]; [[NSRunLoop currentRunLoop] run]; }return 0; }

运行后,打开系统的时间与偏好设置修改时区,结果如下。
2018-04-08 19:18:29.488975+0800 test[1786:359257] 亲爱的乘客们,我们换时区啦! 2018-04-08 19:18:29.516014+0800 test[1786:359257] Dear passengers, we are in a new time zone!

(4)Block对象(Blocks) Block知识点较多,将放入下一篇文章讲解
block对象

    推荐阅读