本章將介紹事件驅動程序,這個程序能保持運行、等待事件,並做相應的處理。
回調就是將一段可執行代碼和特定的事件綁定起來。
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)對象不擁有目標