《Objective-C編程 第二版》筆記15:回調

      本章將介紹事件驅動程序,這個程序能保持運行、等待事件,並做相應的處理。

       回調就是將一段可執行代碼和特定的事件綁定起來。

       OC中實現回調的4種途徑。

       (1) 目標-動作對:在程序開始等待前,要求當事件發生時,向指定的對象發送某個特定消息。這裏接收消息的對象是目標,消息的選擇器是動作。

       (2) 輔助對象:在程序開始等待前,要求當事件發生時,向遵守相應協議的輔助對象發送消息。 委託對象和數據源是常見的輔助對象。

       (3) 通知:蘋果公司提供了通知中心對象。在程序開始等待前,可以告訴通知中心某個對象正在等待某些特定的通知,當其中的某個通知出現時,向指定的對象發送特定的消息。

       (4) Block對象:Block是一段可執行的代碼。在程序開始等待前,聲明一個Block對象,當事件發生時,執行這段Block對象。

 1、運行循環

創建一個CallBacks項目。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //NSRunLoop是運行循環的類,NSRunLoop 實例會持續等待,直到特定事件發生時,執行回調
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}

Product->Stop結束程序。

2、目標-動作對

      計時器使用的是目標-動作對。

首先創建一個新文件,BNRLogger

.h文件
#import <Foundation/Foundation.h>

@interface BNRLogger : NSObject
//
@property (nonatomic) NSDate *lastTime;

- (NSString *) lastTimeString;
//動作方法總是有一個實參,它是用於傳入發送動作消息的那個對象
- (void) updateLastTime:(NSTimer *)t;

@end

.m文件

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

@end

這樣在main.m中就可以創建目標動作對了。

#import <Foundation/Foundation.h>
#import "BNRLogger.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BNRLogger *logger = [[BNRLogger alloc] init];
        //設置每隔2秒,向logger實例發送updateLastTime:消息,並重復發送
        __unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger selector:@selector(updateLastTime:) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}

3、輔助對象

通過異步NSURLConnection實例從網上抓取數據。讓BNRLogger實例稱爲NSURLConnection的輔助對象,或者稱爲委託對象。

當特定的事件發生時,該對象會向輔助對象發送相應的消息。

#import <Foundation/Foundation.h>
#import "BNRLogger.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BNRLogger *logger = [[BNRLogger alloc] init];
        //先創建URL
        NSURL *url = [NSURL URLWithString:@"http://www.baidu.com/a.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;
}

BNRLogger.h中,聲明BNRLogger會實現NSURLConnectionDelegate以及NSURLConnectionDataDelegate的協議方法:


#import <Foundation/Foundation.h>

@interface BNRLogger : NSObject
<NSURLConnectionDelegate,NSURLConnectionDataDelegate>
@property (nonatomic) NSDate *lastTime;

- (NSString *) lastTimeString;
- (void) updateLastTime:(NSTimer *)t;

@end

 異步傳輸需要使用一個NSMutableData實例。將傳過來的一塊塊數據添加到NSMutableData對象中

BNRLogger.h

@interface BNRLogger : NSObject
<NSURLConnectionDelegate,NSURLConnectionDataDelegate>
//添加NSMutableData實例
{
    NSMutableData *_incomingData;
}
@property (nonatomic) NSDate *lastTime;

- (NSString *) lastTimeString;
- (void) updateLastTime:(NSTimer *)t;

@end

BNRLogger.m

//收到一定字節數的數據後會被調用
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    NSLog(@"received %lu bytes",[data length]);
    //對象不存在就創建它
    if(!_incomingData){
        _incomingData = [[NSMutableData alloc] init];
        
    }
    [_incomingData appendData: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;
}

4、通知

類似於觀察者模式

在main.m中將BNRLogger實例註冊爲觀察者,使之在系統時區設置發生變化時能夠收到相應的通知。


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://www.baidu.com/a.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;
}

BNRLogger.m中實現相應方法,該方法會在系統發佈NSSystemTimeZoneDidChangeNotigication通知時被調用


//時區變化時被調用的方法
- (void) zoneChange:(NSNotification *)note
{
    NSLog(@"The system time zone has changed!");
}

5、如何選擇

(1)對於只做一件事情的對象,使用目標-動作對

(2)對於功能更復雜的對象,使用輔助對象。

(3)對於要觸發多個回調的對象,使用通知。

6、回調與對象所有權

爲了避免強引用循環,應該遵循以下規則:

(1)通知中心不擁有觀察者

(2)對象不擁有委託對象或數據源對象

(3)對象不擁有目標

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章