1.回调机制
所谓回调就是讲一段可执行的代码与特定的一个事件绑定起来,当事件发生时就会调用这段代码。
Objective-C的回调有四种途径实现
- 目标-动作对(target-action): 事件发生时,向特定的对象发送特定的消息。接收消息的对象为目标,消息的选择器(selector)是动作。
- 辅助对象(helper objects):事件发生时,向遵守特定协议的辅助对象发送消息。委托对象(delegate)和数据源(data source)是常见的辅助对象。
- 通知(notification): 苹果公司添加了一种称之为通知中心(notification center)的对象。程序开始前,可以告知消息中心“某个对象正在等待某些特定的通知。当通知出现时,向指定的对象发送特定的消息”。即事件发生时,相关对象向通知中心发布通知,然后由通知中心将通知发生给等待通知的对象。
- Block对象(Blocks): Block是一段可执行代码,声明一个Block对象,在事件发生时,调用该Block对象。
事件驱动的程序需要一个等待事件发生的负责,OS X和iOS系统用NSRunloop的类(运行循环)来等待事件的发生,示例代码如下:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
@autoreleasepool{
[[NSRunLoop currentRunLoop] run];
}
return 0;
}
runloop是一种闲时循环,等待事件的发生,runloop会有一个autorelease pool,runloop更新时[pool drain],向池中的对象发送release消息。
2.具体实现
(1)目标-动作对(target-action)
以NSTimer对象每隔2S,让一个TSLogger对象设置时间和打印时间。
// TSLogger.h
#import <Foundation/Foundation.h>
@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 <Foundation/Foundation.h>
#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)就是将一件属于委托者做的事情交给被委托者来处理。受委托完成任务的对象称之为委托对象。
创建一个委托协议;
对于委托者:
- 委托者声明要委托的属性,该属性遵守协议;
- 委托者在自己实现的方法通过遵守协议的属性,调用协议内的方法;
- 委托者设置好需要委托对象维护的属性
对于委托对象:
- 委托对象实现委托协议所定义的方法。
举例如下
有个人想要开公交赚钱,但是只有自己一个人忙不过来,需要找个售票的帮他卖票和告知情况。这里我们假定每2S来一个乘客。
- 创建一个Bus协议,描述需求是需要会卖票和报告卖票信息。对应第一步
- 在公交车类里声明一个卖票者属性,这个属性应由外界赋值。对应A.1
- 公交车上路后(startRun),代码内部的sellTicket由准守该协议的onePerson执行。对应A.2
//公交车的使用协议
@protocol BusProtocol <NSObject>
-(void)sellTicket;
-(void)reportSituation;
@end
//公交车类的声明
@interface Bus : NSObject
@property(nonatomic,strong)id<busProtocol>onePerson;
-(void)startRun;
@end
//公交车类的实现
@implementation Bus
-(void)startRun
{
if(self.onePerson)
{
[self.onePerson sellTicket];
[self.onePerson reportSituation];
}
}
@end
我们需要的卖票对象如下,它遵守协议实现了卖票的方法。对应B.1
//委托对象seller类的声明
@interface Seller : NSObject <busProtocol>
@end
//委托对象seller类的实现
@implementation Seller
-(void)sellTicket
{
NSLog(@"开始售票!");
}
-(void)reportSituation
{
NSLog(@"完成售票!");
}
@end
最后在main中,设置委托者里需要委托对象维护的属性,即onePerson是委托对象seller。对应A.3
#import <Foundation/Foundation.h>
#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 <Foundation/Foundation.h>
@interface airlineHostess : NSObject
-(void) report;
@end
// airlineHostess.m
#import "airlineHostess.h"
@implementation airlineHostess
-(void) report
{
NSLog(@"亲爱的乘客们,我们换时区啦!");
}
@end
main.m中注册观察者
#import <Foundation/Foundation.h>
#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知识点较多,将放入下一篇文章讲解