委託、通知傳值的用法與區別

低耦合性是良好程序的特性。低耦合性程序可讀性和可維護性比較好。Cocoa中的委託、通知功能可使低耦合性更易實現,下面結合demo說明如何使用委託、通知進行傳值,及委託與通知的區別。ios

1. 委託傳值

委託傳值在反向傳值中使用。使用委託可讓委託和委託對象之間的關係變得清晰,特別是在委託的方法必須實現時。git

委託傳值步驟以下:github

1.1 在ChildViewController.h聲明協議,協議內方法默認必須實現。若是想選擇實現,在方法前用@optional標誌出來。編程

#import <UIKit/UIKit.h>

@protocol ChildVCDelegate <NSObject>

- (void)didReceiveText:(NSString *)string;

@optional
- (void)receiveTextFailedWithError:(NSError *)error;

@end

1.2 在ChildViewController.h接口部分建立一個ChildVCDelegate類型的實例變量。此時的特性應該使用weak,不然會形成循環引用。app

#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協議。異步

#import "ViewController.h"
#import "ChildViewController.h"

@interface ViewController () <ChildVCDelegate>

@end

1.4 在RootViewController.m實現協議方法,將ChildViewController的代理委託給當前控制器。post

@implementation ViewController

// 1
ChildViewController *childVC = [[ChildViewController alloc] init];
childVC.delegate = self;

- (void)didReceiveText:(NSString *)string
{
    
}
註釋1後的代碼須要添加到跳轉到 ChildViewController的方法內。若是使用純代碼編程,添加到 presentViewController: animated: completion: showViewController: animated: 方法前;若是使用 storyboardsegue跳轉,添加到 prepareForSegue: sender: 方法內,此時初始化視圖控制器應該使用 SecViewController *secVC =segue.destinationViewController;

1.5 在ChildViewController.m實現部分,調用代理方法。爲防止運行時出現問題,調用方法前要先判斷代理是否實現了調用的方法。atom

// 在某方法內
if ([self.delegate respondsToSelector:@selector(didReceiveText:)])
    {
        [self.delegate didReceiveText:@"pro648"];
    }

2. 通知傳值

NSNotificationCenter對象(簡稱通知中心)提供了廣播信息的機制,NSNotificationCenter對象實質上是一個通知分發表。對象使用addObserver: selector: name: object: addObserverForName: object: queue: usingBlock: 方法向通知中心註冊以接收通知,每次調用上面的方法都會指定一組通知。所以,對象能夠經過屢次調用這些方法註冊爲不一樣通知的觀察者。url

每個運行的Cocoa程序都有一個默認通知中心,通常不須要本身建立。NSNotificationCenter對象只能在單個進程中傳遞通知。若是須要向其餘進程發送通知,或從其餘進程接收通知,請使用NSDistributedNotificationCenterspa

2.1 添加觀察者

要想接收通知,先要在通知中心註冊觀察者,註冊時聲明想要觀察通知的名稱。若是你是在爲iPhone應用程序的視圖控制器添加觀察者,最好寫在viewDidLoad方法中,這樣能夠確保視圖控制器加載完成時只建立惟一一個觀察者用以接收通知。添加觀察者方法以下:

[[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didReceiveText:)
                                                 name:@"DidReceiveNotification"
                                               object:nil];

觀察者對象self是當前視圖控制器,selector指明當視圖控制器接收到通知時調用的方法,這個方法必須爲無返回類型、帶有一個參數。以下所示:

- (void)didReceiveText:(NSNotification *)notification

若是須要獲取與通知一塊兒發送的用戶信息,能夠從NSNotification對象中提取,以下:

- (void)didReceiveText:(NSNotification *)notification
{
    NSDictionary *userInfo = [notification userInfo];
    NSString *receivedText = [userInfo objectForKey:@"YOUR_KEY"];
    ...
}

2.2 發送通知

發送通知的方法很簡單,以下所示:

NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"YOUR_OBJECT" forKey:@"YOUR_KEY"];
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"DidReceiveNotification"
                                                        object:self
                                                      userInfo:userInfo];

通知名稱通常爲字符串常量,object能夠是任何想要和通知一塊兒發送的對象,但通常爲selfnil,若是須要發送額外信息,可使用可選的userInfo。若是不須要發送額外信息,能夠直接把userInfo設置爲nil,或使用postNotificationName: object: 方法。

2.3 移除觀察者

從OS X 10.11和iOS 9.0開始,NSNotificationCenter將再也不向已被釋放掉的觀察者發送通知,通知中心對觀察者是零引用( zeroing weak reference)。所以,下一次通知中心想要向觀察者發送通知時,會檢測到觀察者已不存在併爲咱們移除觀察者,也就是再也不須要手動移除觀察者。須要注意的是,若是使用addObserverForName: object: queue: usingBlock: 方法添加的觀察者,或須要支持iOS 8 或更低版本,依舊須要移除觀察者,移除方法以下:

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:@"DidReceiveNotification"
                                                  object:nil];
}

若是要移除全部觀察者,能夠直接使用removeObserver: 方法。

3. 建立demo

這個demo總體思路是:有三個視圖控制器,第一個視圖控制器上有兩個UILabel,一個名稱爲下一頁UIButton,點擊下一頁按鈕進入第二個視圖控制器;第二個視圖控制器上有一個UILabel,一個UITextField,兩個UIButton,在UITextField輸入文本後,點擊上一頁UITextField的內容使用委託傳值到第一個視圖控制器並在UILabel顯示,點擊下一頁進入第三個視圖控制器;第三個視圖控制器有一個UITextField和一個上一頁按鈕,在UITextField輸入文本後點擊上一頁按鈕,使用通知傳值到前兩個視圖控制器,並顯示到UILabel中。

以下面gif所示:

Delegation&Notification.gif

這裏提供一個demo模版,在這個模板上添加代碼進行傳值練習。

模板名稱:Delegation&Notification模板
下載地址:https://github.com/pro648/Bas...

4. 使用委託傳值

4.1 在SecondViewController.h接口前面聲明協議,用來傳值。

#import <UIKit/UIKit.h>

@protocol SendTextDelegate <NSObject>

- (void)sendText:(NSString *)string;

@end

@interface SecondViewController : UIViewController

@end

4.2 在SecondViewController.h中定義一個代理屬性。

@interface SecondViewController : UIViewController

@property (weak, nonatomic) id<SendTextDelegate> delegate;

@end

4.3 在SecondViewController.m實現文件中,調用代理方法。這裏在點擊上一頁按鈕回到首頁時調用代理方法,把self.textField的內容傳給代理。傳值前能夠先判斷代理是否實現了協議的方法,防止運行時出現問題。更新後的代碼以下:

- (IBAction)backToVC:(UIButton *)sender
{
    // 判斷是否實現了協議方法
    if ([self.delegate respondsToSelector:@selector(sendText:)])
    {
        // 代理實現了協議方法,傳送TextField內文本給代理
        [self.delegate sendText:self.textField.text];
    }else
    {
        NSLog(@"代理沒有實現協議方法,%d, %s",__LINE__, __PRETTY_FUNCTION__);
    }
    
    // 返回ViewController
    [self.navigationController popViewControllerAnimated:YES];
}

4.4 進入ViewController.m文件,聲明遵照SendTextDelegate協議。在跳轉到SecondViewController的方法中設置SecondViewController的代理爲當前控制器。

@interface ViewController () <SendTextDelegate>

- (void)goToSecondVC:(UIButton *)sender
{
    // 跳轉到SecondViewController
    SecondViewController *secVC = [[SecondViewController alloc] init];
    
    // 設置secVC的代理爲當前控制器
    secVC.delegate = self;
    
    [self.navigationController pushViewController:secVC animated:YES];
}

4.5 在ViewController.m實現代理方法,並把傳來的值顯示到self.deleLabel中。

- (void)sendText:(NSString *)string
{
    self.deleLabel.text = string;
}

5. 使用通知傳值

5.1 添加觀察者

ViewController.mSecondViewController.mviewDidLoad方法中添加觀察者,name使用全局變量,接收到通知後,執行被調用的方法,把通知附帶的字符串顯示在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:方法註冊觀察者。代碼以下:

- (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 發送通知

首先在ThirdViewController.m實現部分前先聲明通知名稱爲全局變量。

NSString *NotificationFromThirdVC = @"NotificationFromThirdVCTextField";

ThirdViewController.m實現部分,在點擊回到上一頁按鈕時發送通知,把UITextField中的字符串作爲額外信息發送,更新後代碼以下:

- (IBAction)backToSecVC:(UIButton *)sender
{
    // 發送通知
    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,且不計劃支持iOS 8或更低版本,ViewController.m中添加的觀察者不須要移除。

SecondViewController.m中添加觀察者使用的是addObserverForName:object:forQueue:usingBlock:方法,必須手動移除觀察者。代碼以下:

- (void)dealloc {
    // 移除觀察者。
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NotificationFromThirdVC
                                                  object:nil];
}
使用通知中心傳遞信息時,必定要先實例化觀察者,再發送通知。例如: TabBar上有 VC1VC2兩個視圖控制器,運行後先進入 VC1,若是直接在 VC1上發送通知, VC2上將不能接收到通知,由於此時 VC2尚未運行, VC2的觀察者尚未在通知中心註冊,因此須要進入 VC1後再點擊進入 VC2,以後再返回 VC1發送通知,此時 VC2就能夠接收到通知。

如今,運行demo,以下所示:

Delegation&Notification.gif

總結

初看,通知是一種沒有缺點的方式來減小類之間的依賴,你甚至不須要向你的類添加一個委託實例變量。如今再來看一下通知的缺點,當你發送通知時,通知中心會同步向全部在通知中心註冊的觀察者發送信息,直到全部觀察者調用他們的註冊方法後,發起通知的代碼纔會再次得到控制。值得注意的是,當你向多個觀察者發送通知而且發送通知的代碼須要等待完成某些操做時,只有當全部觀察者方法被調用並執行完畢時,發送通知的代碼纔會再次得到控制(觀察者方法以一些未指定的順序一個接一個地調用)。爲解決這個問題,一種方法是在不一樣線程上有額外通知中心,同時使用異步通知,NSNotificationQueue容許調用當即返回。這樣在大多數狀況下會額外增長代碼的複雜性。另外一種簡便方法是使用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];
    }
}

這樣可使發佈通知的代碼更快得到控制。此時觀察者方法在同一線程執行。

通知是將信息傳播到你沒法接觸到的多個對象的一種方法,所以,它能夠用在視圖控制器間傳遞數據,但通常來講,不要這樣作。當你發送一個通知,你不知道哪個對象會對此作出反應,若是遇到錯誤將難以追蹤,別人維護你的代碼也會變的更加困難。

NSUserDefaults是用來永久保存用戶偏好設置,以便app下次啓動時使用。任何保存在此位置的數據如沒有明確刪除會永遠保存在這裏。因此最好不要使用NSUserDefaults傳值。

始終使用代理將信息傳回其餘控制器,內容視圖控制器應該永遠不須要知道源視圖控制器的類或不是它建立的視圖控制器。另外,若是你想獲取對象屬性的變化,最好使用Key Value Observing

在任何狀況下,都不該該讓視圖控制器發送通知或委託消息。在多數狀況下,視圖控制器應該更改模型,而後模型通知觀察者或委託它已被更改。

文件名稱:Delegation&Notification
源碼地址:https://github.com/pro648/Bas...

參考資料:

  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/tip...

本文地址:https://github.com/pro648/tip...

相關文章
相關標籤/搜索