推送對App的重要性不言而喻,是每個iOS開發者必修的技能。網上的資料對於初學者並不友好(至少對於我來講),其中有許多坑。而且因爲要配置證書,只能真機調試等,學起來更是難上加難。這篇文章是從剛開始接觸推送的起點寫起。最近有點忙,有些地方沒有細究,只是暫時知道了能實現什麼,而且貼出了一些文章的連接,方便進一步研究,若是有錯誤的地方請指出來。html
這篇先探究蘋果原生態推送,下篇再寫極光推送和IMiOS —— 極光推送和極光IM。bash
蘋果原生態推送服務器
推送知識點及疑惑的地方微信
推送消息調用方法的時機,以及系統能作到的更多騷擴展。markdown
iOS推送分爲本地推送和遠程推送。app
它們都須要用戶受權。本地不須要聯網和證書,遠程須要聯網和證書。ide
本地推送好比鬧鐘,能夠設置推送時間,是否重複推送,通知內容等。oop
遠程推送,就多坑了。本文主要探討遠程推送。post
在項目 target 中,打開Capabilitie -> Push Notifications,並會自動在項目中生成 .entitlement 文件。打開Capablitie -> Background Modes -> Remote notifications。fetch
iOS8.0以上在AppDelegate.m中
#ifdef NSFoundationVersionNumber_iOS_9_x_Max #import <UserNotifications/UserNotifications.h> #endif 複製代碼
而且遵循協議UNUserNotificationCenterDelegate
(該協議是iOS10.0+用,後面再提到)。
做用:蘋果APNs服務器找到對應的設備和App。
原理:經過設備UDID和App的Bundle Identifier生成deviceToken。這樣APNs服務器接受到信息就知道轉發給哪一個設備的哪一個App。
作法:
在程序首次調用如下方法時,會請求推送權限。
首先用戶要容許App發送通知。而後App發起註冊,最終註冊成功回調方法得到deviceToken。這個過程一般是在程序剛啓動的時候。
注意:發起註冊的方法在iOS8.0後更新了一次,10.0後又更新了一次。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self replyPushNotificationAuthorization:application]; return YES; } - (void)replyPushNotificationAuthorization:(UIApplication *)application{ if (IOS10_OR_LATER) { //iOS 10 later UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; //必須寫代理,否則沒法監聽通知的接收與點擊事件 center.delegate = self; [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) { if (!error && granted) { //用戶點擊容許 NSLog(@"註冊成功"); }else{ //用戶點擊不容許 NSLog(@"註冊失敗"); } }]; // 能夠經過 getNotificationSettingsWithCompletionHandler 獲取權限設置 //以前註冊推送服務,用戶點擊了贊成仍是不一樣意,以及用戶以後又作了怎樣的更改咱們都無從得知,如今 apple 開放了這個 API,咱們能夠直接獲取到用戶的設定信息了。注意UNNotificationSettings是隻讀對象哦,不能直接修改! [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { NSLog(@"========%@",settings); //打印結果 ========<UNNotificationSettings: 0x1740887f0; authorizationStatus: Authorized, notificationCenterSetting: Enabled, soundSetting: Enabled, badgeSetting: Enabled, lockScreenSetting: Enabled, alertSetting: NotSupported, carPlaySetting: Enabled, alertStyle: Banner> }]; }else if (IOS8_OR_LATER){ //iOS 8 - iOS 10系統 UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil]; [application registerUserNotificationSettings:settings]; }else{ //iOS 8.0系統如下 [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound]; } //註冊遠端消息通知獲取device token [application registerForRemoteNotifications]; } 複製代碼
對以上註冊方法說幾句
categories
,這裏爲了方便寫爲nil。其做用是針對推送作拓展功能。這裏給出圖,讓讀者暫時知道有什麼用,還能自定義這個界面,文章後面會再提到。iOS8的方法,能直接輸入。
iOS10的方法,能增長按鈕事件。
iOS10的方法,自定義界面。registerForRemoteNotificationTypes:
,不然不管如何也不能註冊成功。None:不顯示任何東西。
Alert:彈出提示框。
Badge:App小紅點。
Sound:發出聲音或震動。
複製代碼
實測極光推送,Alert< Badge < Sound。即只設置了Badge仍是會默認帶上Alert,設置了Sound會默認帶上Badge和Sound。不過看接口是按位或操做,仍是乖乖寫JPAuthorizationOptionAlert|JPAuthorizationOptionBadge|JPAuthorizationOptionSound
。
註冊結果回調
#pragma mark - 獲取device Token //獲取DeviceToken成功 - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{ //解析NSData獲取字符串 //我看網上這部分直接使用下面方法轉換爲string,你會獲得一個nil(別怪我不告訴你哦) //錯誤寫法 //NSString *string = [[NSString alloc] initWithData:deviceToken encoding:NSUTF8StringEncoding]; //正確寫法 NSString *deviceString = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]; deviceString = [deviceString stringByReplacingOccurrencesOfString:@" " withString:@""]; NSLog(@"deviceToken===========%@",deviceString); } //獲取DeviceToken失敗 - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{ NSLog(@"[DeviceToken Error]:%@\n",error.description); } 複製代碼
獲取deviceToken存在本地,等用戶登陸後,和帳號id一塊兒發送到服務器綁定起來。如總體流程圖那樣。
iOS10.0以前
// 處理本地推送
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
}
複製代碼
調用時機:App處於前臺收到推送;在iOS7後,開啓了 Remote Notification,App處於後臺收到推送。
// 處理遠程消息 // 方法二是在iOS 7以後新增的方法,能夠說是 方法一 的升級版本,若是app最低支持iOS 7的話能夠不用添加 方法一了。 //- (void)application:(UIApplication *)application //didReceiveRemoteNotification:(NSDictionary *)userInfo //{ // NSLog(@"userinfo:%@",userInfo); // NSLog(@"收到推送消息:%@",[[userInfo objectForKey:@"aps"] objectForKey:@"alert"]); //} // 其中completionHandler這個block能夠填寫的參數UIBackgroundFetchResult是一個枚舉值。主要是用來在後臺狀態下進行一些操做的,好比請求數據,操做完成以後,必須通知系統獲取完成,可供選擇的結果有 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { NSLog(@"userinfo:%@",userInfo); NSLog(@"收到推送消息:%@",[[userInfo objectForKey:@"aps"] objectForKey:@"alert"]); completionHandler(UIBackgroundFetchResultNewData); // typedef NS_ENUM(NSUInteger, UIBackgroundFetchResult) { // UIBackgroundFetchResultNewData, // UIBackgroundFetchResultNoData, // UIBackgroundFetchResultFailed // } } 複製代碼
這裏解釋一下userInfo
,詳細點進連接看。
{ "aps" : { "alert" : { "title" : "iOS遠程消息,我是主標題!-title", "subtitle" : "iOS遠程消息,我是主標題!-Subtitle", "body" : "Dely,why am i so handsome -body" }, "badge" : "2" } } 做者:Dely 連接:https://www.jianshu.com/p/c58f8322a278 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。 複製代碼
這裏補充一點東西。
遠程推送通知,分爲 普通推送/後臺推送/靜默推送 3 種類型。
在推送通知-後臺通知/靜默通知文章裏,有深刻介紹。
簡單的說,userInfo
中的aps
中能夠設置一個鍵值對"content-available" : 1
,其表明後臺推送。在後臺推送基礎上不設置badge、sound、aleert的靜默推送能夠靜悄悄讓App更新數據。
這三種類型對應調用的方法會有所不一樣,文末會說。
iOS10.0以後,新增兩個方法。原來的方法仍是須要實現的,各自的調用時機不同。
// App處於前臺接收到通知 - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) { NSLog(@"iOS10 收到遠程通知"); }else { // 判斷爲本地通知 NSLog(@"iOS10 收到本地通知"); } // 在前臺默認不顯示推送,若是要顯示,就要設置如下內容 // 微信設置裏-新消息通知-微信打開時-聲音or振動 就是該原理 // 須要執行這個方法,選擇是否提醒用戶,有Badge、Sound、Alert三種類型能夠設置 completionHandler(UNNotificationPresentationOptionBadge| UNNotificationPresentationOptionSound| UNNotificationPresentationOptionAlert); } 複製代碼
我的以爲這裏iOS 10的方法didRecieve有歧義,實際是點擊推送才調用。
// 點擊通知後會調用 - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler { completionHandler(UIBackgroundFetchResultNewData); NSDictionary *userInfo = response.notification.request.content.userInfo; //程序關閉狀態點擊推送消息打開 能夠在App啓動方法的launchOptions獲知 if (self.isLaunchedByNotification) { //TODO } else{ //前臺運行 if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { //TODO } //後臺掛起時 else{ //TODO } //收到推送消息手機震動,播放音效 AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); AudioServicesPlaySystemSound(1007); } //設置應用程序角標數爲1 [UIApplication sharedApplication].applicationIconBadgeNumber = 1; // 此處必需要執行下行代碼,否則會報錯 completionHandler(); } 複製代碼
這些方法還有坑,筆者放到本文最後的調用時機裏再說。
到這裏已經把整個流程大體描繪出來了。
這裏補充一些知識點,和提出一些疑惑的地方。
補充點小知識點,應該能更好地理解原理。
APNs與設備保持長鏈接。(APNs服務器真猛,信息量這麼大)
基於上一點,不難理解,推送自己是 iOS 系統的行爲。因此在 App 沒有運行(沒有在前臺也沒有在後臺)的時候:
1⃣️仍然可以推送及接收(通知中心通知、頂部橫幅、刷新 App 右上角的小圓點即 badge [如下簡稱角標] 等都會由系統來控制和展現)。
2⃣️(收到推送時,是沒法在 App 的代碼中獲取到通知內容的。由於沙盒機制,此時 App 的任何代碼都不可能被執行。)這個觀點是在另外一篇文章看到的,但筆者實測發現並非這麼回事。處於後臺收到推送是會執行代碼的。
因此就算App關掉後臺應用刷新,照樣能接收到推送。
以微信爲例,若是你打開了後臺應用刷新,那麼當你收到新消息提醒以後,你打開微信,未讀信息已經在那了(若是網速沒問題的話);而當你收到新消息提示,打開微信後,消息纔剛剛開始收取。
iOS的推送,內容的大小最大是4KB。 A發送很長的文字給B,是發到服務器,由服務器暫存數據,通知B來取。B收到推送後去服務器下載。(圖片、語音等相似)
iOS10之後,收到推送能在後臺下載附件,限制:圖片是10M Video是50M。 有興趣的能夠去研究下,www.jianshu.com/p/f5337e8f3…
APNs服務器會向App服務器返回一個發送結果。(雖然這是後臺的事,仍是稍微瞭解下)
怎麼針對特定的人羣發出推送。例如羣聊。
是把羣id發到服務器,而後服務器查出成員各自的id對應的deviceToken發出推送嗎?極光有個根據Tag推送,之後瞭解到會更新文章。
若是該帳號並不在線,那這條消息要怎麼處理?
把消息暫時存在後臺服務器,等待有設備登陸這個帳號後綁定userId和deviceToken後,發起推送。
點擊推送默認只是打開App。但也能增長一些操做,例如跳轉到某個界面、微信直接回復等。
因此最後深刻了解推送還能怎麼玩,以及解釋App在前臺、掛起、關閉狀態下對應的方法。
1.若是App被kill掉的狀況下,點擊了推送,那麼首先要啓動App,會調用- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
。 參數launchOptions
能分辨出是經過點擊App icon仍是點擊推送打開App。
在iOS7.0以前,處於前臺收到推送,調用(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
。
在iOS7.0~10.0-,處於前臺收到推送,調用- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
。
坑了筆者的是,在iOS7.0+(包括10.0後),收到後臺推送或靜默推送,調用- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
。
3.iOS10.0+就功能就豐(皮)富了。
在前臺時收到推送,調用- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
。
因此,App處於前臺時收到後臺推送或靜默推送的話,在iOS10+會調用兩個方法,注意不要重複處理。- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
和- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
。若是你還點擊了推送,那麼還會調用下面這個方法。。。
點擊推送,調用- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler
。
在iOS10後,針對這三個方法,爲了避免重複處理同一條推送的內容。筆者建議儘可能同一模塊的推送,在這各個方法中不要相同邏輯處理。
例如QQ,若是採用後臺或靜默推送,就只在靜默中處理;
不是靜默推送。在前臺收到,就紅點顯示,通知欄不要顯示。若是不在前臺,通知欄顯示,點擊後可跳轉到對應聊天框。
以前上面提過的category
對推送作拓展。由於項目暫時沒有這個需求,而求最近有點忙,就先不鑽研這部分了。找到了一篇好文章,介紹了iOS10的推送按鈕操做。
iOS10.0後能對推送進行查找和刪除 利用場景,例如微信撤回後,本來的推送將被改成"用戶"撤回了一條信息。
// Notification requests that are waiting for their trigger to fire //獲取未送達的全部消息列表 - (void)getPendingNotificationRequestsWithCompletionHandler:(void(^)(NSArray<UNNotificationRequest *> *requests))completionHandler; //刪除全部未送達的特定id的消息 - (void)removePendingNotificationRequestsWithIdentifiers:(NSArray<NSString *> *)identifiers; //刪除全部未送達的消息 - (void)removeAllPendingNotificationRequests; // Notifications that have been delivered and remain in Notification Center. Notifiations triggered by location cannot be retrieved, but can be removed. //獲取已送達的全部消息列表 - (void)getDeliveredNotificationsWithCompletionHandler:(void(^)(NSArray<UNNotification *> *notifications))completionHandler __TVOS_PROHIBITED; //刪除全部已送達的特定id的消息 - (void)removeDeliveredNotificationsWithIdentifiers:(NSArray<NSString *> *)identifiers __TVOS_PROHIBITED; //刪除全部已送達的消息 - (void)removeAllDeliveredNotifications __TVOS_PROHIBITED; 做者:Dely 連接:https://www.jianshu.com/p/c58f8322a278 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。 複製代碼