美文网首页
27章:回调

27章:回调

作者: 帽子和五朵玫瑰 | 来源:发表于2018-06-01 09:58 被阅读0次

    回调

    本章之前的代码都是“主动的”,这些代码会向Foundation对象(NSArray)发送消息,并告诉这些对象应该做什么。本章之前所列举的程序会在运行后立刻退出。

    本章我们会一起来创建一个不止是,开始运行,执行,退出的程序。反之,它是一个由事件驱动的程序,这个程序能保持运行,等待事件,并作出相应的处理。除非你关闭程序,否则他不会自动退出,它会一直在后台等待事件的发生。

    比如,点击鼠标,触摸事件等。

    回调(callback)就是将一段可以执行的代码和一个特定的事情绑定起来,当特定的事件发生时,就会执行这段代码。

    目标-动作队(target-action):在程序开始等待前,要求(当指定的事件发生时,向指定的对象发送特定的消息。)这里接受消息的对象是(target),消息的选择器(selector)是动作(action)。

    辅助对象(helper objects):在程序开始等待前,要求“当事件发生时,向遵守相应协议的辅助对象发送消息”,委托对象和数据源都是常见的辅助对象。

    通知(notification)苹果公司提供了一种称为通知中心(notification)的对象。在程序开始等待前,可以告知通知中心“某个对象正在等待某些特定的通知,当其中某个通知出现时,向指定的对象发送特定的消息。”当事件发生时,相关的对象会向通知中心发布通知,然后再由通知中心将通知转发给正在等待该通知的对象。

    Block对象,block是一段可执行的代码,在程序开始等待前,声明一个Block对象,当事件发生时,执行这段Block对象。

    运行循环(runloop)

    事件驱动的程序需要有一个对象,专门负责等待事件的发生。OSX系统和iOS吸引有一个名为NSRunLoop的类。NSRunLoop实例会持续等待着,当特定的事件发生时,就会向相应的对象发送小徐,NSRunLoop实例会在特定的事件发生时出发回调。

    创建一个循环:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            [[NSRunLoop currentRunLoop] run];
        }
        return 0;
    }
    
    

    这时run后,程序不会结束,运行循环正在等待事件的发生。

    目标-动作对

    计时器使用的是目标-动作对机制。创建计时器时,要设定延迟,目标和动作。在指定延迟时间后,计时器会向设定的目标发送指定的消息

    下面要创建一个拥有NSRunLoop对象和NSTimer对象的程序。每隔两秒,NSTimer对象会向其目标发送指定的动作消息。此外,还要创建一个BNRLogger类,这个类的实例将被设置为NSTimer对象的目标,如27.1所示。

    @implementation BNRLogger
    -(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];
    }
    -(void)updateLastTime:(NSTimer *)t{
        NSDate *now = [NSDate date];
        [self setLastTime:now];
        NSLog(@"JUST set Time to %@",self.lastTimeString);
    }
    
    @end
    
    
    
    @interface BNRLogger : NSObject
    @property (nonatomic) NSDate *lastTime;
    -(NSString *) lastTimeString;
    -(void) updateLastTime:(NSTimer *)t;
    @end
    
    
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            BNRLogger *logger = [[BNRLogger alloc] init];
            __unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0
                                                                       target:logger
                                                                     selector:@selector(updateLastTime:)
                                                                     userInfo:nil
                                                                      repeats:YES];
            [[NSRunLoop currentRunLoop] run];
        }
        return 0;
    }
    
    

    27.3辅助对象

    比如 通过NSURLConnection的实例方法从web服务器获取数据。数据的传输时需要同步的,也就是说,所有的数据必须一次传输成功。会有以下两个问题。

    1.获取数据是会阻塞主线程。如果在正式的应用中使用该方法,呢么在获取数据时,用户界面会失去响应。

    2.某些情况下无法实现回调。例如,当web服务器要求提供用户名和密码是,程序无法通过回调机制来提供相应的信息。

    通常会以异步的模式来使用NSURLConnection。在异步模式下,NSURLConnection不会一次发送全部数据,它会发送块状的数据,并多次发送,。也就是说,需要有传输相关的时间,且程序要准备好响应这个事件,相关的事件有:得到数据,web服务器要求提供认证信息或获取数据失败等。

    为了实现这种更复杂的传输,我们要使用一个异步的NSURLConnection的辅助对象。更确切的说,BNRLooger对象会成为NSURLConnection的委托对象。

    当特定的事件发生时,该对象会向辅助对象发送相应的消息,苹果味NSURLConnection提供了一套协议,协议是一些列方法声明,辅助对象可以更具协议实现相应的方法。

    
    #import "BNRLogger.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            BNRLogger *logger = [[BNRLogger alloc] init];
            NSURL *url = [NSURL URLWithString:@"http://ww.gutenberg.org/cache/epub/205/pg205.txt"];
            NSURLRequest *request = [NSURLRequest requestWithURL:url];
            __unused NSURLConnection *fetchConn = [[NSURLConnection alloc] initWithRequest:request
                                                                                  delegate:logger
                                                                          startImmediately:YES];
            __unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0
                                                                       target:logger
                                                                     selector:@selector(updateLastTime:)
                                                                     userInfo:nil
                                                                      repeats:YES];
            [[NSRunLoop currentRunLoop] run];
        }
        return 0;
    }
    
    
    
    #import <Foundation/Foundation.h>
    @interface BNRLogger : NSObject <NSURLConnectionDelegate,NSURLConnectionDataDelegate>
    {
        NSMutableArray *_incomingData;
    }
    @property (nonatomic) NSDate *lastTime;
    -(NSString *) lastTimeString;
    -(void) updateLastTime:(NSTimer *)t;
    @end
    
    
    
    //
    //  BNRLogger.m
    //  CallBack
    //
    //  Created by 啦啦哥 on 2018/5/31.
    //  Copyright © 2018年 啦啦哥. All rights reserved.
    //
    
    #import "BNRLogger.h"
    
    @implementation BNRLogger
    -(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];
    }
    -(void)updateLastTime:(NSTimer *)t{
        NSDate *now = [NSDate date];
        [self setLastTime:now];
        NSLog(@"JUST set Time to %@",self.lastTimeString);
    }
    -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
        NSLog(@"received %lu Bytes",[data length]);
        if(!_incomingData){
            _incomingData = [[NSMutableArray alloc] init];
        }
        [_incomingData addObject:data];
    }
    -(void)connectionDidFinishLoading:(NSURLConnection *)connection{
        NSLog(@"Got it all");
        NSString *string = [[NSString alloc] initWithData:_incomingData encoding:NSUTF8StringEncoding];
        _incomingData = nil;
        NSLog(@"String has %lu characters",[string length]);
    }
    -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
        NSLog(@"connection failed :%@",[error localizedDescription]);
        _incomingData = nil;
    }
    @end
    
    

    目前的回调规则如下,当要向一个对象发送一个回调时,用目标-动作对(target-action)。当要向一个对象发送多个回调时,可以使用符合相应协议的辅助对象。

    27.4通知

    当用户修改Mac系统的时区设置时,程序中的很多对象可能需要知道系统发生的这一变化,这些对象都可以通过通知中心,将自己注册成为观察者。

    当系统的时区设置发送变化时,会向通知中心发布NSSystemTimeZoneDidChangeNotification通知,然后通知中心会将该通知转发给相应的观察者。

    在main.h中,将BNRLogger实例注册为观察者,使之在系统的时区设置发生变化时能够受收到相应的通知,代码如下:

    #import <Foundation/Foundation.h>
    #import "BNRLogger.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            BNRLogger *logger = [[BNRLogger alloc] init];
            [[NSNotificationCenter defaultCenter]addObserver:logger
                                                    selector:@selector(zoneChange:)
                                                        name:NSSystemTimeZoneDidChangeNotification
                                                      object:nil];
            
            NSURL *url = [NSURL URLWithString:@"http://ww.gutenberg.org/cache/epub/205/pg205.txt"];
            NSURLRequest *request = [NSURLRequest requestWithURL:url];
            __unused NSURLConnection *fetchConn = [[NSURLConnection alloc] initWithRequest:request
                                                                                  delegate:logger
                                                                          startImmediately:YES];
            __unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0
                                                                       target:logger
                                                                     selector:@selector(updateLastTime:)
                                                                     userInfo:nil
                                                                      repeats:YES];
            [[NSRunLoop currentRunLoop] run];
        }
        return 0;
    }
    
    #import "BNRLogger.h"
    
    @implementation BNRLogger
    //···········
    //··········
    -(void)zoneChange:(NSNotification *)note{
        NSLog(@"the system time zone has changed!");
    }
    @end
    

    向通知中心注册观察者时,你可以知道某个特定的通知名(例如:NSWindowDidResizeNotification)以及发布通知的来源(比如“我只想接受到这个窗口调整的大小的通知”)。这两个参数你都可以设置为nil。但是,如果你将这两个参数都设置为nil,就会接收到程序中所有对象发布的每条通知,对一个桌面应用来说,通知的数量非常多

    27.5如何选择

    对于只做一件事情的对象(例如NSTimer),使用目标-动作对。

    对于功能更复杂的对象(例如NSURLConnection),使用辅助对象,最常见的辅助对象类型时委托对象

    对于要触发多个(其他对象中的)回调的对象(例如NSTimeZone),使用通知

    27.6回调与对象所有权

    无论是哪种类型的回调,如果代码编写不正确,那么都有陷入强引用循环的风险。常发生这种情况:创建的对象拥有一个指向回调对象的指针。而这个回调对象的指针指向你创建的对象,他们彼此之间具有强引用关系,最后陷入一个强引用循环,这两个对象都无法释放。

    所以在编写代码时,应该遵循以下规则。

    通知中心不拥有观察者。如果将某个对象注册为观察者,那么通常应该在释放该对象时将其移出通知中心。

    -(void)dealloc
    {
        [[NSNotificationCenter defaultCenter] removeObsever:self];
    }
    

    对象不拥有委托对象或数据源对象。如果某个新创建的对象是另一个对象的委托对象或数据源对象。那么该对象应该在其dealloc方法中取消相应的关联。

    -(void)dealloc
    {
        [windowThatBossesMeAround setDelegate:nil];
        [tableViewThatBegsForData setDataSource:nil];
    }
    

    对象不拥有目标。如果某个新创建的对象是另一个对象的目标,那么该对象应该在其dealloc方法中将相应目标指针覆为nil

    -(void)dealloc
    {
        [buttonThatKeepsSendingMeMessages setTarget : nil];
    }
    

    相关文章

      网友评论

          本文标题:27章:回调

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