你要知道的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)行講解,讀者可按需查閱邓了。
- KVC 使用方法詳解及底層實(shí)現(xiàn)
- KVO 正確使用姿勢(shì)進(jìn)階及底層實(shí)現(xiàn)
- Protocol與Delegate 使用方法詳解
- NSNotificationCenter 通知使用方法詳解
- KVO恨诱、Delegate、Notification 區(qū)別及相關(guān)使用場(chǎng)景
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è)面,ViewController
和NextViewController
苟穆,在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)通知有UIKeyboardWillShowNotification
和UIKeyboardWillHideNotification
聪黎,顧名思義一個(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)不吝賜教旅择。