低耦合性是良好程序的特性扶欣。低耦合性程序可讀性和可維護(hù)性比較好鹅巍。Cocoa中的委托、通知功能可以使低耦合性更易實(shí)現(xiàn)料祠,下面結(jié)合demo說明如何使用委托骆捧、通知進(jìn)行傳值,及委托與通知的區(qū)別髓绽。
1. 委托傳值
委托傳值在反向傳值中使用敛苇。使用委托可以讓委托和委托對(duì)象之間的關(guān)系變得清晰,特別是在委托的方法必須實(shí)現(xiàn)時(shí)顺呕。
委托傳值步驟如下:
1.1 在ChildViewController.h
聲明協(xié)議枫攀,協(xié)議內(nèi)方法默認(rèn)必須實(shí)現(xiàn)。如果想選擇實(shí)現(xiàn)株茶,在方法前用@optional
標(biāo)志出來来涨。
#import <UIKit/UIKit.h>
@protocol ChildVCDelegate <NSObject>
- (void)didReceiveText:(NSString *)string;
@optional
- (void)receiveTextFailedWithError:(NSError *)error;
@end
1.2 在ChildViewController.h
接口部分創(chuàng)建一個(gè)ChildVCDelegate
類型的實(shí)例變量。此時(shí)的特性應(yīng)該使用weak
启盛,否則會(huì)造成循環(huán)引用蹦掐。
#import <UIKit/UIKit.h>
@protocol ChildVCDelegate <NSObject>
- (void)didReceiveText:(NSString *)string;
@optional
- (void)receiveTextFailedWithError:(NSError *)error;
@end
@interface ChildViewController : UIViewController
@property (weak, nonatomic) id<ChildVCDelegate> delegate;
@end
1.3 在RootViewController.m
中,使你的類遵守ChildViewController.h
里聲明的ChildVCDelegate
協(xié)議僵闯。
#import "ViewController.h"
#import "ChildViewController.h"
@interface ViewController () <ChildVCDelegate>
@end
1.4 在RootViewController.m
實(shí)現(xiàn)協(xié)議方法卧抗,將ChildViewController
的代理委托給當(dāng)前控制器。
@implementation ViewController
// 1
ChildViewController *childVC = [[ChildViewController alloc] init];
childVC.delegate = self;
- (void)didReceiveText:(NSString *)string
{
}
注釋1后的代碼需要添加到跳轉(zhuǎn)到
ChildViewController
的方法內(nèi)鳖粟。如果使用純代碼編程社裆,添加到presentViewController: animated: completion:
或showViewController: animated:
方法前;如果使用storyboard的segue跳轉(zhuǎn)向图,添加到prepareForSegue: sender:
方法內(nèi)浦马,此時(shí)初始化視圖控制器應(yīng)該使用SecViewController *secVC =segue.destinationViewController;
时呀。
1.5 在ChildViewController.m
實(shí)現(xiàn)部分,調(diào)用代理方法晶默。為防止運(yùn)行時(shí)出現(xiàn)問題谨娜,調(diào)用方法前要先判斷代理是否實(shí)現(xiàn)了調(diào)用的方法。
// 在某方法內(nèi)
if ([self.delegate respondsToSelector:@selector(didReceiveText:)])
{
[self.delegate didReceiveText:@"pro648"];
}
2. 通知傳值
NSNotificationCenter
對(duì)象(簡(jiǎn)稱通知中心)提供了廣播信息的機(jī)制磺陡,NSNotificationCenter
對(duì)象實(shí)質(zhì)上是一個(gè)通知分發(fā)表趴梢。對(duì)象使用addObserver: selector: name: object:
或addObserverForName: object: queue: usingBlock:
方法向通知中心注冊(cè)以接收通知,每次調(diào)用上面的方法都會(huì)指定一組通知币他。因此坞靶,對(duì)象可以通過多次調(diào)用這些方法注冊(cè)為不同通知的觀察者。
每一個(gè)運(yùn)行的Cocoa程序都有一個(gè)默認(rèn)通知中心蝴悉,一般不需要自己創(chuàng)建彰阴。NSNotificationCenter
對(duì)象只能在單個(gè)進(jìn)程中傳遞通知。如果需要向其他進(jìn)程發(fā)送通知拍冠,或從其他進(jìn)程接收通知尿这,請(qǐng)使用NSDistributedNotificationCenter
。
2.1 添加觀察者
要想接收通知庆杜,先要在通知中心注冊(cè)觀察者射众,注冊(cè)時(shí)聲明想要觀察通知的名稱。如果你是在為iPhone應(yīng)用程序的視圖控制器添加觀察者晃财,最好寫在viewDidLoad
方法中叨橱,這樣可以確保視圖控制器加載完成時(shí)只創(chuàng)建唯一一個(gè)觀察者用以接收通知。添加觀察者方法如下:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveText:)
name:@"DidReceiveNotification"
object:nil];
觀察者對(duì)象self
是當(dāng)前視圖控制器断盛,selector
指明當(dāng)視圖控制器接收到通知時(shí)調(diào)用的方法罗洗,這個(gè)方法必須為無返回類型、帶有一個(gè)參數(shù)钢猛。如下所示:
- (void)didReceiveText:(NSNotification *)notification
如果需要獲取與通知一起發(fā)送的用戶信息栖博,可以從NSNotification
對(duì)象中提取,如下:
- (void)didReceiveText:(NSNotification *)notification
{
NSDictionary *userInfo = [notification userInfo];
NSString *receivedText = [userInfo objectForKey:@"YOUR_KEY"];
...
}
2.2 發(fā)送通知
發(fā)送通知的方法很簡(jiǎn)單厢洞,如下所示:
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"YOUR_OBJECT" forKey:@"YOUR_KEY"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"DidReceiveNotification"
object:self
userInfo:userInfo];
通知名稱一般為字符串常量仇让,object
可以是任何想要和通知一起發(fā)送的對(duì)象,但一般為self
或nil
躺翻,如果需要發(fā)送額外信息丧叽,可以使用可選的userInfo
。如果不需要發(fā)送額外信息公你,可以直接把userInfo
設(shè)置為nil
踊淳,或使用postNotificationName: object:
方法。
2.3 移除觀察者
從OS X 10.11和iOS 9.0開始,NSNotificationCenter
將不再向已被釋放掉的觀察者發(fā)送通知迂尝,通知中心對(duì)觀察者是零引用( zeroing weak reference)脱茉。因此,下一次通知中心想要向觀察者發(fā)送通知時(shí)垄开,會(huì)檢測(cè)到觀察者已不存在并為我們移除觀察者琴许,也就是不再需要手動(dòng)移除觀察者。需要注意的是溉躲,如果使用addObserverForName: object: queue: usingBlock:
方法添加的觀察者榜田,或需要支持iOS 8 或更低版本,依舊需要移除觀察者锻梳,移除方法如下:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:@"DidReceiveNotification"
object:nil];
}
如果要移除所有觀察者箭券,可以直接使用removeObserver:
方法。
3. 創(chuàng)建demo
這個(gè)demo整體思路是:有三個(gè)視圖控制器疑枯,第一個(gè)視圖控制器上有兩個(gè)UILabel
辩块,一個(gè)名稱為下一頁UIButton
,點(diǎn)擊下一頁按鈕進(jìn)入第二個(gè)視圖控制器荆永;第二個(gè)視圖控制器上有一個(gè)UILabel
废亭,一個(gè)UITextField
,兩個(gè)UIButton
屁魏,在UITextField
輸入文本后滔以,點(diǎn)擊上一頁將UITextField
的內(nèi)容使用委托傳值到第一個(gè)視圖控制器并在UILabel
顯示捉腥,點(diǎn)擊下一頁進(jìn)入第三個(gè)視圖控制器氓拼;第三個(gè)視圖控制器有一個(gè)UITextField
和一個(gè)上一頁按鈕,在UITextField
輸入文本后點(diǎn)擊上一頁按鈕抵碟,使用通知傳值到前兩個(gè)視圖控制器桃漾,并顯示到UILabel
中。
如下面gif所示:
這里提供一個(gè)demo模版拟逮,在這個(gè)模板上添加代碼進(jìn)行傳值練習(xí)撬统。
模板名稱:Delegation&Notification模板
下載地址:https://github.com/pro648/BasicDemos-iOS
4. 使用委托傳值
4.1 在SecondViewController.h
接口前面聲明協(xié)議,用來傳值敦迄。
#import <UIKit/UIKit.h>
@protocol SendTextDelegate <NSObject>
- (void)sendText:(NSString *)string;
@end
@interface SecondViewController : UIViewController
@end
4.2 在SecondViewController.h
中定義一個(gè)代理屬性恋追。
@interface SecondViewController : UIViewController
@property (weak, nonatomic) id<SendTextDelegate> delegate;
@end
4.3 在SecondViewController.m
實(shí)現(xiàn)文件中,調(diào)用代理方法罚屋。這里在點(diǎn)擊上一頁按鈕回到首頁時(shí)調(diào)用代理方法苦囱,把self.textField
的內(nèi)容傳給代理。傳值前可以先判斷代理是否實(shí)現(xiàn)了協(xié)議的方法脾猛,防止運(yùn)行時(shí)出現(xiàn)問題撕彤。更新后的代碼如下:
- (IBAction)backToVC:(UIButton *)sender
{
// 判斷是否實(shí)現(xiàn)了協(xié)議方法
if ([self.delegate respondsToSelector:@selector(sendText:)])
{
// 代理實(shí)現(xiàn)了協(xié)議方法,傳送TextField內(nèi)文本給代理
[self.delegate sendText:self.textField.text];
}else
{
NSLog(@"代理沒有實(shí)現(xiàn)協(xié)議方法猛拴,%d, %s",__LINE__, __PRETTY_FUNCTION__);
}
// 返回ViewController
[self.navigationController popViewControllerAnimated:YES];
}
4.4 進(jìn)入ViewController.m
文件羹铅,聲明遵守SendTextDelegate
協(xié)議蚀狰。在跳轉(zhuǎn)到SecondViewController
的方法中設(shè)置SecondViewController
的代理為當(dāng)前控制器。
@interface ViewController () <SendTextDelegate>
- (void)goToSecondVC:(UIButton *)sender
{
// 跳轉(zhuǎn)到SecondViewController
SecondViewController *secVC = [[SecondViewController alloc] init];
// 設(shè)置secVC的代理為當(dāng)前控制器
secVC.delegate = self;
[self.navigationController pushViewController:secVC animated:YES];
}
4.5 在ViewController.m
實(shí)現(xiàn)代理方法职员,并把傳來的值顯示到self.deleLabel
中麻蹋。
- (void)sendText:(NSString *)string
{
self.deleLabel.text = string;
}
5. 使用通知傳值
5.1 添加觀察者
在ViewController.m
和SecondViewController.m
的viewDidLoad
方法中添加觀察者,name使用全局變量廉邑,接收到通知后哥蔚,執(zhí)行被調(diào)用的方法,把通知附帶的字符串顯示在notiLabel上蛛蒙。更新后的代碼如下:
// ViewController.m
extern NSString *NotificationFromThirdVC;
@implementation ViewController
- (void)viewDidLoad {
...
// 添加觀察者
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveNotificationMessage:)
name:NotificationFromThirdVC
object:nil];
}
- (void)didReceiveNotificationMessage:(NSNotification *)notification
{
if ([[notification name] isEqualToString:NotificationFromThirdVC])
{
// 把通知傳送的字符串顯示到notiLabel
NSDictionary *dict = [notification userInfo];
NSString *string = [dict objectForKey:@"TextField"];
self.notiLabel.text = string;
}
}
// SecondViewController.m中的代碼與ViewController.m中的一樣糙箍,你可以自己寫。如果遇到問題牵祟,可以在文章尾部下載源碼查看深夯。
SecondViewController.m
中使用addObserverForName:object:queue:usingBlock:
方法注冊(cè)觀察者。代碼如下:
- (void)viewDidLoad
{
...
// 添加觀察者
[[NSNotificationCenter defaultCenter] addObserverForName:NotificationFromThirdVC
object:nil
queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
if ([note.name isEqualToString:NotificationFromThirdVC]) {
// 把通知傳送的字符串顯示到notiLabel诺苹。
NSDictionary *userInfo = [note userInfo];
self.notiLabel.text = [userInfo valueForKey:@"TextField"];
}
}];
}
5.2 發(fā)送通知
首先在ThirdViewController.m
實(shí)現(xiàn)部分前先聲明通知名稱為全局變量咕晋。
NSString *NotificationFromThirdVC = @"NotificationFromThirdVCTextField";
在ThirdViewController.m
實(shí)現(xiàn)部分,在點(diǎn)擊回到上一頁按鈕時(shí)發(fā)送通知收奔,把UITextField
中的字符串做為額外信息發(fā)送掌呜,更新后代碼如下:
- (IBAction)backToSecVC:(UIButton *)sender
{
// 發(fā)送通知
NSString *string = self.textField.text;
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:string forKey:@"TextField"];
[[NSNotificationCenter defaultCenter] postNotificationName:NotificationFromThirdVC
object:nil
userInfo:userInfo];
// 返回SecondViewController
[self.navigationController popViewControllerAnimated:YES];
}
5.3 移除觀察者
在ViewController.m
中添加觀察者使用的是addObserver: selector: name: object:
方法,模擬器是iOS 11坪哄,且不計(jì)劃支持iOS 8或更低版本质蕉,ViewController.m
中添加的觀察者不需要移除。
SecondViewController.m
中添加觀察者使用的是addObserverForName:object:forQueue:usingBlock:
方法翩肌,必須手動(dòng)移除觀察者模暗。代碼如下:
- (void)dealloc {
// 移除觀察者。
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NotificationFromThirdVC
object:nil];
}
使用通知中心傳遞信息時(shí)念祭,一定要先實(shí)例化觀察者兑宇,再發(fā)送通知。例如:TabBar上有VC1和VC2兩個(gè)視圖控制器粱坤,運(yùn)行后先進(jìn)入VC1隶糕,如果直接在VC1上發(fā)送通知,VC2上將不能接收到通知站玄,因?yàn)榇藭r(shí)VC2還沒有運(yùn)行枚驻,VC2的觀察者還沒有在通知中心注冊(cè),所以需要進(jìn)入VC1后再點(diǎn)擊進(jìn)入VC2蜒什,之后再返回VC1發(fā)送通知测秸,此時(shí)VC2就可以接收到通知。
現(xiàn)在,運(yùn)行demo霎冯,如下所示:
總結(jié)
初看铃拇,通知是一種沒有缺點(diǎn)的方式來減少類之間的依賴,你甚至不需要向你的類添加一個(gè)委托實(shí)例變量∩蜃玻現(xiàn)在再來看一下通知的缺點(diǎn)慷荔,當(dāng)你發(fā)送通知時(shí),通知中心會(huì)同步向所有在通知中心注冊(cè)的觀察者發(fā)送信息缠俺,直到所有觀察者調(diào)用他們的注冊(cè)方法后显晶,發(fā)起通知的代碼才會(huì)再次獲得控制。值得注意的是壹士,當(dāng)你向多個(gè)觀察者發(fā)送通知并且發(fā)送通知的代碼需要等待完成某些操作時(shí)磷雇,只有當(dāng)所有觀察者方法被調(diào)用并執(zhí)行完畢時(shí),發(fā)送通知的代碼才會(huì)再次獲得控制(觀察者方法以一些未指定的順序一個(gè)接一個(gè)地調(diào)用)躏救。為解決這個(gè)問題唯笙,一種方法是在不同線程上有額外通知中心,同時(shí)使用異步通知盒使,NSNotificationQueue
允許調(diào)用立即返回崩掘。這樣在大多數(shù)情況下會(huì)額外增加代碼的復(fù)雜性。另一種簡(jiǎn)便方法是使用performSelector: withObject: afterDelay:
延遲處理通知少办。
- (void)didReceiveNotificationMessage:(NSNotification *)notification
{
if ([[notification name] isEqualToString:NotificationFromThirdVC])
{
// 把通知傳送的字符串顯示到notiLabel
NSDictionary *dict = [notification userInfo];
NSString *string = [dict objectForKey:@"TextField"];
// 延遲處理
[self performSelector:@selector(DO_YOUR_REAL_WORK) withObject:string afterDelay:0.3];
}
}
這樣可以使發(fā)布通知的代碼更快獲得控制苞慢。此時(shí)觀察者方法在同一線程執(zhí)行。
通知是將信息傳播到你無法接觸到的多個(gè)對(duì)象的一種方法英妓,因此挽放,它可以用在視圖控制器間傳遞數(shù)據(jù),但一般來說鞋拟,不要這樣做骂维。當(dāng)你發(fā)送一個(gè)通知惹资,你不知道哪一個(gè)對(duì)象會(huì)對(duì)此做出反應(yīng)贺纲,如果遇到錯(cuò)誤將難以追蹤,別人維護(hù)你的代碼也會(huì)變的更加困難褪测。
NSUserDefaults
是用來永久保存用戶偏好設(shè)置猴誊,以便app下次啟動(dòng)時(shí)使用。任何保存在此位置的數(shù)據(jù)如沒有明確刪除會(huì)永遠(yuǎn)保存在這里侮措。所以最好不要使用NSUserDefaults
傳值懈叹。
始終使用代理將信息傳回其他控制器,內(nèi)容視圖控制器應(yīng)該永遠(yuǎn)不需要知道源視圖控制器的類或不是它創(chuàng)建的視圖控制器分扎。另外澄成,如果你想獲取對(duì)象屬性的變化,最好使用Key Value Observing。
在任何情況下墨状,都不應(yīng)該讓視圖控制器發(fā)送通知或委托消息卫漫。在多數(shù)情況下,視圖控制器應(yīng)該更改模型肾砂,然后模型通知觀察者或委托它已被更改列赎。
文件名稱:Delegation&Notification
源碼地址:https://github.com/pro648/BasicDemos-iOS
參考資料: