NSNotificationCenter 通知的使用方法詳解

你要知道的KVC座菠、KVO条霜、Delegate、Notification都在這里

轉(zhuǎn)載請(qǐng)注明出處 http://www.reibang.com/p/f6224f075437

本系列文章主要通過(guò)講解KVC、KVO摇零、Delegate磨确、Notification的使用方法沽甥,來(lái)探討KVO、Delegate乏奥、Notification的區(qū)別以及相關(guān)使用場(chǎng)景摆舟,本系列文章將分一下幾篇文章進(jìn)行講解,讀者可按需查閱邓了。

NSNotificationCenter 通知的使用方法詳解

NSNotificationCenter通知中心是iOS程序內(nèi)部的一種消息廣播的實(shí)現(xiàn)機(jī)制骗炉,可以在不同對(duì)象之間發(fā)送通知進(jìn)而實(shí)現(xiàn)通信照宝,通知中心采用的是一對(duì)多的方式,一個(gè)對(duì)象發(fā)送的通知可以被多個(gè)對(duì)象接收句葵,這一點(diǎn)與我們前面講解的KVO機(jī)制類似厕鹃,KVO觸發(fā)的回調(diào)函數(shù)也可以被對(duì)個(gè)對(duì)象響應(yīng),但代理模式delegate則是一種一對(duì)一的模式乍丈,委托對(duì)象只能有一個(gè)剂碴,對(duì)象也只能和委托對(duì)象通過(guò)代理的方式通信。

首先看一下比較重要的NSNotification類诗赌,這是通知中心的基礎(chǔ)汗茄,通知中心發(fā)送的的通知都會(huì)封裝成該類的對(duì)象進(jìn)而在不同對(duì)象之間傳遞。其比較重要的屬性和方法如下:

//通知的名稱铭若,有時(shí)可能會(huì)使用一個(gè)方法來(lái)處理多個(gè)通知洪碳,可以根據(jù)名稱區(qū)分
@property (readonly, copy) NSNotificationName name;
//通知的對(duì)象递览,常使用nil,如果設(shè)置了值注冊(cè)的通知監(jiān)聽器的object需要與通知的object匹配瞳腌,否則接收不到通知
@property (nullable, readonly, retain) id object;
//字典類型的用戶信息绞铃,用戶可將需要傳遞的數(shù)據(jù)放入該字典中
@property (nullable, readonly, copy) NSDictionary *userInfo;

//下面三個(gè)是NSNotification的構(gòu)造函數(shù),一般不需要手動(dòng)構(gòu)造
- (instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

NSNotification通知類本身很簡(jiǎn)單嫂侍,需要著重理解的就是其三個(gè)屬性儿捧,接下來(lái)看一下NSNotificationCenter通知中心,通知中心采用單例的模式挑宠,整個(gè)系統(tǒng)只有一個(gè)通知中心菲盾,通過(guò)如下代碼獲取:

[NSNotificationCenter defaultCenter]

再看一下通知中心的幾個(gè)核心方法:

/*
注冊(cè)通知監(jiān)聽器,只有這一個(gè)方法
observer為監(jiān)聽器
aSelector為接到收通知后的處理函數(shù)
aName為監(jiān)聽的通知的名稱
object為接收通知的對(duì)象各淀,需要與postNotification的object匹配懒鉴,否則接收不到通知
*/
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;

/*
發(fā)送通知,需要手動(dòng)構(gòu)造一個(gè)NSNotification對(duì)象
*/
- (void)postNotification:(NSNotification *)notification;

/*
發(fā)送通知
aName為注冊(cè)的通知名稱
anObject為接受通知的對(duì)象碎浇,通知不傳參時(shí)可使用該方法
*/
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;

/*
發(fā)送通知
aName為注冊(cè)的通知名稱
anObject為接受通知的對(duì)象
aUserInfo為字典類型的數(shù)據(jù)临谱,可以傳遞相關(guān)數(shù)據(jù)
*/
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

/*
刪除通知的監(jiān)聽器
*/
- (void)removeObserver:(id)observer;

/*
刪除通知的監(jiān)聽器
aName監(jiān)聽的通知的名稱
anObject監(jiān)聽的通知的發(fā)送對(duì)象
*/
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;

/*
以block的方式注冊(cè)通知監(jiān)聽器
*/
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

接下來(lái)舉一個(gè)栗子,和之前delegate的栗子相同奴璃,只不過(guò)這里使用通知來(lái)實(shí)現(xiàn)悉默,依舊是兩個(gè)頁(yè)面,ViewControllerNextViewController苟穆,在ViewController中有一個(gè)按鈕和一個(gè)標(biāo)簽抄课,點(diǎn)擊按鈕跳轉(zhuǎn)到NextViewController視圖中,NextViewController中包含一個(gè)輸入框和一個(gè)按鈕鞭缭,用戶在完成輸入后點(diǎn)擊按鈕退出視圖跳轉(zhuǎn)回ViewController并在ViewController的標(biāo)簽中展示用戶填寫的數(shù)據(jù)剖膳,接下來(lái)看一下代碼:

//ViewController部分代碼

- (void)viewDidLoad
{
    //注冊(cè)通知的監(jiān)聽器,通知名稱為inputTextValueChangedNotification岭辣,處理函數(shù)為inputTextValueChangedNotificationHandler:
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inputTextValueChangedNotificationHandler:) name:@"inputTextValueChangedNotification" object:nil];

}

//按鈕點(diǎn)擊事件處理器
- (void)buttonClicked
{
    //按鈕點(diǎn)擊后創(chuàng)建NextViewController并展示
    NextViewController *nvc = [[NextViewController alloc] init];
    [self presentViewController:nvc animated:YES completion:nil];
}

//通知監(jiān)聽器處理函數(shù)
- (void)inputTextValueChangedNotificationHandler:(NSNotification*)notification
{
    //從userInfo字典中獲取數(shù)據(jù)展示到標(biāo)簽中
    self.label.text = notification.userInfo[@"inputText"];
}

- (void)dealloc
{
    //當(dāng)ViewController銷毀前刪除通知監(jiān)聽器
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"inputTextValueChangedNotification" object:nil];
}


//NextViewController部分代碼
//用戶完成輸入后點(diǎn)擊按鈕的事件處理器
- (void)completeButtonClickedHandler
{
    //發(fā)送通知吱晒,并構(gòu)造一個(gè)userInfo的字典數(shù)據(jù)類型,將用戶輸入文本保存
    [[NSNotificationCenter defaultCenter] postNotificationName:@"inputTextValueChangedNotification" object:nil userInfo:@{@"inputText": self.textField.text}];
    //退出視圖
    [self dismissViewControllerAnimated:YES completion:nil];
}

代碼比較簡(jiǎn)單不再給出相關(guān)運(yùn)行截圖了沦童,不難發(fā)現(xiàn)NSNotificationCenter的使用步驟如下:

  • 1仑濒、在需要監(jiān)聽某通知的地方注冊(cè)通知監(jiān)聽器

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inputTextValueChangedNotificationHandler:) name:@"inputTextValueChangedNotification" object:nil];
    
  • 2、實(shí)現(xiàn)通知監(jiān)聽器的回調(diào)函數(shù)

    - (void)inputTextValueChangedNotificationHandler:(NSNotification*)notification
    {
        self.label.text = notification.userInfo[@"inputText"];
    }
    
  • 3偷遗、在監(jiān)聽器對(duì)象銷毀前刪除通知監(jiān)聽器

    - (void)dealloc
      {
          [[NSNotificationCenter defaultCenter] removeObserver:self name:@"inputTextValueChangedNotification" object:nil];
      }
    
  • 4墩瞳、如有通知需要發(fā)送,使用NSNotificationCenter發(fā)送通知

    [[NSNotificationCenter defaultCenter] postNotificationName:@"inputTextValueChangedNotification" object:nil userInfo:@{@"inputText": self.textField.text}];
    

對(duì)于刪除監(jiān)聽器這一步驟在iOS9以后似乎變得不那么重要氏豌,iOS9開始不再對(duì)已經(jīng)銷毀的監(jiān)聽器發(fā)送通知喉酌,當(dāng)監(jiān)聽器對(duì)象銷毀后發(fā)送通知也不會(huì)造成野指針錯(cuò)誤,這一點(diǎn)比KVO更加安全,KVO在監(jiān)聽器對(duì)象銷毀后仍會(huì)觸發(fā)回調(diào)函數(shù)就可能造成野指針錯(cuò)誤泪电,因此使用通知也就可以不手動(dòng)刪除監(jiān)聽器了般妙,但如果需要適配iOS9之前的系統(tǒng)還是需要養(yǎng)成手動(dòng)刪除監(jiān)聽器的習(xí)慣。

上面的栗子很簡(jiǎn)單相速,但有一點(diǎn)是需要強(qiáng)調(diào)的碟渺,我們?cè)?code>NextViewController中發(fā)送的通知是在main線程中發(fā)送的,因此ViewController中的監(jiān)聽器回調(diào)函數(shù)也會(huì)在main線程中執(zhí)行突诬,因此我們?cè)诒O(jiān)聽器回調(diào)函數(shù)中修改UI不會(huì)產(chǎn)生任何問(wèn)題苫拍,但當(dāng)通知是在其他線程中發(fā)送的,監(jiān)聽器回調(diào)函數(shù)很有可能就是在發(fā)送通知的那個(gè)線程中執(zhí)行旺隙,我們知道UI的更新必須在主線程中執(zhí)行绒极,這個(gè)時(shí)候就需要注意,如果通知監(jiān)聽器回調(diào)函數(shù)有需要更新UI的代碼催束,需要使用GCD放在主線程中執(zhí)行集峦,代碼如下:

//NextViewController發(fā)送通知的代碼修改為如下代碼:
- (void)completeButtonClickedHandler
{
    //使用GCD獲取一個(gè)非主線程的線程用于發(fā)送通知
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@"inputTextValueChangedNotification" object:nil userInfo:@{@"inputText": self.textField.text}];
    });
    
    [self dismissViewControllerAnimated:YES completion:nil];
}

//ViewController通知監(jiān)聽器的回調(diào)函數(shù)修改為如下代碼:
- (void)inputTextValueChangedNotificationHandler:(NSNotification*)notification
{
    //使用GCD獲取主線程并更新UI
    dispatch_async(dispatch_get_main_queue(), ^{
        self.label.text = notification.userInfo[@"inputText"];
    });
    //如果不在主線程更新UI很有可能無(wú)法正確執(zhí)行
    //self.label.text = notification.userInfo[@"inputText"];
}

很多時(shí)候我們使用的是第三方框架發(fā)送的通知伏社,或是系統(tǒng)提供的通知抠刺,我們無(wú)法預(yù)知這些通知是否是在主線程中發(fā)送的,為了安全起見最好在需要更新UI時(shí)使用GCD將更新的邏輯放入主線程執(zhí)行摘昌。

系統(tǒng)提供了很多各式各樣的通知速妖,比如當(dāng)我們要實(shí)現(xiàn)IM即時(shí)通訊類app的聊天頁(yè)面輸入框時(shí)就可以使用系統(tǒng)鍵盤發(fā)出的通知,相關(guān)通知有UIKeyboardWillShowNotificationUIKeyboardWillHideNotification聪黎,顧名思義一個(gè)是鍵盤即將展示罕容,一個(gè)是鍵盤即將退出的通知,接下來(lái)給一個(gè)簡(jiǎn)單的實(shí)現(xiàn):

#import "ViewController.h"

#define ScreenWidth [[UIScreen mainScreen] bounds].size.width
#define ScreenHeight [[UIScreen mainScreen] bounds].size.height

@interface ViewController ()

@property (nonatomic, strong) UIView *containerView;
@property (nonatomic, strong) UITextField *textField;

@end

@implementation ViewController

@synthesize containerView = _containerView;
@synthesize textField = _textField;

- (instancetype)init
{
    if (self = [super init])
    {
        self.view.backgroundColor = [UIColor whiteColor];

        //創(chuàng)建一個(gè)容器View可自定義相關(guān)UI
        self.containerView = [[UIView alloc] initWithFrame:CGRectMake(0, ScreenHeight - 60, ScreenWidth, 60)];
        self.containerView.backgroundColor = [UIColor redColor];
        [self.view addSubview:self.containerView];
        
        //用戶輸入的UITextField
        self.textField = [[UITextField alloc] initWithFrame:CGRectMake(20, 10, ScreenWidth - 40, 40)];
        self.textField.placeholder = @"input...";
        self.textField.backgroundColor = [UIColor greenColor];
        [self.containerView addSubview:self.textField];
        
        [self.view addSubview:self.containerView];
        
        //添加一個(gè)手勢(shì)點(diǎn)擊空白部分后收回鍵盤
        UIGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapView)];
        [self.view setMultipleTouchEnabled:YES];
        [self.view addGestureRecognizer:gesture];

    }
    return self;
}

- (void)viewDidLoad
{
    //注冊(cè)UIKeyboardWillShowNotification通知稿饰,監(jiān)聽鍵盤彈出事件
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    //注冊(cè)UIKeyboardWillHideNotification通知锦秒,監(jiān)聽鍵盤回收事件
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}

//自定義手勢(shì)響應(yīng)處理器
- (void)tapView
{
    //觸發(fā)收回鍵盤事件
    [self.textField resignFirstResponder];
}

//UIKeyboardWillShowNotification通知回調(diào)函數(shù)
- (void)keyboardWillShow:(NSNotification*)notification
{
    //獲取userInfo字典數(shù)據(jù)
    NSDictionary *userInfo = [notification userInfo];
    //根據(jù)UIKeyboardBoundsUserInfoKey鍵獲取鍵盤高度
    float keyboardHeight = [[userInfo objectForKey:@"UIKeyboardBoundsUserInfoKey"] CGRectValue].size.height;
    //獲取鍵盤彈出的動(dòng)畫時(shí)間
    NSTimeInterval animationDuration;
    [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
    //自定義動(dòng)畫修改ContainerView的位置
    [UIView animateWithDuration:animationDuration animations:^{
        self.containerView.frame = CGRectMake(0, ScreenHeight - keyboardHeight - self.containerView.frame.size.height, self.containerView.frame.size.width, self.containerView.frame.size.height);
    }];

}

//UIKeyboardWillHideNotification通知回調(diào)函數(shù)
- (void)keyboardWillHide:(NSNotification*)notification
{
    //獲取動(dòng)畫執(zhí)行執(zhí)行時(shí)間
    NSValue *animationDurationValue = [[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey];
    NSTimeInterval animationDuration;
    [animationDurationValue getValue:&animationDuration];
    //自定義動(dòng)畫修改ContainerView的位置
    [UIView animateWithDuration:animationDuration animations:^{
        self.containerView.frame = CGRectMake(0, ScreenHeight - self.containerView.frame.size.height, self.containerView.frame.size.width, self.containerView.frame.size.height);
    }];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

@end

最終效果如下圖所示(GIF下載可能有點(diǎn)慢):

絲滑鍵盤

備注

由于作者水平有限,難免出現(xiàn)紕漏喉镰,如有問(wèn)題還請(qǐng)不吝賜教旅择。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市侣姆,隨后出現(xiàn)的幾起案子生真,更是在濱河造成了極大的恐慌,老刑警劉巖捺宗,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柱蟀,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蚜厉,警方通過(guò)查閱死者的電腦和手機(jī)长已,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人术瓮,你說(shuō)我怎么就攤上這事胶果。” “怎么了斤斧?”我有些...
    開封第一講書人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵早抠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我撬讽,道長(zhǎng)蕊连,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任游昼,我火速辦了婚禮甘苍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘烘豌。我一直安慰自己载庭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開白布廊佩。 她就那樣靜靜地躺著囚聚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪标锄。 梳的紋絲不亂的頭發(fā)上顽铸,一...
    開封第一講書人閱讀 49,856評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音料皇,去河邊找鬼谓松。 笑死,一個(gè)胖子當(dāng)著我的面吹牛践剂,可吹牛的內(nèi)容都是我干的鬼譬。 我是一名探鬼主播,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼逊脯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼优质!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起男窟,我...
    開封第一講書人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盆赤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后歉眷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牺六,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年汗捡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了淑际。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畏纲。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖春缕,靈堂內(nèi)的尸體忽然破棺而出盗胀,到底是詐尸還是另有隱情,我是刑警寧澤锄贼,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布票灰,位于F島的核電站,受9級(jí)特大地震影響宅荤,放射性物質(zhì)發(fā)生泄漏屑迂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一冯键、第九天 我趴在偏房一處隱蔽的房頂上張望惹盼。 院中可真熱鬧,春花似錦惫确、人聲如沸手报。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)掩蛤。三九已至,卻和暖如春所袁,著一層夾襖步出監(jiān)牢的瞬間盏档,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工燥爷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人懦窘。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓前翎,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親畅涂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子港华,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349

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