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;
}
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

最后在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对象

你可能感兴趣的:(Objective-C回调)