傳送告白推播的 Push Notification


iOS App 有著各式各樣的功能,但這當中如果要投票選一個讓人又愛又恨的功能,絕對非推播莫屬。

push notifications

如上圖所示,每天我們會收到來自不同 App 的推播訊息。我們恨推播,因為很多都是煩人的廣告訊息;但有時我們卻又愛推播,比方彼得潘想跟溫蒂告白,傳 LINE 訊息問她要不要在一起時,期待馬上就能收到 LINE 的推播,看到溫蒂 Say YES。

不管彼得潘能不能告白成功,為了讓天下有情人都能終成眷屬,接下來彼得潘將教大家實作一個接收播推的 App,相信到時候你用自己親手做的 App 推播告白訊息,對方一定會被你的真心感動的。 (如果對方不回應,你可以每天照三餐推播,總有一天她會理你的。)

Local notification 和 Remote notification

iOS 上的推播,分為 local notification 和 remote notification 兩種。Local notification 和時間或位置有關,需要事先設定,直接由本機端觸發。比方每天下午六點提醒我們該下班的推播,或是靠近 101 Apple Store 出現的 iPhone X 廣告推播。Remote notification 則透過網路傳送到我們的手機,因此它比較彈性,任何時候你心血來潮,都可以發送新的推播訊息。

接下來我們的告白推播將以 remote notification 實作。當我們半夜睡不著覺時,不如就爬到屋頂傳推播給對方,讓對方也睡不著吧。

Remote notification 的傳送對象

Remote notification 傳送的對象,可以分為以下三種:

  • 某個人 – 傳給特定對象,比方使用 FB, LINE 等聊天 App 傳早安訊息給暗戀的女生。

  • 所有人 – 傳給所有安裝了 App 的人,比方誠品 App 在週年慶時傳送推播,提醒大家 Swift 書籍 5 折優惠。

  • 滿足某條件的人 – 傳給部分的人,比方 Sogo 百貨 App 在三月時傳推播給三月生日的壽星,提醒她來店換取生日禮物。

Remote notification 的限制

Remote notification 很強大,不過它並非無所不能,為了避免你期待太高,之後反而失望,最好先了解它的一些限制。

  • 付費成為開發會員才能發送推播 – 推播要透過 Apple 的 server 發送,Apple 當然不會無緣無故幫我們呀,所以請先付錢給他,他才會幫我們發送。

  • 不能在模擬器測試 – 推播只能傳送到實機上,無法發送到模擬器,所以請在 iPhone, iPad 或 iPod Touch 測試。

  • 有可能沒收到推播訊息 – 雖然大部分的時候,我們都能收到推播訊息,但請記得它有可能失敗。比方如果你發送推播時,對方剛好在爬喜馬拉雅山,iPhone 沒網路,那她當然收不到呀。雖然 Apple 的 server 會幫我們保留傳送失敗的推播,之後再重送,但它只會保留一段時間,過期的推播將被可憐地清掉。因此若她不幸在深山裡迷路,一個星期收不到網路,就算她之後找到回家的路,下山有了網路,也收不到之前漏接的推播。

類似郵差寄信的推播傳送過程

在開始實作 remote notification 之前,先讓我們認識一下推播訊息如何經歷千辛萬苦,傳遞到 iOS 裝置上的 App。

push-notification-process

推播傳送的過程,其實跟真實世界的寄信很像。如上圖所示,一開始有個 provider server 負責發送推播,它的角色就像寄件者。一般來說,它會是我們自己的後台,因此我們可從後台的程式控制,自由決定推播的內容以及何時發送,比方過年時發送推播給溫蒂,跟她說恭喜發財,紅包拿來。

但在推播傳送的過程中,最重要的角色卻是 Apple 的 server,APNs (Apple Push Notification Service) Server,因為它就像郵差,只有它知道如何將推播送達 iOS 裝置上的 App。

因此,provider server 會先將推播傳送到 APNs server,然後 APNs server 再將推播傳送到 iOS 裝置,最後 iOS 再將推播傳給接收的 App。對比寄信的情境,iOS 裝置就像屋子的信箱,App 就像收件者。

現在我們了解了推播的流程,但 APNs server 怎麼知道推播要送到地球上哪一個 iOS 裝置 的 App 呢 ? 寄信時我們透過地址指定接收的對象,推播也是一樣的概念,只是它不是房子的地址,而是一個稱為 device token 的字串。之後在實作時,我們將對它有更進一步的認識。

接下來,就讓我們趕緊開始實作推播告白 App,才不會被虎克船長搶先用 Android App 跟溫蒂告白呀。

讓 App 具有接收推播的能力

首先,我們在 App 的 General 頁面將 Team 設為付費帳號。記住,只有付費的開發會員能發送推播 !

App-general

然後,在 App 的 Capabilities 頁面打開 Push Notifications 。

App-capabilities

打開開關後,它會幫我們做以下兩件事:

  1. 以 Xcode 裡設定的 Bundle ID,在 Apple 的開發網站建立 App ID。
  2. App-BundleID

    Apple-AppID

  3. 產生推播需要的 entitlements 檔。
  4. entitlements-file

取得推播權限

不是每個人都喜歡推播,所以我們一定要經過使用者同意後,才能發送推播。

接下來就讓我們在 App 一啟動時,勇敢地詢問她是否願意接收通知吧。

利用 UNUserNotificationCenter 的 current() 取得 UNUserNotificationCenter 物件後,呼叫它的 function requestAuthorization(options:completionHandler:),請求使用者允許 App 傳送通知。另外別忘了要先 import UserNotifications,如此才能順利使用推播的相關型別。

接著讓我們再仔細看一下 requestAuthorization(options:completionHandler:),了解它的參數 options。

在請求使用者同意通知時,我們必須透過參數 options 告知通知會包含哪些東西。Options 的型別為 UNAuthorizationOptions,定義如下:

為了增加告白成功的機率,我們希望通知同時包含浪漫的文字,好聽的聲音,並以 App Icon 上的數字表示這是第幾次告白,因此我們傳入 [.alert, .sound, .badge]。

push-notification-preview

使用者看到的通知請求將如上圖所示,當她經過仔細思量,終於選了允許或不允許後,將觸發 function requestAuthorization 的參數 completionHandler 執行,granted 說明是否允許,允許時值為 true,error 則說明錯誤的原因。

一旦使用者做了選擇,不管允許還是不允許,之後通知請求的訊息都不會再出現,除非她將 App 移除並重新安裝。若她改變心意,比方原本允許,後來發現你是邊緣人,想要封鎖你,也是可以,只是她不能從原來的 App 改變,而要從設定 App 的通知頁面變更。

reset-push-notification

向 Apple 註冊,請求接收推播

App 想要接收推播,原來一點也不簡單,不只要使用者同意,也要 Apple 同意,就好像彼得潘想追到溫蒂,不只要她同意,也要她的父母同意,不然只能私奔到月球了。

如以下程式碼,我們在 App 啟動時呼叫 UIApplication 物件的 function registerForRemoteNotifications, APNs server 註冊,請求接收推播。

一旦註冊成功,protocol UIApplicationDelegate 的 function application(_:didRegisterForRemoteNotificationsWithDeviceToken:) 將被呼叫。我們可在 AppDelegate 裡定義此 function,讀取它宇宙無敵重要的參數 deviceToken。

前面提過推播傳送時,device token 就像地址,有了它才知道通知要送給誰。但是別忘了,通知得先從我們的後台 provider server 發送,provider 就像寄件者,他在發送推播時,就要指定 token,到時候郵差 APNs server 才知道要寄給誰。因此一般來說,我們會在此 function 裡將 token 傳到我們的後台。

但是請注意,剛剛 function 的參數 deviceToken 型別為 Data,我們習慣先將它轉換為字串後再上傳。此字串的格式必須為十六進位,如以下 Apple 文件裡的範例,00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0 代表 token,它將被帶在推播封包裡 HTTP/2 request 的 :path 欄位。

path-device-token

以下為將 deviceToken 轉換為十六進位字串的程式碼。由於 deviceToken 型別為 Data,因此我們用 for in 將每個 byte 的內容當成 UInt8 型別讀取,然後再利用 String(format: “%02x”, byte),將數字變成十六進位的字串後,一個個組合起來。%02x 的 x 代表十六進位,02 表示將以兩個數字表示,若內容不足兩位數,第一位將補 0,比方 a 將變成 0a。

token-byte

如果熟悉 function reduce 的朋友,也可以用以下寫法組合出十六進位的字串。

成功將 token 轉換為字串後,之後即可撰寫上傳的程式碼,將它上傳到我們的後台。對很多 App 來說,token 會對應到某個使用者。比方彼得潘想跟溫蒂和虎克船長告白(虎克船長是備胎),先請他們兩個安裝 App。到時候他們的 App 得到 token,上傳到後台時,後台將記錄 token A 是溫蒂,token B 是虎克船長,如此當彼得潘從後台控制發送推播給溫蒂時,才知道要搭配 token A。

關於 device token,還有以下幾點特別的地方要注意:

  • Token 對應到 iOS 裝置的 App – 同一個 iOS 裝置上,不同的 App 會得到不同的 token。

  • 當使用者不同意接收通知時,App 無法取得 token – 換句話說,function application(_:didRegisterForRemoteNotificationsWithDeviceToken:) 不會被呼叫。

  • Token 可能會變,記得更新 – 就像人會變心一樣,token 也會變,所以我們最好在每次 App 啟動時呼叫 registerForRemoteNotifications,如此一旦 token 改變,觸發 application(_:didRegisterForRemoteNotificationsWithDeviceToken:) 時,我們才能立即上傳後台,確保 App 能收到通知。

  • 跟 APNs server 註冊接收推播失敗時,將觸發以下 function。

現在 App 已經具備接收推播的能力,接下來,就讓我們連到 Apple 開發網站,進行發送推播需要的其它設定。

產生 APNs Key

想要從 provider server 發送推播,我們還需要 APNs key。下載 APNs key 的步驟如下:

  1. 登入 [Apple 開發網站](https://developer.apple.com),點選 Certificates, Identifiers & Profiles
  2. apple-developer-3

  3. 點選 Keys 下的 All 後,點選右上角的 + 新增 key。
  4. apple-developer-2

  5. 輸入 key 的名字,勾選 APNs 後,點擊 Continue 按鈕。
  6. apple-developer-3

    APNs 裡的說明文字提到 One key is used for all of yours apps,因此就算我們有一百個 App 要發送推播,都可以共用同一個 key。除此之外,APNs key 也不會過期,一旦生成,你可以用一萬年!

  7. 點選 Confirm 按鈕完成 key 的新增。
  8. apple-developer-4

  9. 點選 Download 下載 key。
  10. apple-developer-5

    apple-developer-6

下載的 key 將是附檔名 p8 的檔案。請特別注意這段英文文字:After downloading your key, it cannot be re-downloaded as the server copy is removed。此 key 十分珍貴,只能被下載一次,因此下載後請好好保存,如果弄丟只能重新產生新 key。

apple-developer-7

如果將來你不喜歡 key 了,想將它刪除,只要點選想移除的 key 後,按 Revoke 按鈕即可。刪除前請三思,因為一旦刪除,它將不能再拿來發送推播。

apple-developer-8

apple-developer-9

利用 Node.js 程式當 provider server 發送推播

有很多方法可以實現 provider server,你可以用 PHP, Ruby on Rails 等開發 server 程式,或是利用第三方的後台,比方 Firebase。接下來我們將直接在 Mac 上執行 Node.js 程式,實際示範如何發送推播。本部份的重點在測試推播的發送,所以你不需要特別學習 Node.js 的語法,只要依步驟操作修改即可。

為了執行 Node.js 程式,我們的 Mac 必須先安裝 Node.js。如果你在 terminal 輸入指令 node 按 Enter 後,出現 command not found,即表示尚未安裝。

要安裝 Node.js,我們要先連到 Node.js 的網站,點選 macOS Installer 下載 Node.js 的 pkg。

node.js-download

點擊 Node.js 的 pkg 進行安裝。

node.js-download-2

node.js-download-3

安裝成功後,就可以在 terminal 順利輸入 node 指令。

node-terminal

下一步,我們要建立傳送推播的 Node.js 程式資料夾 apns。

然後安裝發送推播的套件,apn。關於套件 apn 的詳細介紹,可參考連結

再將剛剛下載的 key (p8 檔)放到 apns 資料夾下。

apns-file

在 apns 資料夾下撰寫發送推播的 js 檔,設定 key, keyId, teamId。

比方檔案取名為 push.js,輸入以下內容:

Key 填入 p8 檔的檔名,keyId 可從 Apple 的開發網站點擊 key 後查詢。

apple-developer-8

而 teamId 則可在 Apple 開發網站的 Membership 頁面找到。

teamId

看一看剛剛的 js 檔,有個 production 欄位,它代表什麼意思呢 ?

它和 APNs server 有關,APNs server 有兩種,development 和 distribution。Development server 的網址是 api.development.push.apple.com:443,我們平常從 Xcode 開發裝到手機的 App,收到的推播來自 development server。而 distribution server 的網址為 api.push.apple.com:443,我們從 App Store, TestFlight, AdHoc 安裝的 App,則從 distribution server 接收推播。

待會測試時,我們將從 Xcode 安裝 App 到 iPhone,因此我們將 production 設為 false。之後當我們 App 上架,想送推播給下載正式 App 的使用者時,再將 production 設為 true。

也許記憶力好的讀者會想到,剛剛出現過的 entitlements 有個 APS Environment 欄位,內容是 development,應該就代表它可以接收來自 development server 的推播吧。

entitlements-file

沒錯,的確如此,那如果之後上架的 App 要接收來自 production server 的推播,是不是要再手動改成 production 呢 ? 感謝貼心的 Xcode,它幫我們做好了,當我們從 Xcode 的 Archive 製作上架的 App 時,APS Environment 欄位將被自動改成 production。

aps-environment

不僅於此,當我們用 Archive 製作 App Store, TestFlight, AdHoc 版本的 App 時,它還會自動幫我們產生 distribution profile 呢。

distribution-profile

之後,我們要設定 device token 和推播的內容。

在剛剛的 push.js 檔的最後加入以下程式。

前面我們看到 App 如何取得 device token 的十六進位字串。為了測試方便,我們在此直接將 deviceToken 設為 App 取得的 token 字串。

Topic 欄位要輸入 App 的 bundle ID。至於推播的內容,最常設定的是以下三個欄位:

  1. Alert: 推播的文字內容。

  2. Sound: 推播的聲音檔名,default 表示預設的推播聲。沒有指定 sound 的話,將變成無聲推播。如果你覺得自己唱歌很好聽,也可以錄成音檔,以自訂的音檔當推播聲 (不能超過 30 秒)。

  3. Badge: App Icon 上顯示的數字。

最後,我們可以在 terminal 輸入 node push.js 發送推播。

push-notification

一切順利的話,此時溫蒂應可在 iPhone 上看到感人的 I love you。

推播內容的真面目 – JSON Dictionary

推播的內容其實是我們熟悉的 JSON 格式,剛剛例子裡設定的 alert,sound 和 badge,最後將變成 dictionary key aps 下的欄位。

在 aps 下可以設定的欄位不僅於此,比方 content-available 可以實現使用者看不見的背景更新推播,silent notification,讓 App 在背景收到通知後,執行某項工作,比方連到後台抓取新資料。對於推播欄位的好奇寶寶,可進一步參考 Apple 官方文件的說明。

除了 Apple 在 aps 下定義的欄位,我們也可加入自訂的欄位,放在跟 aps 同一層,方便到時候 App 收到推播時,解讀內容做對應的動作。

比方以上例子,link 欄位放上彼得潘網站的網址,到時候使用者點選推播,打開 App 時,即可讓 App 的程式顯示 link 欄位對應的帥氣網頁。

呈現聲音,圖片和影片的 Rich Notification

iOS 的推播不斷進化,目前已可支援 Rich Notification,呈現酷炫的聲音、圖片和影片,不過並非每個格式都支援,下表為它支援的格式和大小限制。

rich-notification

但是聲音、圖片和影片要怎麼包含在推播的 JSON 裡呢 ? 基本上我們不會將它們直接放在 JSON 裡,因為推播的內容有大小限制,一般的上限是 4KB。

我們會在 JSON 裡將 mutable-content 設為 1,然後搭配 Notification Service Extension,讓 App 收到推播時,先在背景修改內容後再顯示,比方解密被加密的推播訊息,或從圖片或影片的網址,下載資料後顯示在推播上。對此部分有興趣的朋友,可參考 WWDC16 的 Advanced NotificationsApple 官方文件

點選推播和收到推播時觸發的 function

最後,我們來聊聊收到推播時,如何讓 App 的程式執行 ? 由於到時候觸發的 function 來自 protocol UNUserNotificationCenterDelegate,所以我們先在 application(_:didFinishLaunchingWithOptions:) 裡加入以下程式,將 AppDelegate 物件設為 UNUserNotificationCenter 的代理人。

接著定義以下 2 個 UNUserNotificationCenterDelegate 的 function。

當使用者點選推播打開 App 時 (App 在背景或是沒被啟動),將觸發 userNotificationCenter(:didReceive:withCompletionHandler:)。而當 App 本來就在前景,收到推播時則會觸發 userNotificationCenter(:willPresent:withCompletionHandler:)。不管是哪一種,我們都可透過參數取得 UNNotificationContent 物件,解析推播的內容。

以上兩個 function,當我們完成想做的事情後,都要呼叫 completionHandler。但在前景收到推播觸發的 userNotificationCenter(_:willPresent:withCompletionHandler:) 比較特別一點,它的 completionHandler 還可以傳入參數,控制是否在前景顯示推播,比方 completionHandler([]) 代表不顯示, completionHandler([.alert]) 則可顯示推播文字。

總結

以上是關於 Apple push notification 的簡單介紹。若能模仿以上步驟操作,應該就能順利開發出接收推播的 App。當然,push notification 還有很多進階的功能,比方客製推播 UI 畫面的 Notification Content Extension、iOS 11 的隱藏推播訊息 (hidden notification content) 等,有興趣的朋友,都可再進一步研究相關文件。

關於 Swift iOS App 開發的相關技術,大家若有任何問題,都可在底下留言,或是直接 FB / LINE 聯絡彼得潘。當彼得潘回答大家的問題時,其實也在找答案的過程中精進學習,增長了自己的功力,和大家交了朋友,獲得再多錢也買不到的回報和收獲。


彼得潘,正職作家,副業講師,深愛 Apple 相關的所有人事物。精通 Swift iOS 程式設計,平日的興趣為桌球,情歌和寫作。除了一天一顆蘋果強身,也努力保持一天研究一項 iOS SDK 技術的習慣。著作: Swift程式設計入門,App 程式設計入門-iPhone,iPad 課程: 彼得潘的 iOS App 程式設計入門,文組生的 iOS App 程式設計入門。Line ID: deeplovepeterpan

blog comments powered by Disqus
訂閲電子報

訂閲電子報

AppCoda致力於發佈優質iOS程式教學,你不必每天上站,輸入你的電子郵件地址訂閱網站的最新教學文章。每當有新文章發佈,我們會使用電子郵件通知你。

已收你的指示。請你檢查你的電郵,我們已寄出一封認證信,點擊信中鏈結才算完成訂閱。

Shares
Share This