美文网首页
OC中callback的理解和使用

OC中callback的理解和使用

作者: wilsonhan | 来源:发表于2018-06-03 12:26 被阅读0次

简介
本文通过阅读Objective-C编程一书的第27、28章,对回调的四种方式进行理解。

OC中回调的方式主要有一下四种:

  • 目标-动作对(target-action):当某个事件发生时,向指定对象发送特定消息。target指的是对象,action通过消息选择器(selector)选择。
  • 辅助对象(helper objects):使用协议的方式,当事件发生时,向遵守响应协议的辅助对象发送消息。(协议类似于Java中的接口类,定义了接口方法,让其他类去实现)。
  • 通知(notification):苹果公司提供的方法,通知中心(notification center)对象。程序员向通知中心告知当某个特定事件发生时,向指定的对象发送特定消息。
  • Block对象:Block是一段可执行代码,在事件发生时,执行这段代码。Block类似于匿名函数或者lambda,允许程序员将调用的代码和需要回调的代码写到一起,方便阅读。

目标-动作对 (target-action)

OC中的计时器使用的是target-action机制,创建计时器,设定延迟、目标以及动作,在指定的延迟时间后,计时器会向设定的目标发送指定的消息。
接下来的例子,定义一个Logger类,通过计时器调用Logger的方法,打印当前的时间。计时器的使用方式在main方法中。

WHLogger类定义

@interface WHLogger : NSObject

@property  (nonatomic) NSDate *lastTime;//记录上一次保存的时间
-(NSString*) lastTimeString;//返回lastTime格式化文本
-(void)updateLastTime:(NSTimer *)t;//更新lastTime,用于计时器的调用

@end

WHLogger类实现

@implementation WHLogger

//定义一个static的NSDateFormatter对象,用于格式化lastTime
//返回lastTime的字符串
-(NSString*) lastTimeString{
    static NSDateFormatter *dateFormatter = nil;
    if(!dateFormatter){
        dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
        [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
        NSLog(@"created dateFormatter");
    }
    return [dateFormatter stringFromDate:self.lastTime];
}
//更新lastTime对象,该函数由计时器周期性调用
-(void)updateLastTime:(NSTimer *)t{
    NSDate *now = [NSDate date];
    [self setLastTime:now];
    NSLog(@"Just set time to %@", self.lastTimeString);
}
@end

mian.m实现

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        WHLogger *logger = [[WHLogger alloc] init];//logger对象
        
        __unused NSTimer *timer = [NSTimer//创建一个定时器
                          scheduledTimerWithTimeInterval:2.0//时间间隔2秒,
                          target:logger//target为logger对象,
                          selector:@selector(updateLastTime:)//使用@selector语句传递action的消息名称
                          userInfo:nil 
                          repeats:YES];//重复执行

        [[NSRunLoop currentRunLoop] run];//调用系统的运行循环,NSRunLoop会持续等待,并在特定事件触发时,向相应的对象发送消息。
    }
    return 0;
}

打印结果

2018-06-03 10:47:30.791626+0800 CallbackStudy[44416:5166912] Just set time to 2018年6月3日 上午10:47:30
2018-06-03 10:47:32.788496+0800 CallbackStudy[44416:5166912] Just set time to 2018年6月3日 上午10:47:32
2018-06-03 10:47:34.791369+0800 CallbackStudy[44416:5166912] Just set time to 2018年6月3日 上午10:47:34
2018-06-03 10:47:36.788678+0800 CallbackStudy[44416:5166912] Just set time to 2018年6月3日 上午10:47:36
2018-06-03 10:47:38.790786+0800 CallbackStudy[44416:5166912] Just set time to 2018年6月3日 上午10:47:38

这里有一个小疑问,若在头文件中删除掉updateLastTime方法的声明,仅在.m文件中定义,updateLastTime方法变为一个私有方法,但此时计时器仍能正确调用该方法,系统是如何找到私有方法的?

辅助对象(helper objects)

通过实现协议方法,将对象设置为委托对象,在事件发生时,委托对象的协议方法会被调用。接下来通过网络下载数据的代码来描述委托的使用方式。

在网络中下载数据时,使用同步的方式会导致主线程阻塞,所以在下载东西时,通常使用异步的方式进行下载,通过NSURLConnection的异步模式下载数据。这里在Logger类中实现NSURLConnection的协议,让Logger对象成为NSURLConnection的委托对象(delegate)。

首先看Logger类的定义,该类中声明了要实现的协议

@interface WHLogger : NSObject
    <NSURLConnectionDelegate, NSURLConnectionDataDelegate>//声明WHLogger类会实现这两个协议的方法
{
    NSMutableData *_incomingData;//保存下载的数据
}
@end

Logger类实现,主要实现了协议中的三个方法

  • connection:didReceiveData:收到一定字节数的数据后会被调用
  • connectionDidFinishLoading:最后一部分数据处理完毕后调用
  • connection:didFailWithError:数据失败时被调用
@implementation WHLogger

//收到一定字节数的数据后会被调用
- (void)connection:(NSURLConnection *)connection
    didReceiveData:(NSData *)data
{
    NSLog(@"received %lu bytes", [data length]);
    
    //初始化incomingData
    if(!_incomingData){
        _incomingData = [[NSMutableData alloc] init];
    }
    [_incomingData appendData:data];
}

//最后一部分数据处理完毕后会被调用
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    NSLog(@"Got it all!");
    
    NSData *data = [[NSData alloc] initWithData:_incomingData];
    
    _incomingData = nil;
    NSLog(@"data has %lu characters", [data length]);
    
    [data writeToFile:@"/Users/wilsonhan/Documents/qzx.jpeg" atomically:YES];

}

//获取数据失败时会被调用
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    NSLog(@"connection failed: %@", [error localizedDescription]);
    _incomingData = nil;
}
@end

main.m实现

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        WHLogger *logger = [[WHLogger alloc] init];
        
        NSURL *url = [NSURL URLWithString:@"要下载的数据的网络地址"];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        __unused NSURLConnection *fetchConn = [[NSURLConnection alloc]
                                               initWithRequest:request//下载请求
                                               delegate:logger//设置委托对象
                                               startImmediately:YES];
        
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}

通知(Notifications)

这里通过通知中心,获取用户修改Mac系统的时区设置时的NSSystemTimeZoneDidChangeNotification通知。

Logger类的实现

@implementation WHLogger
//用户修改时区时,调用该函数
-(void)zoneChange:(NSNotification *)note{
    NSLog(@"The system time zone has changed!");
}
@end

main.m的实现

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        WHLogger *logger = [[WHLogger alloc] init];
        
        [[NSNotificationCenter defaultCenter]
                             addObserver:logger//添加logger为观察者
                             selector:@selector(zoneChange:)//使用选择器传递接收消息的方法
                             name:NSSystemTimeZoneDidChangeNotification//设置需要接收的通知
                             object:nil];
        
        [[NSRunLoop currentRunLoop] run];
        
        [[NSNotificationCenter defaultCenter] removeObserver:logger];//要记得移除观察者
    }
    
    return 0;
}

代码执行后,logger对象被注册为观察者,当用户修改系统时区时,运行的代码就会打印一条语句,表明接收到了系统通知。

三种回调方式如何做选择

  • 对于只做一件事情的对象(如NSTimer),使用target-action
  • 对于功能更复杂的对象,使用扶助对象,最常使用的是委托对象
  • 对于要触发多个回调的对象,使用通知。

避免强引用循环引用

  • 对象不拥有target。应在dealloc方法中将target指针赋为nil
  • 对象不拥有委托对象或数据源对象。在dealloc方法中取消相应的关联,调用setDelegate:nil
  • 通知中心不拥有观察者,在释放时将对象移出通知中心,在dealloc中调用[[NSNotificationCenter defaultCenter] removeObserver:self];

Block对象

Block对象是一段代码,没有函数名,由^开始,表明这段代码是一个Block对象。
Block对象类似于其他编程语言的匿名函数、lambda、closure、函数指针等。

这里使用Block实现一个在给定字符串中移除所有元音字母的功能。

main.m的实现

typedef void (^ArrayEnumerationBlock)(id, NSUInteger, BOOL*);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //旧字符串array,保存要处理的所有字符串
        NSArray *oldStrings = @[@"Sauerkraut", @"Raygun", @"Big Nerd Ranch", @"Mississippi"];
        //打印
        NSLog(@"original strings: %@", oldStrings);
        //新字符串数组,保存处理后的字符串
        NSMutableArray *newStrings = [NSMutableArray array];
        //创建数组对象, 保存需要从字符串中移除的字符
        NSArray *vowels = [NSArray arrayWithObjects:@"a", @"e", @"i", @"o", @"u", nil];
        //声明Block变量
        void (^devowelizer)(id, NSUInteger, BOOL *);
        //使用typedef的方式声明的Block变量,与上一行代码功能相同
        //ArrayEnumerationBlock devowelizer;
        //将Block对象赋值给变量
        devowelizer = ^(id string, NSUInteger i, BOOL *stop){
            //获取传入的字符串
            NSMutableString *newString = [NSMutableString stringWithString:string];
            //枚举数组中的字符串,将所有出现的元音字母替换成空字符串
            for(NSString *s in vowels){
                NSRange fullRange = NSMakeRange(0, [newString length]);
                [newString
                 replaceOccurrencesOfString:s
                 withString:@""
                 options:NSCaseInsensitiveSearch
                 range:fullRange];
            }
            //将处理后的字符串添加到newStrings中
            [newStrings addObject:newString];
        };
        //枚举数组对象,针对每个数组中的对象,执行Block对象devowelizer
        [oldStrings enumerateObjectsUsingBlock:devowelizer];
        //这里也可以不用声明block对象,直接编写block代码
        //[oldStrings enumerateObjectsUsingBlock:^{
        //   block代码块
        //}];
        NSLog(@"new strings: %@", newStrings);
    }
    return 0;
}

打印结果

original strings: (
    Sauerkraut,
    Raygun,
    "Big Nerd Ranch",
    Mississippi
)
new strings: (
    Srkrt,
    Rygn,
    "Bg Nrd Rnch",
    Msssspp
)

Block对象与其他回调

通过Block对象,可以将回调有关的代码写在与设置回调的代码相同的地方,这样方便其他程序员阅读这段代码。

将通知部分的代码改成Block

在上文通知部分的代码,可以从观察者方式修改成Block的方式,代码如下

[[NSNotificationCenter defaultCenter]
     addObserverForName:NSSystemTimeZoneDidChangeNotification
     object:nil
     queue:nil
     usingBlock:^(NSNotification *note){
        NSLog(@"The system time zone has changed!");
    }];

调用结果与使用addObserver:selector:name:object方法结果相同。

总结

本文通过简单的例子使用了OC中回调的四种方式,了解了如何使用该四种方式,以及不同回调方式适用的场景,接下来需要对不同回调方式的实现原理进行理解。

相关文章

  • OC中callback的理解和使用

    简介本文通过阅读Objective-C编程一书的第27、28章,对回调的四种方式进行理解。 OC中回调的方式主要有...

  • OC中关于成员变量与属性的理解

    最近重新接触OC,对于OC中成员变量和属性的理解和使用存在一些问题,理出来记录下 q、成员变量、实例变量、属性 在...

  • Swift & JS

    1.使用JS文件中的方法和变量 2.直接使用OC中写JS语句来调用 3.OC中的方法写入JS,JS可以使用OC中的...

  • EventBus 在项目中的使用

    我把它当成强大的callBack来理解、使用。 https://github.com/greenrobot/Eve...

  • OC中消息发送机制

    OC是C的超集,所以理解OC中的消息通讯机制,首先最好理解C语言中的函数调用方式。C语言使用“静态绑定”,也就是说...

  • Swfit语法tips

    关于函数和闭包的介绍 在Swift中定义的Enum,OC中使用 Swift中定义如下,注意@objc 在OC中使用...

  • kashgari学习笔记-1

    1、回调函数的使用 使用了两个回调函数,eval_callback和tf_board_callback。 1、ev...

  • Js中的callback函数的理解

    callback就是把一个函数作为另外一个函数的参数传入,当这个函数执行完成后再调用这个函数. 1.一个函数 2....

  • UIImagePickerController .mediaTy

    先来看最终效果 在OC中的使用方式 在Swift中的使用方式 错误方式,模仿OC的写法 正确方式 实例 在OC使用...

  • 2018-01-24

    今天学习了一个关于Callback 函数的一些用法简单比较一下使用callback和未使用cellback所出现的...

网友评论

      本文标题:OC中callback的理解和使用

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