ios消息機(jī)制(NSNotification 和 NSNotificationCenter)


問題的背景

IOS中委托模式和消息機(jī)制基本上開發(fā)中用到的比較多境钟,一般最開始頁面?zhèn)髦低ㄟ^委托實(shí)現(xiàn)的比較多,類之間的傳值用到的比較多,不過委托相對來說只能是一對一饲齐,比如說頁面A跳轉(zhuǎn)到頁面B诫隅,頁面的B的值改變要映射到頁面A腐魂,頁面C的值改變也需要映射到頁面A,那么就需要需要兩個(gè)委托解決問題逐纬。NSNotificaiton則是一對多注冊一個(gè)通知蛔屹,之后回調(diào)很容易解決以上的問題。

概念

iOS消息通知機(jī)制算是同步的豁生,觀察者只要向消息中心注冊兔毒, 即可接受其他對象發(fā)送來的消息,消息發(fā)送者和消息接受者兩者可以互相一無所知甸箱,完全解耦育叁。這種消息通知機(jī)制可以應(yīng)用于任意時(shí)間和任何對象,觀察者可以有多個(gè)芍殖,所以消息具有廣播的性質(zhì)豪嗽,只是需要注意的是,觀察者向消息中心注冊以后豌骏,在不需要接受消息時(shí)需要向消息中心注銷龟梦,屬于典型的觀察者模式。

消息通知中重要的兩個(gè)類:

(1)NSNotificationCenter: 實(shí)現(xiàn)NSNotificationCenter的原理是一個(gè)觀察者模式窃躲,獲得NSNotificationCenter的方法只有一種变秦,那就是[NSNotificationCenter defaultCenter] ,通過調(diào)用靜態(tài)方法defaultCenter就可以獲取這個(gè)通知中心的對象了框舔。NSNotificationCenter是一個(gè)單例模式蹦玫,而這個(gè)通知中心的對象會(huì)一直存在于一個(gè)應(yīng)用的生命周期赎婚。

(2) NSNotification: 這是消息攜帶的載體,通過它樱溉,可以把消息內(nèi)容傳遞給觀察者挣输。

使用

1.通過NSNotificationCenter注冊通知NSNotification,viewDidLoad中代碼如下:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationFirst:) name:@"First" object:nil];
 
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationSecond:) name:@"Second" object:nil];

第一個(gè)參數(shù)是觀察者為本身福贞,第二個(gè)參數(shù)表示消息回調(diào)的方法撩嚼,第三個(gè)消息通知的名字,第四個(gè)為nil表示表示接受所有發(fā)送者的消息~

回調(diào)方法:

-(void)notificationFirst:(NSNotification *)notification{
    NSString  *name=[notification name];
    NSString  *object=[notification object];
    NSLog(@"名稱:%@----對象:%@",name,object);
}
 
-(void)notificationSecond:(NSNotification *)notification{
    NSString  *name=[notification name];
    NSString  *object=[notification object];
    NSDictionary  *dict=[notification userInfo];
    NSLog(@"名稱:%@----對象:%@",name,object);
    NSLog(@"獲取的值:%@",[dict objectForKey:@"key"]);
}

2.消息傳遞給觀察者

[[NSNotificationCenter defaultCenter] postNotificationName:@"First" object:@"博客園-Fly_Elephant"];
 
NSDictionary  *dict=[[NSDictionary alloc]initWithObjects:@[@"keso"] forKeys:@[@"key"]];
 
[[NSNotificationCenter defaultCenter] postNotificationName:@"Second" object:@"http://www.cnblogs.com/xiaofeixiang" userInfo:dict];

3.銷毀觀察者

-(void)dealloc{
    NSLog(@"觀察者銷毀了");
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

也可以通過name單個(gè)刪除:

[[NSNotificationCenter defaultCenter] removeObserver:self name:@"First" object:nil];

4.運(yùn)行結(jié)果

2015-04-26 15:08:25.900 CustoAlterView[2169:148380] 觀察者銷毀了
2015-04-26 15:08:29.222 CustoAlterView[2169:148380] 名稱:First----對象:博客園-Fly_Elephant
2015-04-26 15:08:29.222 CustoAlterView[2169:148380] 名稱:Second----對象:http://www.cnblogs.com/xiaofeixiang
2015-04-26 15:08:29.223 CustoAlterView[2169:148380] 獲取的值:keso

深入分析觀察者


如果想讓對象監(jiān)聽某個(gè)通知挖帘,則需要在通知中心中將這個(gè)對象注冊為通知的觀察者完丽。早先,NSNotificationCenter提供了以下方法來添加觀察者:

- (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender

這個(gè)方法帶有4個(gè)參數(shù)拇舀,分別指定了通知的觀察者逻族、處理通知的回調(diào)、通知名及通知的發(fā)送對象骄崩。這里需要注意幾個(gè)問題

  1. notificationObserver不能為nil聘鳞。
  2. notificationSelector回調(diào)方法有且只有一個(gè)參數(shù)(NSNotification對象)。
  3. 如果notificationName為nil要拂,則會(huì)接收所有的通知(如果notificationSender不為空抠璃,則接收所有來自于notificationSender的所有通知)。如代碼清單1所示脱惰。
  4. 如果notificationSender為nil搏嗡,則會(huì)接收所有notificationName定義的通知;否則拉一,接收由notificationSender發(fā)送的通知彻况。
  5. 監(jiān)聽同一條通知的多個(gè)觀察者,在通知到達(dá)時(shí)舅踪,它們執(zhí)行回調(diào)的順序是不確定的纽甘,所以我們不能去假設(shè)操作的執(zhí)行會(huì)按照添加觀察者的順序來執(zhí)行。

對于以上幾點(diǎn)抽碌,我們來重點(diǎn)關(guān)注一下第3條悍赢。以下代碼演示了當(dāng)我們的notificationName設(shè)置為nil時(shí),通知的監(jiān)聽情況货徙。

測試代碼如下

添加一個(gè)Observer左权,其中notificationName為nil

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:nil object:nil];

    [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil];
}

- (void)handleNotification:(NSNotification *)notification
{
    NSLog(@"notification = %@", notification.name);
}

@end

運(yùn)行后的輸出結(jié)果如下:

notification = TestNotification
notification = UIWindowDidBecomeVisibleNotification
notification = UIWindowDidBecomeKeyNotification
notification = UIApplicationDidFinishLaunchingNotification
notification = _UIWindowContentWillRotateNotification
notification = _UIApplicationWillAddDeactivationReasonNotification
notification = _UIApplicationDidRemoveDeactivationReasonNotification
notification = UIDeviceOrientationDidChangeNotification
notification = _UIApplicationDidRemoveDeactivationReasonNotification
notification = UIApplicationDidBecomeActiveNotification

可以看出,我們的對象基本上監(jiān)聽了測試程序啟動(dòng)后的所示消息痴颊。當(dāng)然主之,我們很少會(huì)去這么做务冕。

而對于第4條贝攒,使用得比較多的場景是監(jiān)聽UITextField的修改事件,通常我們在一個(gè)ViewController中甩栈,只希望去監(jiān)聽當(dāng)前視圖中的UITextField修改事件,而不希望監(jiān)聽所有UITextField的修改事件糕再,這時(shí)我們就可以將當(dāng)前頁面的UITextField對象指定為notificationSender量没。

NSNotification Block


在iOS 4.0之后,NSNotificationCenter為了跟上時(shí)代突想,又提供了一個(gè)以block方式實(shí)現(xiàn)的添加觀察者的方法殴蹄,如下所示:

- (id<NSObject>)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block

大家第一次看到這個(gè)方法時(shí)是否會(huì)有這樣的疑問:觀察者呢?參數(shù)中并沒有指定具體的觀察者猾担,那誰是觀察者呢袭灯?實(shí)際上,與前一個(gè)方法不同的是绑嘹,前者使用一個(gè)現(xiàn)存的對象作為觀察者稽荧,而這個(gè)方法會(huì)創(chuàng)建一個(gè)匿名的對象作為觀察者(即方法返回的id<NSObject>對象),這個(gè)匿名對象會(huì)在指定的隊(duì)列(queue)上去執(zhí)行我們的block圾叼。

這個(gè)方法的優(yōu)點(diǎn)在于添加觀察者的操作與回調(diào)處理操作的代碼更加緊湊蛤克,不需要拼命滾動(dòng)鼠標(biāo)就能直接找到處理代碼捺癞,簡單直觀夷蚊。這個(gè)方法也有幾個(gè)地方需要注意:

  1. name和obj為nil時(shí)的情形與前面一個(gè)方法是相同的。
  2. 如果queue為nil髓介,則消息是默認(rèn)在post線程中同步處理惕鼓,即通知的post與轉(zhuǎn)發(fā)是在同一線程中;但如果我們指定了操作隊(duì)列唐础,情況就變得有點(diǎn)意思了箱歧,我們一會(huì)再講。
  3. block塊會(huì)被通知中心拷貝一份(執(zhí)行copy操作)一膨,以在堆中維護(hù)一個(gè)block對象呀邢,直到觀察者被從通知中心中移除。所以豹绪,應(yīng)該特別注意在block中使用外部對象价淌,避免出現(xiàn)對象的循環(huán)引用,這個(gè)我們在下面將舉例說明瞒津。
  4. 如果一個(gè)給定的通知觸發(fā)了多個(gè)觀察者的block操作蝉衣,則這些操作會(huì)在各自的Operation Queue中被并發(fā)執(zhí)行。所以我們不能去假設(shè)操作的執(zhí)行會(huì)按照添加觀察者的順序來執(zhí)行巷蚪。
  5. 該方法會(huì)返回一個(gè)表示觀察者的對象病毡,記得在不用時(shí)釋放這個(gè)對象。

下面我們重點(diǎn)說明一下第2點(diǎn)和第3點(diǎn)屁柏。

關(guān)于第2點(diǎn)啦膜,當(dāng)我們指定一個(gè)Operation Queue時(shí)有送,不管通知是在哪個(gè)線程中post的,都會(huì)在Operation Queue所屬的線程中進(jìn)行轉(zhuǎn)發(fā):

代碼如下:
在指定隊(duì)列中接收通知

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserverForName:TEST_NOTIFICATION object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {

        NSLog(@"receive thread = %@", [NSThread currentThread]);
    }];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSLog(@"post thread = %@", [NSThread currentThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil];
    });
}

@end

在這里功戚,我們在主線程里添加了一個(gè)觀察者娶眷,并指定在主線程隊(duì)列中去接收處理這個(gè)通知。然后我們在一個(gè)全局隊(duì)列中post了一個(gè)通知啸臀。我們來看下輸出結(jié)果:

post thread = <NSThread: 0x7ffe0351f5f0>{number = 2, name = (null)}
receive thread = <NSThread: 0x7ffe03508b30>{number = 1, name = main}

可以看到届宠,消息的post與接收處理并不是在同一個(gè)線程中。如上面所提到的乘粒,如果queue為nil豌注,則消息是默認(rèn)在post線程中同步處理,大家可以試一下灯萍。

對于第3點(diǎn)轧铁,由于使用的是block,所以需要注意的就是避免引起循環(huán)引用的問題旦棉,如下:

block引發(fā)的循環(huán)引用問題

@interface Observer : NSObject

@property (nonatomic, assign) NSInteger i;
@property (nonatomic, weak) id<NSObject> observer;

@end

@implementation Observer

- (instancetype)init
{
    self = [super init];

    if (self)
    {
        NSLog(@"Init Observer");

        // 添加觀察者
        _observer =  [[NSNotificationCenter defaultCenter] addObserverForName:TEST_NOTIFICATION object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {

            NSLog(@"handle notification");

            // 使用self
            self.i = 10;
        }];
    }

    return self;
}

@end

·#pragma mark - ViewController

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self createObserver];

    // 發(fā)送消息
    [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil];
}

- (void)createObserver {

    Observer *observer = [[Observer alloc] init];
}

@end

運(yùn)行后的輸出如下:

Init Observer
handle notification

我們可以看到createObserver中創(chuàng)建的observer并沒有被釋放齿风。所以,使用 – addObserverForName:object:queue:usingBlock:一定要注意這個(gè)問題绑洛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末救斑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子真屯,更是在濱河造成了極大的恐慌脸候,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绑蔫,死亡現(xiàn)場離奇詭異运沦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)配深,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門携添,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人篓叶,你說我怎么就攤上這事烈掠。” “怎么了澜共?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵向叉,是天一觀的道長。 經(jīng)常有香客問我嗦董,道長母谎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任京革,我火速辦了婚禮奇唤,結(jié)果婚禮上幸斥,老公的妹妹穿的比我還像新娘。我一直安慰自己咬扇,他們只是感情好甲葬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著懈贺,像睡著了一般经窖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梭灿,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天画侣,我揣著相機(jī)與錄音,去河邊找鬼堡妒。 笑死配乱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的皮迟。 我是一名探鬼主播搬泥,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼伏尼!你這毒婦竟也來了忿檩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤烦粒,失蹤者是張志新(化名)和其女友劉穎休溶,沒想到半個(gè)月后代赁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扰她,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年芭碍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了徒役。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窖壕,死狀恐怖忧勿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瞻讽,我是刑警寧澤鸳吸,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站速勇,受9級特大地震影響晌砾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烦磁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一养匈、第九天 我趴在偏房一處隱蔽的房頂上張望哼勇。 院中可真熱鬧,春花似錦呕乎、人聲如沸积担。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帝璧。三九已至,卻和暖如春湿刽,著一層夾襖步出監(jiān)牢的瞬間聋溜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工叭爱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撮躁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓买雾,卻偏偏與公主長得像把曼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子漓穿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載自南峰子的技術(shù)博客 一個(gè)NSNotificationCenter對象(通知中心)提供了在程序中廣播消息的機(jī)制嗤军,...
    我消失1314閱讀 887評論 0 2
  • NSNotificationCenter對象(通知中心)提供了在程序中廣播消息的機(jī)制,它實(shí)質(zhì)上就是一個(gè)通知分發(fā)表晃危。...
    9de75b652cd9閱讀 751評論 0 1
  • 一個(gè)NSNotificationCenter對象(通知中心)提供了在程序中廣播消息測機(jī)制,它實(shí)質(zhì)上就是一個(gè)通知分發(fā)...
    DomAndMona閱讀 808評論 0 2
  • 1.屬性readwrite叙赚,readonly,assign僚饭,retain震叮,copy,nonatomic 各是什么作...
    曾令偉閱讀 1,052評論 0 10
  • 早安鳍鸵,童詩 溫暖的人 玉米 我多么希望苇瓣,與一個(gè)溫暖的人 相互依偎著站在...
    一粒玉米閱讀 145評論 0 1