iOS 開發中,怎樣用好 Notifications?

故胤道長發表於2017-04-11
111721232-ea70b56ad9049d5c

前言

在 iOS 開發中,有這樣一個場景:某件重要的事情必須立刻讓使用者知道,甚至不惜以打斷使用者當前操作為代價來強調這份重要性。這就是通知(Notifiations)。目前常用的框架為 UserNotifications,它主要用來在鎖屏和應用介面通過彈窗來顯示通知。另一個框架是 Notification Center ,以它實現的跨 object 通知以及原生的 KVO(Key-Value-Observing) 是 iOS 中觀察者模式的主要實現手段。

本文內容:

  • UserNotifications 介紹
  • 本地通知(Local Notifications)
  • 遠端通知(Remote Notifications)
  • 觀察者模式(Observer Pattern)

UserNotifications 介紹

UserNotifications 是 iOS 10 剛剛引入的全新框架。與以往版本的本地通知和遠端通知分別處理不同,這次蘋果把兩者的 API 統一。從此以後,無論處理本地通知還是遠端通知,都是用 UserNotifications 框架

UserNotifications 的流程也十分簡單,主要分以下 4 步:

121721232-9e3c071c1c92c173
UserNotifications 流程
  • 註冊

通過呼叫 requestAuthorization 這個方法,通知中心會向使用者傳送通知許可請求。在彈出的 Alert 中點選同意,即可完成註冊。

  • 建立

如果是本地推送,則在 AppDelegate 中設定推送引數;如果是遠端推送,則無需設定引數,推送的內容和觸發時間都在遠端伺服器端配置。

  • 推送

這一步就是系統或者遠端伺服器推送通知。伴隨著一聲清脆的響聲(或自定義的聲音),通知對應的UI顯示到手機介面的過程。

  • 響應

當使用者看到通知後,點選進去會有相應的響應選項。如下圖:

131721232-e4494da0aceb4f06

例如 Instagram 這個 App ,使用者看到它的通知後有3個選項:一是 Like , 點選之後就是給你朋友的照片點贊;另一個是 Quick Reply,點選之後可以評論照片;最後是 View Post,點選之後是進入 Instagram 主 App 進行照片瀏覽。使用者不同的選擇決定了之後的操作,筆者稱這個過程是對 Notification 的響應

本地通知

因為通知是針對整個 App 級別的功能,所以一般在 AppDelegate 中完成註冊和建立的過程。程式碼如下:

在建立過程中,有以下幾點值得注意:

  • 觸發機制。如果是時間觸發,就用 UNCalendarNotificationTrigger;如果是地點觸發,就用 UNLocationNotificationTrigger。
  • 通知內容。除了標題(title)、內容(body)、聲音(sound)外,還可以新增副標題(subTitle)甚至是圖片。新增圖片的示例程式碼如下:

  • Identifier。一個 App 可能有多種本地通知,它們之間是通過 Identifier 進行區分的。
  • 將建立好的通知傳入通知中心。多個 Notifications 之間有先後順序,它們排成佇列在通知中心中。這裡我們為了方便演示,刪除了以前所有的通知。

完成了註冊和建立,我們只要在合適的時間讓系統推送通知即可。程式碼中表現為在某個時間點呼叫scheduleNotification(date)。之後我們就可以看到相應的通知彈出:

一般情況下使用者會點選通知直接進入 App 檢視。假如要實現在通知出現時快速操作,比如過10分鐘再提醒我這樣的選項,我們又該怎麼做呢?這時候我們引入UNNotificationActionUNNotificationCategory

  • UNNotificationAction: 響應通知的單個具體操作。例如直接給相關推送資訊點贊。
  • UNNotificationCategory: 響應操作對應的類別。相當於是多個 UNNotificationAction 構成的群組,表明一類響應操作。

下面一段程式碼就是創立了一個 “Remind me later” 的 UNNotificationAction 響應操作,並將其加入到 “normal” 的
UNNotificationCategory 類別之中。

有了上面程式碼,當使用者點選通知,我們就能看到相應的快捷操作。那麼使用者點選 “Remind me later” ,我們該如何在 App 中設定對應的操作,讓系統在10分鐘後再次推送響應通知呢?

141721232-d1e814df70516eac

很簡單,我們只要在UNUserNotificationCenterDelegate 協議中實現userNotificationCenter(_:didReceive:withCompletionHandler:)
。當使用者點選通知選項時,這個方法自動被呼叫。這裡我們通過 identifier 來判斷具體是哪一個選項被點選,再呼叫對應響應方法即可。

遠端通知

再接觸遠端程式碼的具體實現之前,我們先來看看遠端通知的原理:

151721232-fe84455f9c02782f
遠端通知
  1. App 向 iOS 系統申請推送許可權
  2. iOS 系統向 APNs(Apple Push Notification Service) 請求手機 device token,並告訴 App,能接受推送的通知。
  3. App 將手機的 device token 傳給後端
  4. 後端向 APNs 推送通知
  5. APNs 將響應通知推送給響應手機

從以上流程我們可以看出,APNs 在這裡啟動了監管者和託管者的作用,無論是請求還是推送都要經過 APNs。也就是說,所有的推送都必須按照 APNs 的遊戲規則來。

有人到這裡要問了,所有推送都指望 APNs,那流量那麼大,APNs 崩了怎麼辦?

這確實是這個系統的一個弊端,就是耦合度太高,過於指望 APNs 很容易造成單點故障。所以,蘋果在 iOS 10 以前,對於遠端通知的內容,做了以下限制:

In iOS 8 and later, the maximum size allowed for a notification payload is 2 kilobytes; Apple Push Notification service refuses any notification that exceeds this limit. (Prior to iOS 8 and in OS X, the maximum payload size is 256 bytes.)

就是說,最多傳 2 KB 通知。這樣即使 1 秒鐘內有 100 萬個遠端推送同時發生,也就 2 GB。這對於一個大公司來說毫無壓力。

後來在 iOS 10 中,蘋果引入了 Notification Content Extension 和 Notification Service Extension,這時候就可以修改原來的 notification 內容了,比如新增多媒體檔案之類。講這兩個 extension 的文章太多,筆者這裡不作贅述,只提供以下原理圖一張。

161721232-835eb22081fa6716

下面我們來看下具體怎麼實現。遠端推送與本地推送不同在於,在註冊通知前,先要設定 App 使其允許遠端通知。具體做法就是去 App Settings -> Capabilities -> Push Notifications,開啟 Push Notificaitons。

171721232-be1ea923ee11784a

接著就是老步驟註冊。注意不同的是這次要說明是遠端通知。程式碼如下:

遠端通知的內容由遠端伺服器決定,本地無需建立。伺服器端需要以下幾個關鍵資料來確認對指定的手機進行推送:

  • Device Token: APNs 用來確認究竟是哪臺機器,哪個 App的引數。它可以通過以下程式碼獲取。

開發 App 的正確做法是把 Device Token 傳送到伺服器端,這裡為了演示方便,就直接列印出來了。Device Token 大概長下面這樣:

5311839E985FA01B56E7AD74334C0137F7D6AF71A22745D0FB50DED665E0E882

  • Key ID: 後臺伺服器傳送通知時, APNs 對其的認證號碼。它需要你去開發者中心註冊 APNs Auth Key。它會產生一個 .p8 檔案,Key ID 就在其中。
181721232-dea80f93be10e2b6
APNs Auth Key
  • Team ID: 你 Apple ID 對應的號碼。可以在 App Settings -> Bundle Identifier 裡找到。

這樣伺服器就可以向你的手機傳送通知了。加入響應操作,同樣是藉助UNNotificationActionUNNotificationCategory ,並呼叫userNotificationCenter(_:didReceive:withCompletionHandler:),與本地推送的響應處理是一模一樣的。

觀察者模式

觀察者模式是設計模式中的一種,就是說一個物件當自身某些狀態發生變化的時候,自身發生相應操作或通知給另一個物件。物件之間無需有直接或間接的關係。這種設計模式的最大的好處是在於解耦。因為兩個物件可以分別單獨設計,只需在特定情況下通知對方即可。

下面請看一道面試題:請自行設計 Swift 的 Notification API,使其能夠實現 iOS 中的觀察者模式。

拿到這道題目,我們首先要分析 Notification API 對於觀察者模型的使用場景,無非就是兩種:跨 object 通知,以及 KVO(Key-Value-Observing)。

跨 object 通知以及 NotificationCenter 設計

首先我們來看跨 object 通知。一個最簡單的應用場景,當一個 ViewController 初始化時,它要通知 Network 部分去下載相應的圖片以填充對應的 UIImageView。所以流程如下:

  1. Network 註冊觀察 ViewController 初始化行為
  2. ViewController 發生初始化行為,併發出相應通知
  3. Network 得到通知,觀察到 ViewController 行為的發生
  4. Network 根據通知,呼叫 downloadImage 方法

根據以上流程,我們發現這種邏輯是 objects 之間的訊號傳遞和接收過程。比較好的設計方法是單獨設計一個 Notification 類別,它相當於是一個通知排程中心,處理任意 objects 之間的通知,而不影響 objects 本身的其他操作。所以我們設計出了 NotificationCenter 這個類別,它有這兩個操作:

由於是跨 object 之間的通知,所以可知此類通知具有一般性,故而 NotificationCenter 設計為單例比較好:

最後還要注意一個問題,就是當觀察者被回收的時候,我們一定要撤銷觀察,否則會發生通知發向一個 nil 類的情況,導致 App 崩潰。於是我們這樣設計:

然後將它新增在類 deinit 中:

貌似我們已經設計好了針對跨 object 的最簡單 API。對照一下 Apple 官方的 NotificationCenter API,發現確實也是這個思路。不過他們設計的更全面可靠,這裡大家可以自行比較。

KVO

我們來看第二個情況,就是 KVO — 鍵值觀察。

顧名思義,鍵值觀察就是說當某個屬性發生變化,其對應的值也發生變化。它一般用於單個 object 內部的情況。舉個具體的例子,ViewController 一開始 UIImageView 沒有圖片的時候,我們用 activityIndicator 顯示載入狀態,當 Network 下載好圖片並給 UIImageView 賦值之後,我們停止 activityIndicator 的載入狀態。也就是說我們觀察 image 這個屬性,當它由 nil 變成非 nil 時,程式作出關閉 activityIndicator 動畫的相應操作

191721232-f9b60b6cde487d06

所以基本流程如下:

  1. ViewController 給 UIImageView 新增 activityIndicator,啟動動畫效果
  2. ViewController 觀察 UIImageView 的 image 屬性
  3. ViewController 通過上面提到的跨 object 通知,從 Network 裡下載 image,並給 UIImageView 賦值
  4. ViewController 觀察到 UIImageView 的 image 屬性已經被賦值,所以啟動相應方法,關閉 activityIndicator 的動畫

這裡我們可以看出來,這是針對單個 object 的某個屬性變化而設計出來的通知框架。所以我們不妨用 extension 的形式對 NSObject 新增通知方法。

同是不要忘記 deinit 的時候 removeObserver,防止 App 崩潰。對比 Apple 官方的 addObserver APIobserveValue API,我們發現蘋果還引入了一個引數context來更加靈活的處理通知觀察機制。你可以定義不同的 context 並根據這些 context 來對屬性變化做出處理。比如下面這樣:

總結

iOS 10中蘋果的本地推送和遠端推送 API 達到了高度統一,都使用 UserNotifications 這個框架來實現,學習曲線大幅下降。功能也得到了大幅度擴充套件,多媒體檔案新增、擴充套件包、分類別響應、3D Touch 都使得推送功能更加靈活。

至於蘋果自己設計的 KVO 和 NotificationCenter 機制,筆者認為有很大的侷限性。因為對應的通知和相應程式碼段之間有一定距離,程式碼量很大的時候非常容易找不到對應的相應。同時這種觀察者模式又難以測試,程式碼維護和質量很難得到保證。正是因為這些原因,響應式程式設計才日漸興起,大家不妨去看看 RxSwift 和 ReactCocoa,其對應的 MVVM 架構也在系統解耦上要優於原生的 MVC。

參考

Introduction to User Notifications Framework in iOS 10
Push Notifications Tutorial: Getting Started
Send Push Notifications to iOS Devices using Xcode 8 and Swift 3

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

iOS 開發中,怎樣用好 Notifications? iOS 開發中,怎樣用好 Notifications?

相關文章