1.回調(diào)機制
所謂回調(diào)就是講一段可執(zhí)行的代碼與特定的一個事件綁定起來,當(dāng)事件發(fā)生時就會調(diào)用這段代碼。
Objective-C的回調(diào)有四種途徑實現(xiàn)
- 目標(biāo)-動作對(target-action): 事件發(fā)生時,向特定的對象發(fā)送特定的消息。接收消息的對象為目標(biāo),消息的選擇器(selector)是動作。
- 輔助對象(helper objects):事件發(fā)生時,向遵守特定協(xié)議的輔助對象發(fā)送消息。委托對象(delegate)和數(shù)據(jù)源(data source)是常見的輔助對象。
- 通知(notification): 蘋果公司添加了一種稱之為通知中心(notification center)的對象。程序開始前,可以告知消息中心“某個對象正在等待某些特定的通知。當(dāng)通知出現(xiàn)時,向指定的對象發(fā)送特定的消息”。即事件發(fā)生時,相關(guān)對象向通知中心發(fā)布通知,然后由通知中心將通知發(fā)生給等待通知的對象。
- Block對象(Blocks): Block是一段可執(zhí)行代碼,聲明一個Block對象,在事件發(fā)生時,調(diào)用該Block對象。
事件驅(qū)動的程序需要一個等待事件發(fā)生的負責(zé),OS X和iOS系統(tǒng)用NSRunloop的類(運行循環(huán))來等待事件的發(fā)生,示例代碼如下:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
@autoreleasepool{
[[NSRunLoop currentRunLoop] run];
}
return 0;
}

runloop是一種閑時循環(huán),等待事件的發(fā)生,runloop會有一個autorelease pool,runloop更新時[pool drain],向池中的對象發(fā)送release消息。
2.具體實現(xiàn)
(1)目標(biāo)-動作對(target-action)
以NSTimer對象每隔2S,讓一個TSLogger對象設(shè)置時間和打印時間。
// TSLogger.h
#import <Foundation/Foundation.h>
@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 <Foundation/Foundation.h>
#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中設(shè)置了目標(biāo)-動作對的目標(biāo)為TSLogger對象logger,動作為updateLastTime:,時間是每隔2S,重復(fù)進行。而在TSLogger對象中進行了具體的動作方法的實現(xiàn)。
這就是目標(biāo)-動作對的大體用法,即在指定的時刻觸發(fā)事件,情況比較簡單,而且只做了一件事。
(2)輔助對象(helper objects)
輔助對象有委托對象和數(shù)據(jù)源兩種,我們先看委托對象
<1>委托對象
委托(delegate)就是將一件屬于委托者做的事情交給被委托者來處理。受委托完成任務(wù)的對象稱之為委托對象。
創(chuàng)建一個委托協(xié)議;
對于委托者:
- 委托者聲明要委托的屬性,該屬性遵守協(xié)議;
- 委托者在自己實現(xiàn)的方法通過遵守協(xié)議的屬性,調(diào)用協(xié)議內(nèi)的方法;
- 委托者設(shè)置好需要委托對象維護的屬性
對于委托對象:
- 委托對象實現(xiàn)委托協(xié)議所定義的方法。
舉例如下
有個人想要開公交賺錢,但是只有自己一個人忙不過來,需要找個售票的幫他賣票和告知情況。這里我們假定每2S來一個乘客。
- 創(chuàng)建一個Bus協(xié)議,描述需求是需要會賣票和報告賣票信息。對應(yīng)第一步
- 在公交車類里聲明一個賣票者屬性,這個屬性應(yīng)由外界賦值。對應(yīng)A.1
- 公交車上路后(startRun),代碼內(nèi)部的sellTicket由準(zhǔn)守該協(xié)議的onePerson執(zhí)行。對應(yīng)A.2
//公交車的使用協(xié)議
@protocol BusProtocol <NSObject>
-(void)sellTicket;
-(void)reportSituation;
@end
//公交車類的聲明
@interface Bus : NSObject
@property(nonatomic,strong)id<busProtocol>onePerson;
-(void)startRun;
@end
//公交車類的實現(xiàn)
@implementation Bus
-(void)startRun
{
if(self.onePerson)
{
[self.onePerson sellTicket];
[self.onePerson reportSituation];
}
}
@end
我們需要的賣票對象如下,它遵守協(xié)議實現(xiàn)了賣票的方法。對應(yīng)B.1
//委托對象seller類的聲明
@interface Seller : NSObject <busProtocol>
@end
//委托對象seller類的實現(xiàn)
@implementation Seller
-(void)sellTicket
{
NSLog(@"開始售票!");
}
-(void)reportSituation
{
NSLog(@"完成售票!");
}
@end
最后在main中,設(shè)置委托者里需要委托對象維護的屬性,即onePerson是委托對象seller。對應(yīng)A.3
#import <Foundation/Foundation.h>
#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;
}
結(jié)果每來一個客人(每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] 完成售票!
委托可以向一個對象發(fā)送多個回調(diào)。
(3)通知(notification)
一個對象發(fā)生變化時,多個對象想要獲得這個變化的通知,比如我們修改了系統(tǒng)的時區(qū),很多對象會想要知曉這一變化。系統(tǒng)的時區(qū)發(fā)生變化時,會向通知中心發(fā)送NSSystemTimeZoneDidChangeNotification通知,然后通知中心會將該通知轉(zhuǎn)發(fā)給相應(yīng)的觀察者們。
實例代碼如下,將airlineHostess(空姐)和airlineBoy作為觀察者,修改時區(qū)后,airlineHostess和airlineBoy廣播換時區(qū)了,一個中文,一個英文。
// airlineHostess.h
#import <Foundation/Foundation.h>
@interface airlineHostess : NSObject
-(void) report;
@end
// airlineHostess.m
#import "airlineHostess.h"
@implementation airlineHostess
-(void) report
{
NSLog(@"親愛的乘客們,我們換時區(qū)啦!");
}
@end
main.m中注冊觀察者
#import <Foundation/Foundation.h>
#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;
}
運行后,打開系統(tǒng)的時間與偏好設(shè)置修改時區(qū),結(jié)果如下。
2018-04-08 19:18:29.488975+0800 test[1786:359257] 親愛的乘客們,我們換時區(qū)啦!
2018-04-08 19:18:29.516014+0800 test[1786:359257] Dear passengers, we are in a new time zone!
(4)Block對象(Blocks)
Block知識點較多,將放入下一篇文章講解