委托、通知傳值的用法與區(qū)別

低耦合性是良好程序的特性扶欣。低耦合性程序可讀性和可維護(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:方法前;如果使用storyboardsegue跳轉(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ì)象,但一般為selfnil躺翻,如果需要發(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所示:

Delegation&Notification.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.mSecondViewController.mviewDidLoad方法中添加觀察者,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上有VC1VC2兩個(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霎冯,如下所示:

Delegation&Notification.gif

總結(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

參考資料:

  1. Delegation or Notification
  2. Unregistering NSNotificationCenter Observers in iOS 9
  3. How iOS View Controllers communicate with each other
  4. NSNotification & NSNotificationCenter

歡迎更多指正:https://github.com/pro648/tips/wiki

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市镐确,隨后出現(xiàn)的幾起案子包吝,更是在濱河造成了極大的恐慌,老刑警劉巖源葫,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诗越,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡息堂,警方通過查閱死者的電腦和手機(jī)掺喻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來储矩,“玉大人感耙,你說我怎么就攤上這事〕炙恚” “怎么了即硼?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)屡拨。 經(jīng)常有香客問我只酥,道長(zhǎng),這世上最難降的妖魔是什么呀狼? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任裂允,我火速辦了婚禮,結(jié)果婚禮上哥艇,老公的妹妹穿的比我還像新娘绝编。我一直安慰自己,他們只是感情好貌踏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布十饥。 她就那樣靜靜地躺著,像睡著了一般祖乳。 火紅的嫁衣襯著肌膚如雪逗堵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天眷昆,我揣著相機(jī)與錄音蜒秤,去河邊找鬼汁咏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛作媚,可吹牛的內(nèi)容都是我干的梆暖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼掂骏,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼轰驳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起弟灼,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤级解,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后田绑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勤哗,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年掩驱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芒划。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欧穴,死狀恐怖民逼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涮帘,我是刑警寧澤拼苍,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站调缨,受9級(jí)特大地震影響疮鲫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弦叶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一俊犯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伤哺,春花似錦燕侠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咸包。三九已至桃序,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烂瘫,已是汗流浹背媒熊。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國打工奇适, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芦鳍。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓嚷往,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親柠衅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子皮仁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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