# 通知 - Notification

不要把這裡的通知和推送通知或者本地通知搞混了，這裡的通知是基於訂閱-發佈模型的，即一個物件 (發佈者) 向其他物件 (訂閱者) 發送消息。

> 發佈者永遠不需要知道訂閱者的任何數據。

Apple 對於通知的使用很頻繁，比如當鍵盤彈出或者收起的時候，系統會分別發送 `UIKeyboardWillShowNotification/UIKeyboardWillHideNotification` 的通知。當你的應用切到後台的時候，又會發送 `UIApplicationDidEnterBackgroundNotification` 的通知。

注意：打開 `UIApplication.swift` 檔案，在檔案結尾你會看到二十多種系統發送的通知。

## 如何使用通知

打開 `FlickrModel.swift` 然後先在類別宣告的下一行先加入一個靜態常數字串：

```swift
static let FLICKR_DATA_COMPLETE:String = "flickrDataComplete"
```

為了避免「通知事件名稱」的誤寫，所以預先定義一個靜態常數字串。

然後我們追加一個專門發送系統通知的程式：

```swift
    private func postFlickrDataCompleteNotification(aPageIndex:UInt){
        let _userObject:[NSObject:AnyObject] = ["pageIndex":aPageIndex]
        NSNotificationCenter.defaultCenter().postNotificationName(FlickrModel.FLICKR_DATA_COMPLETE, object: self, userInfo:_userObject )
    }
```

只要執行`postFlickrDataCompleteNotification(aPageIndex:UInt)`，就會發送一個名稱為`FlickrModel.FLICKR_DATA_COMPLETE`的通知。

然後我們在每一次的 Flickr 資料載入完成後，就執行一次這支程式來透過`NSNotificationCenter.defaultCenter()`發送通知，也就是`onLoadFlickrDataCompleteHandler`裡面的最後一行。

```swift
    private func onLoadFlickrDataCompleteHandler(aRequest:NSURLRequest?, aResponse:NSHTTPURLResponse?, aResult:Result<AnyObject>){
        var _vos:[PhotoVO] = [PhotoVO]()
        var _pageIndex:UInt = 0
        if aResult.error == nil {
            // 中間略
            print("LoadFlickrDataComplete!count:\(_vos.count)")
        }else{
            print("loadFlickrData Error:\(aResult.error)")
        }
        postFlickrDataCompleteNotification(_pageIndex)
    }
```

而我們載入 Flickr 的圖片庫資料時，他給我們的每一頁照片資訊我們已經放到字典裡面，儲存在記憶體中，沒有必要每一次都要再更新資料，所以我們可以再把`loadFlickrData(aPageIndex:UInt)`這支程式再做一下修改，判斷如果字典裡已經有資料的話，就不用再下載了，然後發送通知，如果沒有下載過，就下載資料。

```swift
    func loadFlickrData(aPageIndex:UInt){
        if let _:[PhotoVO] = mPhotoVOs[mPerPage] {
            postFlickrDataCompleteNotification(aPageIndex)
        }else{
            Alamofire.request(Method.GET, FlickrAPI.flickrAPIUrl(), parameters: FlickrAPI.flickrInterestingness(aPageIndex, aPerPage: mPerPage), encoding: ParameterEncoding.URL, headers: nil)
                .responseJSON(completionHandler: onLoadFlickrDataCompleteHandler)
        }
    }
```

然後再打開 `MainViewController.swift` ， 把之前註解的`mPhotoViews`反註解，並鍵入如下程式碼：

```swift
    private var mPhotoViews:[PhotoView] = [PhotoView]()
    private func createDemoLayoutSecond(){
        mFlickrModel.loadFlickrData(1)

        for _index:UInt in 0..<10 {
            let _photoView  :PhotoView = PhotoView()
            let _boardWidth :CGFloat = 5
            let _dWidth     :CGFloat = (self.view.bounds.width - _boardWidth * 5)  / 4
            let _dHeight    :CGFloat = (self.view.bounds.height - _boardWidth * 6) / 5
            let _dX         :CGFloat = _boardWidth + CGFloat(_index % 4) * (_dWidth + _boardWidth)
            let _dY         :CGFloat = 50 + CGFloat(_index / 4) * (_dHeight + _boardWidth)
            _photoView.frame = CGRectMake( _dX, _dY , _dWidth, _dHeight)

            self.view.addSubview(_photoView)
            mPhotoViews.append(_photoView)
        }
    }
```

之前我們是等 Console 顯示 `LoadFlickrDataComplete!count:99` 之後，確定載入完成，再「手動按下按鈕」，讓 PhotoView 去做圖片載入的動作，這一次我們透過觀察者模式來完成這些事情。

現在的 `PhotoModel` 已經會透過`NSNotificationCenter.defaultCenter()`發送`FlickrModel.FLICKR_DATA_COMPLETE`通知了，所以讓`MainViewController`當觀察者去觀察`NSNotificationCenter.defaultCenter()`這個單一設計模式的實體何時會 發送這樣的通知。

```swift
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.whiteColor()

        //createDemoLayout()
        createDemoLayoutSecond()
    }

    private func createDemoLayoutSecond(){
        NSNotificationCenter.defaultCenter().addObserver(self, selector:"onFlickrDataCompleteHandler:", name: FlickrModel.FLICKR_DATA_COMPLETE, object: nil)

        mFlickrModel.loadFlickrData(1)

        for _index:UInt in 0..<10 {
            let _photoView  :PhotoView = PhotoView()
            let _boardWidth :CGFloat = 5
            let _dWidth     :CGFloat = (self.view.bounds.width - _boardWidth * 5)  / 4
            let _dHeight    :CGFloat = (self.view.bounds.height - _boardWidth * 6) / 5
            let _dX         :CGFloat = _boardWidth + CGFloat(_index % 4) * (_dWidth + _boardWidth)
            let _dY         :CGFloat = 50 + CGFloat(_index / 4) * (_dHeight + _boardWidth)
            _photoView.frame = CGRectMake( _dX, _dY , _dWidth, _dHeight)

            self.view.addSubview(_photoView)
            mPhotoViews.append(_photoView)
        }
    }
```

每當 `FlickrModel` 發出一個 `FlickrModel.FLICKR_DATA_COMPLETE` 通知的時候，由於 `MainViewController` 已經註冊了成為觀察者，所以系統會調用 `onFlickrDataCompleteHandler:` 方法。

但是，在實現 `onFlickrDataCompleteHandler:` 之前，我們必須先在 `dealloc` 取消監聽。如果沒有取消監聽消息，消息會發送給一個已經銷毀的物件，導致應用程式閃退。

在 `MainViewController.swift` 加上取消訂閱的程式碼：

```swift
deinit {
    NSNotificationCenter.defaultCenter().removeObserver(self)
}
```

當物件銷毀的時候，把它從所有消息的訂閱列表裡去除。

然後再完成`onFlickrDataCompleteHandler:`：

```swift
    func onFlickrDataCompleteHandler(aNotification: NSNotification){
        if let _pageIndex:UInt = aNotification.userInfo!["pageIndex"] as? UInt {
            let _vos:[PhotoVO] = mFlickrModel.getPhotos(_pageIndex)
            for _index in 0..<mPhotoViews.count {
                mPhotoViews[_index].downloadImage(_vos[_index].url_z,aCacheFileName: _vos[_index].url_z+"_z.png")
            }
        }
    }
```

只要收到`FlickrModel.FLICKR_DATA_COMPLETE`，就會執行`onFlickrDataCompleteHandler(aNotification: NSNotification)`，把通知裡面包含的`userInfo`字典裡面的`pageIndex`取出，再從`FlickrModel`的實體撈取資料，再把圖片資料的網址分別給予畫面上的`PhotoView`。

我們來回顧一下目前所做的流程：

* `FlickrModel` 負責處理最開始要載入的全部圖片資料，包含圖片網址等。
* `FlickrModel` 裡面完成 Flickr資料載入，但是我們不要別的類別再去操作。
* 資料載入完以後，再發送通知說`FlickrModel.FLICKR_DATA_COMPLETE`
* `MainViewController` 當觀察者，負責觀察 `NSNotificationCenter`的實體 有沒有發送`FlickrModel.FLICKR_DATA_COMPLETE`這個通知。
* `MainViewController` 知道 `NSNotificationCenter` 發送了`FlickrModel.FLICKR_DATA_COMPLETE`這個通知後，開始載入圖片。

再回顧一下，我們使用外觀模式隱藏了下載圖片資料的複雜程度。通知的發送者`NSNotificationCenter`並不在乎圖片是如何從網上下載的。

現在運行一下專案，可以看到基本縮圖已經顯示出來了：

![](/files/-Lejh2bASV1_srz6PNGa)

不過出了問題：這個用來提示加載網絡請求的小菊花怎麼一直在顯示！

我們在`PhotoView`建立實體後，然後開始載入圖片時，開啓了這個白色小菊花，但是在圖片下載完畢的時候我們並沒有停掉它。我們可以在每次下載成功的時候關閉它，但是我們不這樣做，這次我們來用用另一個觀察者模式： `KVO` 。

完成到這一步的Demo：

* 查看原始碼
* [下載ZIP](https://github.com/wildenchen/swift-design-patterns/tree/8774a70f22cd1d2123f7e6c90567c20f7cb7c47b/workspaces/FlickrPhotos-7-Notification.zip)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wilden-chen.gitbook.io/swift-design-patterns/observer/notification.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
