en

hi, it seems you are using microsoft internet explorer. it doesn't match web standard and causes problems browsing this site. please please please use mozilla firefox or google chrome instead. thank you!

zh

哦哦!您正在使用Internet Explorer 瀏覽器,它與我們的網頁標準並不相容,可能會導致畫面顯示不正常。
請改用 Mozilla Firefox 或者 Google Chrome 才能正常瀏覽本網站,謝謝!

12.26.2012

類似 Observer Pattern 的 NSNotificationCenter

在 Design Patterns 中的 Observer Pattern 主要目是用來解決一對多的物件之間的依附關係,只要物件狀態一有變動,就可以通知其他相依物件做跟更新的動作,舉個簡單的例子 Observer 就像是一個班級裡負責聯絡的窗口一樣,當班級內有人有訊息需要通知給所有人時,只要告訴這個聯絡窗口,之後就由聯絡窗口統一通知班級內的所有人,當然也包含發佈消息的這個人。在 Objective-C 裡我們並不需要真的去實作出 Observer Pattern,透過使用 NSNotificationCenter 也可以達到相同的效果。

NSNotificationCenter 可以分成三部份來看,分別是註冊(訂閱)、註銷(取消訂閱)與訊息通知。


註冊(訂閱)通知
在訂閱的部份,物件必須向 NSNotificationCenter 進行註冊,並且說明想要訂閱的事件通知,和設定收到通知時要執行的函式,通常我們可以將訂閱的部份直接寫在物件初始化的地方,這樣物件在建立之後就可以立刻向 NSNotificationCenter 訂閱他所需要的資訊。

- (id)init {
    self = [super init];
    if (self) {

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(echoNotification:) name:@"showSomething" object:nil];
    }

    return self;
}

上述程式碼在完成初始化之後隨即向 NSNotificationCenter 訂閱關於 showSomething 的訊息事件,可以解釋成日後只要 NSNotificationCenter 有發佈有關 showSomething 訊息事件的通知,此物件就會執行 echoNotification: 函式,而此物件內的 echoNotification: 函式如下。

- (void)echoNotification:(NSNotification *)notification {

    //取得由NSNotificationCenter送來的訊息
    NSArray *anyArray = [notification object];

    NSLog(@"%@", [anyArray componentsJoinedByString:@"\n"]);
}

由 NSNotificationCenter 送來的訊息可以是任何形態,在這裡假定訂閱的 showSomething 訊息事件只會送來 NSArray 形態的參數。


註銷(取消訂閱)通知
在取消訂閱的部份,可以參考下列程式碼來註銷該物件對 NSNotificationCenter 訂閱的所有訊息事件。

[[NSNotificationCenter defaultCenter] removeObserver:self];
或是使用下列程式碼對特定的訊息事件取消訂閱(同一個物件可以同時訂閱數種不同的訊息事件)。

[[NSNotificationCenter defaultCenter] removeObserver:self name:@"showSomething" object:nil];

觸發訊息通知
最後是觸發訊息通知的部份,其程式碼如下。
[[NSNotificationCenter defaultCenter] postNotificationName:@"showSomething" object:stringArray];
執行上述程式碼,NSNotificationCenter 將會通知所有訂閱 showSomething 訊息事件的物件,並附帶訊息內容(stringArray 參數)。


其他應用
如果我們在訂閱通知的部份裡,將所要訂閱的訊息名稱使用不具名(nil)的方式來對 NSNotificationCenter 進行註冊,那麼將會無差別的訂閱所有訊息通知,這個結果可以幫助我們了解程式在執行時到底觸發了哪些 NSNotification。

也許這樣說有些籠統,下列程式碼我們就將對 NSNotificationCenter 進行註冊的程式碼片段撰寫在 viewDidLoad 中,並且對所有訊息事件通知都進行註冊,你可以在執行結果中發現,當程式開啟時究竟觸發了多少個 NSNotification,這對於日後如果要透過相關的 NSNotification 來進行程式設計時,非常有幫助。
- (void)viewDidLoad {
    [super viewDidLoad];

    //對所有訊息事件通知進行註冊
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myNotification:) name:nil object:nil];
}

-(void)myNotification:(NSNotification*) notification {
    NSLog(@"收到由 %@ 傳來的通知 %@",([[notification object] class]), [notification name]);
}

執行 Single View Application 空白專案樣板時,所有觸發的事件通知的執行結果







4 則留言:

  1. 有問題想請問,
    如果我在aClass init 中宣告了
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(echoNotification:) name:@"showSomething" object:nil]

    且實作了
    - (void)echoNotification:(NSNotification *)notification {

    //取得由NSNotificationCenter送來的訊息
    NSArray *anyArray = [notification object];

    NSLog(@"%@", [anyArray componentsJoinedByString:@"\n"]);
    }

    如果我在bClass 才作訊息通知
    [[NSNotificationCenter defaultCenter] postNotificationName:@"showSomething" object:stringArray];

    這樣可以成功call到aClass的函式嗎?

    回覆刪除
    回覆
    1. cloud 您好:

      是可以的,Observer Pattern( NSNotificationCenter)就是為了應付這類的情況,但是必須注意,你從 bClass 觸發 NSNotificationCenter 時,物件 aClass必須已經存在,否則它不會被觸發,在設計上可以配合繼承會比較容易實作。

      另外如果只是單存解決這樣的問題,你也可以考慮使用 Protocol,來做參數的傳遞,煩請參考「在兩個不同的 Class 之間傳遞參數的方法」一文。
      http://furnacedigital.blogspot.tw/2012/07/class.html

      希望這樣有幫到你。

      刪除
    2. 牛奶:

      感謝你的回答,確實有解決到問題喔:)
      真是太感謝了~~

      刪除
    3. Cloud:

      不用客氣唷~

      刪除