iOS 推送(蘋果原生態)

前言

推送對App的重要性不言而喻,是每個iOS開發者必修的技能。網上的資料對於初學者並不友好(至少對於我來講),其中有許多坑。而且因爲要配置證書,只能真機調試等,學起來更是難上加難。這篇文章是從剛開始接觸推送的起點寫起。最近有點忙,有些地方沒有細究,只是暫時知道了能實現什麼,而且貼出了一些文章的連接,方便進一步研究,若是有錯誤的地方請指出來。html

image

這篇先探究蘋果原生態推送,下篇再寫極光推送和IMiOS —— 極光推送和極光IMbash

目錄

  1. 蘋果原生態推送服務器

  2. 推送知識點及疑惑的地方微信

  3. 推送消息調用方法的時機,以及系統能作到的更多騷擴展。markdown

蘋果原生態推送

  • iOS推送分爲本地推送和遠程推送。app

    它們都須要用戶受權。本地不須要聯網和證書,遠程須要聯網和證書。ide

    本地推送好比鬧鐘,能夠設置推送時間,是否重複推送,通知內容等。oop

    遠程推送,就多坑了。本文主要探討遠程推送。post

遠程推送總體流程

image


實現步驟

  1. 在項目 target 中,打開Capabilitie -> Push Notifications,並會自動在項目中生成 .entitlement 文件。打開Capablitie -> Background Modes -> Remote notifications。fetch

  2. iOS8.0以上在AppDelegate.m中

#ifdef NSFoundationVersionNumber_iOS_9_x_Max
#import <UserNotifications/UserNotifications.h>
#endif
複製代碼

而且遵循協議UNUserNotificationCenterDelegate(該協議是iOS10.0+用,後面再提到)。

  1. 向APNs服務器註冊deviceToken
  • 做用:蘋果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];

}
    
複製代碼

對以上註冊方法說幾句

  • iOS8.0和iOS10.0方法都有個categories,這裏爲了方便寫爲nil。其做用是針對推送作拓展功能。這裏給出圖,讓讀者暫時知道有什麼用,還能自定義這個界面,文章後面會再提到。

iOS8的方法,能直接輸入。

image

iOS10的方法,能增長按鈕事件。

image
iOS10的方法,自定義界面。
image

  • iOS8.0以上的設備不能用registerForRemoteNotificationTypes:,不然不管如何也不能註冊成功。
  • 參數枚舉,表示收到推送後要如何顯示。
None:不顯示任何東西。
Alert:彈出提示框。
Badge:App小紅點。
Sound:發出聲音或震動。
複製代碼

實測極光推送,Alert< Badge < Sound。即只設置了Badge仍是會默認帶上Alert,設置了Sound會默認帶上Badge和Sound。不過看接口是按位或操做,仍是乖乖寫JPAuthorizationOptionAlert|JPAuthorizationOptionBadge|JPAuthorizationOptionSound

image


註冊結果回調

#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一塊兒發送到服務器綁定起來。如總體流程圖那樣。

  1. 收到推送後回調方法

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
來源:簡書
簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
複製代碼

image


這裏補充一點東西。

遠程推送通知,分爲 普通推送/後臺推送/靜默推送 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在前臺、掛起、關閉狀態下對應的方法。

推送消息的方法調用時機以及特殊處理

  • 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
來源:簡書
簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
複製代碼

參考

相關文章
相關標籤/搜索