[iOS x Design Pattern] 觀察者模式 Observer Pattern ( I )

無論在任何程式語言中,觀察者模式都是使用率最高的一種設計模式,在此模式中會有一個主題(subject)物件讓其他物件可以訂閱,而其他的訂閱者就稱為觀察者(Observer),當主題有更新資料時,會主動通知所有的訂閱者,因此訂閱者不需要在費心處理資料有沒有更新這回事。

定義一對多的物件依存關係,讓物件狀態一有變動,就自動通知其他相依物件做該做的更新動作。--Design patterns : Elements of Reusable Object-Orientied Software

Wiki - Observer pattern

問題

在一般生活中也有顯而易見的觀察者模式,

你習慣看某個Blog,但這個作者很有個性,不是固定時間更新Blog,
有的時候一天發三篇文章,有的時候又一個月才發一篇。

你總不可能三天兩頭一直去check這個Blog,這樣太浪費時間了。

這時候你就會想,要是有個東西可以在這個Blog更新的時候通知我,那該有多好。

於是RSS就是這個問題的最佳解決方案,RSS協定讓User可以藉由RSS reader自動檢查這個Blog,並且一有更新就通知你。

iOS的觀察者模式

在iOS當中也有這樣的問題存在,於是官方實作了兩項有解決方案分別是NotificationKVO

[Notification]

iOS提供一個NotifivationCenter,你可以在任何一個地方去訂閱發送通知

  1. 訂閱:告訴NotificationCenter,我要訂閱

    [[NSNotificationCenter defaultCenter] addObserver: self
    selector: @selector(blogUpdated)
    name: @"部落格更新"
    object: nil];
    
  2. 定義Callbak:收到更新後我要做什麼事情

    - (void)blogUpdated{
        NSLog(@"部落格更新了,現在就去看");
    }
    
  3. 更新: 在任何一處都可以更新並通知User

    - (void)didWhiteNewBlog
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"部落格更新" object:self];
    }
    

Notification的優點是,訂閱者不用知道主題(subject)的實體物件在哪裡,只管聆聽某一個name,有點像網路協定中,我們講好我們用某某port來做溝通,subject只管往那個port送資料,而訂閱者只管去那個port收資料。
而這個方法的缺點是,主題(subject)與觀察者(Observer)必須事先協議好溝通的key,如果兩端都是同一個人實作那問題還小,因為你自己定key,假如你沒有主題(subject)的主控權,又要如何訂閱呢? 這就要請到下一位KVO大大了。

[Key-Value-Observer (KVO)]

KVO的實作比較困難一點點,但帶來的好處就是被訂閱的主題(subject)完全無感,就好像你在監聽他一樣(不過完全合法),只要該物件的property修改,就會馬上通知你。

參考Apple官方的KVO Programming Guide,其中指出KVO常用於model and controller layer尤其是從controller去綁定(觀察)model

controller訂閱model

model <- controller

iOS把KVO實作在基礎類別NSObject,所以你可以任意訂閱任一物件,你就是老大不用擔心被告違法(監聽),接著告訴大家該怎麼做:

  1. 了解要訂閱的物件
//Subject.h
@interface Subject : NSObject
@property (strong, nonatomic) NSString *lastBlogPostDate;
@end

我們要訂閱subject物件中lastBlogsPostDate這個property,在他更新部落格文章時,接收第一手的資訊。

  1. 訂閱與接收通知
//Observer.m
#import "Subject.h"

//訂閱
- (void)subscribeSubject:(Subject *)subject
{
    /* 向subject物件增加一個觀察者(self),
  並且我要觀察'lastBlogPostDate'這個屬性,
  options: 當該值更新時把新舊值都傳給我(放在dictionary中)
  */
    [subject addObserver:self forKeyPath:@"lastBlogPostDate" 
  options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
    context:nil];
}

//接收通知
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if([keyPath isEqualToString:@"lastBlogPostDate"])
    {
        NSLog(@"%@",change);
    }
}

其中option的部份有下面幾項:

  • NSKeyValueObservingOptionNew : 通知時要告訴我新的值
  • NSKeyValueObservingOptionOld : 通知時要告訴我舊的值
  • NSKeyValueObservingOptionInitial : 訂閱時會先通知一次
  • NSKeyValueObservingOptionPrior : 變更數值前會先通知一次,變更數值後再通知一次(通知兩次)

KVO的優點是可以更改subject的情況下訂閱(也意味著被訂閱的subject完全不用理會通知observer這件事情),缺點是observer必須要知道subject的實體位置(在訂閱時需要該本體)。

KVO與notification各有好處,就看大家怎麼去用囉:)

後記

為了寫文章反而學到更多,真是不錯,缺點是每天都在熬夜阿XD