iOS 本地通知和遠程通知

一:用戶通知簡介

用戶通知是什麼

iOS中存在三種常見的事件通知方式:NSNofiticationCenter、KVO Notification 和 User Notifications,其中 User Notifications,就是本文將要探討的用戶通知。php

咱們都知道 iOS 系統常常的有一些與 App 相關的通知欄消息,這些消息每每伴隨着提示音以及 App 的桌面圖標右上角的未讀消息提示,這些通知就是 iOS 的用戶通知。程序員

用戶通知的分類

用戶通知分爲兩類:本地通知和遠程通知,其中遠程通知又稱爲推送通知。json

二者最主要的區別是:本地通知是由 App 發送到當前設備上,不須要網絡支持;而遠程通知是由 App 的服務器發送到蘋果的 APNs 服務器,並由 APNs 服務器轉發到相應設備(由 App 服務器指定接收通知的設備)。緩存

二者最主要的共同點是:本地通知和遠程通知對用戶的表現形式是相同的,二者都可以採用通知欄消息、App 桌面圖標右上角角標和提示音的方式通知用戶。服務器

用戶通知有什麼用處

及時有效的(不管是在前臺仍是後臺)向用戶發送消息(聊天信息、新聞、待辦事項、天氣變化等)是用戶通知最大的優點。網絡

此外,有效合理的使用用戶通知,可讓咱們的 App 有更好的體驗,如:併發

  • 當待辦事項將要過時時能夠及時提醒用戶;
  • 當用戶執行下載大文件任務時進入後臺,當下載完成後能夠通知用戶;
  • 當用戶環球旅行時,能夠根據用戶的地理位置推送天氣變化等信息;
  • 當用戶訂閱的某雜誌或新聞主題有更新時,通知用戶;
  • ……

本文後續內容將以應用開發者的角度對用戶通知進行深刻的探討,本文討論內容針對iOS7/8/9,有關 iOS10 系統的用戶通知會另作講解。app

本文中的遠程通知使用了 Simplepush.php ,內部代碼很簡單,可以使用該腳本自定義遠程通知的內容,ide

本文主要參考了蘋果官方的 Local and Remote Notification Programming Guide 以及本文用到的接口的官方文檔。測試

二:本地通知的使用

開啓本地通知功能

  • 對於 iOS7,若是用戶沒有在系統設置裏關閉該 App 的通知功能,那麼開發者無需作任何操做便可使用本地通知功能。

  • 對於 iOS8 及之後的系統,若須要使用本地通知功能,則須要註冊通知類型。

    通知類型有四種:角標(UIUserNotificationTypeBadge)、提示音(UIUserNotificationTypeSound)、提示信息(UIUserNotificationTypeAlert)和無任何通知(UIUserNotificationTypeNone)。

    你能夠註冊上訴四種通知類型的任意組合,但最終可用的通知形式須要根據用戶對此 App 通知的設置肯定。好比:App 內部註冊了角標、提示音和提示信息,可是用戶關閉了聲音通知,那麼收到本地通知時是不會有提示音的。

    對於 iOS8 及之後的系統,註冊本地通知的代碼示例以下:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
            // 只有 iOS8 and later 才須要
            if ([[UIApplication sharedApplication]     respondsToSelector:@selector(registerForRemoteNotifications)]) {
            // 這裏 types 能夠自定義,若是 types 爲 0,那麼全部的用戶通知均會靜默的接收,系統不會給用戶任何提示(固然,App 能夠本身處理並給出提示)
            UIUserNotificationType types = (UIUserNotificationType) (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert);
            // 這裏 categories 可暫不深刻,本文後面會詳細講解。
            UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
            // 當應用安裝後第一次調用該方法時,系統會彈窗提示用戶是否容許接收通知
            [[UIApplication sharedApplication] registerUserNotificationSettings:mySettings];
        }
    
        // Your own other codes.
        return YES;
    }

當系統彈窗提示用戶是否容許接收通知後,用戶可能會拒絕;咱們能夠在 AppDelegate 的 application:didRegisterUserNotificationSettings:

方法中用來查看註冊成功的通知類型,咱們能夠在拿到註冊結果後作自定義操做(好比失敗時彈個窗提示用戶當前沒法使用用戶通知)。

蘋果推薦在以後發送的本地通知時,要避免使用沒有註冊成功的通知類型(並非強制要求)。

- (void)application: (UIApplication*)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
    if (notificationSettings.types & UIUserNotificationTypeBadge) {
        NSLog(@"Badge Nofitication type is allowed");
    }
    if (notificationSettings.types & UIUserNotificationTypeAlert) {
        NSLog(@"Alert Notfication type is allowed");
    }
    if (notificationSettings.types & UIUserNotificationTypeSound) {
        NSLog(@"Sound Notfication type is allowed");
    }
}

發送本地通知

發送一個本地通知主要有以下步驟:

  1. 首先要按照上述 "開啓本地通知功能" 步驟註冊通知類型;
  2. 建立一個 UILocalNotification 對象;
  3. 設置 UILocalNotification 對象的 fireDate 屬性,該屬性表示什麼時間點發送這條本地通知;
    同時能夠設置 timeZone 屬性表示時區,設置 timeZone 後,當用戶跨越時區時,fireDate 會按照時區被調整(相似於鐘錶調整);
    此外,可使用 repeatInterval 和 repeatCalendar 來設置週期性的通知。
  4. 設置通知的提示信息: 
    • 設置 alertTitle 做爲通知的概要,設置 alertBody 做爲通知的具體信息;注意這裏強烈建議使用本地化的字符串,即 NSLocalizedString(@"This is alert body", nil); 。
      注意 alertTitle 屬性只適用於 iOS8.2 及之後的系統
    • 設置 applicationIconBadgeNumber 用於展現 App 桌面圖標的右上角角標;
    • 設置 soundName, 咱們通常設置爲 UILocalNotificationDefaultSoundName;使用自定義 sound 在後面會進一步講解;
    • 在設置提醒方式的值時,對於 iOS8 及之後的系統,能夠檢查下當前提醒方式是否已經註冊成功(能夠用 [[UIApplication sharedApplication] currentUserNotificationSettings] 獲取註冊成功的通知類型)。
  5. 能夠選擇設置 userInfo 屬性,該屬性通常能夠存放業務有關的信息(如 ID 等),這樣收到通知後能夠方便處理業務相關邏輯;
  6. 將上面建立的 UILocalnotification 放入通知隊列中:使用方法 scheduleLocalNotification: 會按照 UILocalnotification 中的 fireDate 進行通知的發送,
    而使用 presentLocalNotificationNow: 會當即發送該本地通知。

下面給出一段示例代碼:

- (void)scheduleLocalNotification {
    NSDate *itemDate = [NSDate date];

    UILocalNotification *localNotif = [[UILocalNotification alloc] init];
    if (localNotif == nil)
        return;
    localNotif.fireDate = [itemDate dateByAddingTimeInterval:10];
    localNotif.timeZone = [NSTimeZone defaultTimeZone];

    localNotif.alertBody = [NSString stringWithFormat:NSLocalizedString(@"%@ after %i seconds scheduled.", nil), @"本地通知", 10];

    localNotif.alertTitle = NSLocalizedString(@"Local Notification Title", nil);

    localNotif.soundName = UILocalNotificationDefaultSoundName;
    localNotif.applicationIconBadgeNumber = 1;

    NSDictionary *infoDict = [NSDictionary dictionaryWithObject:@"ID:10" forKey:@"LocalNotification"];
    localNotif.userInfo = infoDict;

    [[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
}

處理收到的本地通知

這裏分三種狀況討論如何處理本地通知:

應用處於前臺

應用處於前臺時,本地通知到達時,不會有提示音、通知欄橫幅提示,可是 App 桌面圖標的右上角角標是有數值顯示的,因此即便在前臺,咱們也應該對角標數量作處理

此時,咱們能夠在 application:didReceiveLocalNotification: 方法中獲取到本地通知,示例代碼以下:

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
    NSString *itemName = [notification.userInfo objectForKey:@"LocalNotification"];
    [self.windowRootController displayNotification:[NSString stringWithFormat:@"%@ receive from didReceiveLocalNotificaition", itemName]];
    // 這裏將角標數量減一,注意系統不會幫助咱們處理角標數量
    application.applicationIconBadgeNumber -= 1;
}

應用處於後臺

當應用處於後臺時,本地通知到達時,會根據本地通知設置的通知類型以及用戶設置的通知類型進行提示,例如鎖屏界面通知、通知欄通知、聲音、角標。

此時若是滑動鎖屏界面通知或點擊通知欄通知,則會切換應用到前臺,咱們可使用與應用處於前臺時相同的獲取通知的方式。

可是若是咱們點擊 App 桌面圖標,則沒法獲取到用戶通知,此時通知欄消息仍然會存在。此外,角標也不會變化,若是但願修改角標,則須要 App 進入前臺後將其修改。

應用沒有運行

若是應用沒有運行,當本地通知到達時,會根據本地通知設置的通知類型以及用戶設置的通知類型進行提示,例如鎖屏界面通知、通知欄通知、聲音、角標。

此時若是滑動鎖屏界面通知或點擊通知欄通知,則會打開應用,但這時咱們獲取通知的方式與前面有所不一樣,經過 application:didReceiveLocalNotification: 是沒法獲取通知的。

這種狀況咱們須要經過 application:didFinishLaunchingWithOptions: 中的 LaunchOptions 獲取通知,示例代碼以下:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
    if (localNotif) {
        NSString *itemName = [localNotif.userInfo objectForKey:@"LocalNotification"];
        [self.windowRootController displayNotification:[NSString stringWithFormat:@"%@ receive from didFinishLaunch", itemName]];  // custom method
        [UIApplication sharedApplication].applicationIconBadgeNumber = localNotif.applicationIconBadgeNumber-1;
    }

    // Your own other codes.
    return YES;
}

一樣的,可是若是咱們點擊 App 桌面圖標,則沒法獲取到用戶通知,此時通知欄消息仍然會存在。此外,角標也不會變化,若是但願修改角標,則須要 App 進入前臺後將其修改。

地理位置相關的本地通知

在 iOS8 及之後系統中,咱們能夠定義一個與地理位置有關的本地通知,這樣當咱們跨過設定的地理區域時,系統會發送本地通知。

註冊位置相關的本地通知

  1. 須要建立一個 CLLocationManager 對象,併爲其設置一個 delegate;
  2. 請求用戶容許使用定位服務:調用 CLLocationManager 的 =requestWhenInUseAuthorization=,

    注意工程的 plist 中須要配置 NSLocationWhenInUseUsageDescription 選項,不然定位服務沒法正常啓用;示例代碼以下:

    - (void)registerLocationBasedNotification {
        CLLocationManager *locationManager = [[CLLocationManager alloc] init];
        locationManager.delegate = self;
        // 申請定位權限
        [locationManager requestWhenInUseAuthorization];
    }
  3. 經過 CLLocationManagerDelegate 回調檢查用戶是否容許使用定位服務,若是容許了服務,那麼能夠發送一個位置相關的本地通知。

    - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
        // 由於上面咱們是使用了 requestWhenInUseAuthorization,因此這裏檢查的是 kCLAuthorizationStatusAuthorizedWhenInUse
        if (status == kCLAuthorizationStatusAuthorizedWhenInUse) {
        [self scheduleLocalBasedNotification];
        }
    }
  4. 建立一個位置相關的本地通知,並將其交由系統處理。

    - (void)scheduleLocalBasedNotification {
        UILocalNotification *locationNotification = [[UILocalNotification alloc] init];
        locationNotification.alertBody = @"到達xxx";
        locationNotification.regionTriggersOnce = NO; // 表示每次跨越指定區域就會發送通知
        locationNotification.region = [[CLCircularRegion alloc] initWithCenter:LOC_COORDINATE radius:LOC_RADIUS identifier:LOC_IDENTIFIER];
    
        [[UIApplication sharedApplication] scheduleLocalNotification:locNotification];
    }

處理位置相關的本地通知

與上面講過的 「處理收到的本地通知」 比較,這裏能夠在通知裏獲取到 region,而後能夠作自定義操做,其他全部操做均與 「處理收到的本地通知」 一致。

注意若是用戶沒有容許使用定位權限,則沒法收到位置相關的本地通知。

三:遠程通知的使用

APNs 簡介

APNs 是蘋果提供的遠程通知的服務器,當 App 處於後臺或者沒有運行時,若是 App 的服務器(以後咱們稱爲 Provider)須要發送通知信息給客戶端,則須要藉助於 APNs 服務器。

使用 APNs 服務時,遠程通知的路由路徑爲: Provider –> 蘋果的 APNs 服務器 –> 手機設備 –> App。

在這個路徑中,Provider 與 APNs 服務器之間有一個 TLS 鏈接,Provider 經過這個鏈接將遠程通知推送到蘋果的 APNs 服務器;

手機設備與 APNs 服務器之間也會有一個 TLS 鏈接,全部發往手機設備的 APNs 遠程通知都是使用這一個 TLS鏈接,而後由設備區分遠程通知所屬的 App,進而通知給用戶某應用有遠程通知。

下面簡單介紹下這個流程:

設備 與 APNs

設備與 APNs 創建鏈接的過程如圖:

須要明確的要點:

  1. 此鏈接由系統創建並維持,無需開發人員管理;
  2. 上圖中的證書是蘋果設備自己的證書,與開發者帳號中申請的證書無關;
  3. 每一個設備與 APNs 服務器只需維持一條鏈接。

Provider 與 APNs

Provider 與 APNs 創建鏈接的過程如圖:

須要明確的要點:

  1. 此鏈接由 App 的 bundle ID 惟一肯定;
  2. 上圖中 Provider certificate 須要經過開發者帳號申請生成,其中包含 App 的 bundle ID。

APNs 工做的流程

  1. 首先客戶端須要向 APNs 服務器註冊當前 App,APNs 會返回一個 Token(注意這個過程要求 App 有合法的證書,有關證書這裏不作詳細描述);注意不一樣應用在同一設備上獲取的 Token 不一樣,同一應用在不一樣設備上獲取的 Token也不一樣,因此 Token 是跟設備與 App 惟一綁定的;
  2. App 拿到 Token 後須要將其發送給 Provider;
  3. Provider 發送推送通知時,指定 Token 和通知內容,併發送給 APNs 服務器;
  4. APNs 服務器會將通知發送給 Token 對應的設備上;
  5. 設備收到通知後,根據 APNs 發過來的通知中帶有的 bundleID 信息區分是哪一個App的遠程通知(這裏應該是根據 Token 來獲取 bundleID)。

Feedback 機制

Feedback 是 APNs 服務器提供的用於減小服務器壓力以及優化網絡的服務,基本的工做流程以下圖:

  1. Provider 發送一個遠程通知給 APNs 服務器,APNs 服務器會檢測目的設備是否在線,若是不在線,那麼 APNs 服務器會暫存該消息;
  2. 當目的設備上線後,APNs 會發送暫存的消息給目的設備(按照蘋果官方說法暫存消息只會暫存最後一條消息,以前的消息會被丟棄);
  3. 若是目的設備好久都沒有上線,那麼 APNs 消息會把該設備加入 feedback 名單。Provider 能夠按期去 APNs 拉新 feedback 名單;
  4. 當 Provider 再次給以前的設備發送遠程通知時,須要檢查一下 feedback 名單,若是設備在這個名單,則再也不發送給 APNs 了;
  5. 當設備從新上線後,Provider 能夠再將此設備移除 feedback 名單,當 Provider 更新 feedback list 後,就能夠從新給該設備發送遠程通知了。固然,feedback list 的更新可能會有周期,若是須要及時有效的更新 feedback list,那麼須要 App 打開後,及時通知 Provider;
  6. 這種機制的好處就是防止發送多餘無用的遠程通知消息,一方面能夠減緩 APNs 服務器的壓力,另外一方面也能夠減小網絡流量;

開啓遠程通知功能

註冊通知類型

  • 對於 iOS7,無需此步驟;
  • 對於 iOS8 及之後的系統,若須要使用遠程通知功能,則須要註冊通知類型。步驟與 "本地通知的使用" 中 "開啓本地通知功能" 是徹底相同的,此處再也不重複。

註冊遠程通知

基本流程爲:

  1. 註冊通知類型,上一小節已經作了介紹;
  2. 使用 registerForRemoteNotifications 註冊遠程通知(對於 iOS7 使用 registerForRemoteNotificationTypes: );
  3. 使用 application:didRegisterForRemoteNotificationsWithDeviceToken: 接收 APNs 返回的 Token,
    使用 application:didFailToRegisterForRemoteNotificationsWithError: 處理註冊錯誤;
  4. 若是上一步驟中註冊成功了,那麼將獲得的 Token 發送給 Provider。

注意:

  1. 目前看來,對於 iOS9,每次從新安裝應用後獲得的 Token 是不同的,並且每次重裝系統也會改變,因此 每次應用啓動時都須要按上面的步驟註冊一次 ;
  2. 不要將以前的 Token 緩存,當須要將 Token 傳送到 Provider 時,必定要使用 registerForRemoteNotifications 獲取,並使用回調處理註冊結果;
    當應用註冊過通知,並且 Token 沒有改變時,系統會當即返回結果,不會去 APNs 請求。
    這裏猜想系統幫助將 Token 緩存下來,且與應用的狀態進行了關聯,若是應用當前狀態沒有改變,那麼會當即將系統存下的 Token 返回。
    爲了證實這點,能夠將網絡關閉進行測試,若是 App 沒有卸載,也是能夠獲取到 Token 的;
  3. 必定要有開啓了 Push 功能的證書,才能正常使用遠程推送。

註冊遠程通知的示例代碼以下:

- (void)registerRemoteNotifications {
    // 區分是不是 iOS8 or later
    if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerForRemoteNotifications)]) {
        // 這裏 types 能夠自定義,若是 types 爲 0,那麼全部的用戶通知均會靜默的接收,系統不會給用戶任何提示(固然,App 能夠本身處理並給出提示)
        UIUserNotificationType types = (UIUserNotificationType) (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert);
        // 這裏 categories 可暫不深刻,本文後面會詳細講解。
        UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
        // 當應用安裝後第一次調用該方法時,系統會彈窗提示用戶是否容許接收通知
        [[UIApplication sharedApplication] registerUserNotificationSettings:mySettings];
    } else {
        UIRemoteNotificationType types = UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound;
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes:types];
    }
}

- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(nonnull UIUserNotificationSettings *)notificationSettings {
    // Register for remote notifications.
    [[UIApplication sharedApplication] registerForRemoteNotifications];
}

// Handle register result.
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    //得到 device token,這一步處理爲字符串的操做很重要
    NSString *token = [[[deviceToken description]
                        stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]
                        stringByReplacingOccurrencesOfString:@" "
                        withString:@""];
    NSLog(@"DeviceToken string, %@", token);
    [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
    // 將 token 發送給 Provider
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    NSLog(@"Error in registration for apns service. Error: %@", error);
}

發送遠程通知

遠程通知的內容

Provider 發送給 APNs 服務器的內容格式以下:

{
   // aps key 是必需要有的
   "aps" : {
      "alert" : {
         "title" : "通知的概要,對 8.2 之前的系統本選項無效"
         "body" : "通知的具體內容",
         "loc-key" : "GAME_PLAY_REQUEST_FORMAT",
         "loc-args" : [ "Jenna", "Frank"]
      },
      "badge" : 3, // 角標數值
      "sound" : 「chime.aiff" // 能夠自定義提示音
   },

   "userName" : "username", // aps key 以外能夠有自定義內容,須要符合 json 格式
   "message" : ["hello", "world", "programmer"]
}

遠程通知的本地化處理

有兩種方式:

  • 在 Provider 端進行本地化

    App 能夠將當前使用的語言發送給 Provider,Provider 在發送遠程通知前,檢查當前設備使用的語言,並作好本地化後發送給 APNs 服務器。App 發送當前使用的語言給 Provider 的示例代碼:

    NSString *preferredLang = [[NSLocale preferredLanguages] objectAtIndex:0];
    const char *langStr = [preferredLang UTF8String];
    [self sendProviderCurrentLanguage:langStr]; // custom method

通常來講,將當前系統語言信息發送給 Provider 時,也會將 Token 一塊兒發送,這樣 Provider 纔可以在發送遠程通知時根據不一樣目的設備進行本地化處理。

此外,當應用啓動後,用戶可能會修改系統語言,這時,App 須要監聽 NSCurrentLocaleDidChangeNotification 通知,並在處理通知的方法中從新向 Provider 發送當前使用的語言。

  • 在客戶端本地化

    這種模式下,Provider 在發送遠程通知時,須要設置 Payload -> alert 中的本地化相關屬性,以下:

    {
         // aps key 是必需要有的
           "aps" : {
              "alert" : {
                 "title" : "通知的概要,對 8.2 之前的系統本選項無效",
                 "loc-key" : "Remote Notification",
                 "loc-args" : [ "hello", "world"]
              },
              "badge" : 3, // 角標數值
              "sound" : 「chime.aiff" // 能夠自定義提示音
           }
    }

上面 loc-key 以及 loc-args 就是本地化相關的屬性,用於本地化 alert 中的 body。

當 App 收到此消息時,會根據系統當前的語言設置去相應的本地化文件中查找與 loc-key 對應的 value,若是 loc-key 對應的 value 是一個格式化的字符串,那麼能夠用 loc-args 傳遞參數。

假設本地化文件中: "Remote Notification" = "咱們程序員一般鍾愛:%@ %@" ,那麼提示信息就是: "咱們程序員鍾愛:hello world";

此外在本地化文件中咱們也能夠用 %n$@ 代替 %@ 用來表示使用 loc-args 的第幾個參數。

例如:"Remote Notification" = "咱們程序員一般鍾愛:%2$@ %1$@",那麼提示信息是:"咱們程序員鍾愛:world hello"。

一樣的,title-loc-key 和 title-loc-args 是對 alert 中的 title 作本地化的。

處理收到的遠程通知

這裏分三種狀況討論如何處理遠程通知:

應用處於前臺

應用處於前臺時,本地通知到達時,不會有提示音、通知欄橫幅提示。可是 App 桌面圖標的右上角角標是有數值顯示的,因此即便在前臺,咱們也應該對角標數量作處理;

此時,咱們能夠在 application:didReceiveRemoteNotification:fetchCompletionHandler: 方法中獲取到遠程通知,示例代碼以下:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler {
    NSData *infoData = [NSJSONSerialization dataWithJSONObject:userInfo options:0 error:nil];
    NSString *info = [[NSString alloc] initWithData:infoData encoding:NSUTF8StringEncoding];
    [self.windowRootController displayNotification:[NSString stringWithFormat:@"From didReceiveRemoteNotification: %@", info]];
    // 這裏將角標數量減一,注意系統不會幫助咱們處理角標數量
    application.applicationIconBadgeNumber = notification.applicationIconBadgeNumber - 1;
}

應用處於後臺

當應用處於後臺時,遠程通知到達時,會根據註冊通知是設置的通知類型以及用戶設置的通知類型進行提示,例如鎖屏界面通知、通知欄通知、聲音、角標。

此時若是滑動鎖屏界面通知或點擊通知欄通知,則會切換應用到前臺,咱們可使用與應用處於前臺時相同的獲取通知的方式。

可是若是咱們點擊 App 桌面圖標,則沒法獲取到用戶通知,此時通知欄消息仍然會存在。此外,角標也不會變化,若是但願修改角標,則須要 App 進入前臺後將其修改。

應用沒有運行

這裏有兩種處理方式:

  • 與本地通知的處理方式相同,在 application:didFinishLaunchingWithOptions: 的 LaunchOptions 中獲取通知,不過內部代碼會略有不一樣,示例以下:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        NSDictionary *remoteNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
        if (remoteNotif) {
            NSData *infoData = [NSJSONSerialization dataWithJSONObject:remoteNotif options:0 error:nil];
            NSString *info = [[NSString alloc] initWithData:infoData encoding:NSUTF8StringEncoding];
            [self.windowRootController displayNotification:[NSString stringWithFormat:@"From didFinishLaunch: %@", info]];
            [UIApplication sharedApplication].applicationIconBadgeNumber -= 1;
        }
        // Your own other codes.
        return YES;
    }
  • 與應用處於先後臺時處理方式相同,使用 application:didReceiveRemoteNotification:fetchCompletionHandler: 方法,示例代碼見 "應用處於前臺" 時的處理。

    對於遠程通知,推薦使用此種方式處理。

    此外,對於遠程通知,若是咱們點擊 App 桌面圖標,則沒法獲取到用戶通知,此時通知欄消息仍然會存在。此外,角標也不會變化,若是但願修改角標,則須要 App 進入前臺後將其修改。

遠程通知-靜默推送

靜默推送是指應用在前臺或後臺狀態下,收到遠程通知時,沒有彈窗或橫幅提示,即便處於後臺也能夠處理遠程通知。具體使用流程以下:

  1. 打開應用工程 Target 的 Capacities,將 Background Modes 選項打開,而且勾選 Remote Notifications;
  2. 在 Provider 發送遠程通知時,須要將遠程通知 Payload 中的 aps 內的 content-available 設置爲 1,以下:

    aps {  
         content-available: 1
         alert: {...}
    }
  3. 應用須要實現 application:didReceiveRemoteNotification:fetchCompletionHandler: 方法接收靜默推送。

有幾點須要注意:

  1. 使用靜默推送時,alert 字段不該有任何信息,但能夠設置 aps 內的自定義字段;
  2. sound 和 badge 字段能夠設置,但最好不設置,不然會有提示音;
  3. 靜默推送只有當應用處於前臺或後臺時才能處理,當應用沒有啓動時是收不到靜默推送的;
  4. 處理靜默推送時,不能作耗時操做,由於系統只爲這種處理行爲分配少許時間,以下載文件之類的操做請使用後臺下載服務。

可操做通知

首先須要注意的是,可操做通知只適用於 iOS8 及之後的系統。

可操做通知其實並非一種新的通知形式,它只是在這本地通知和遠程通知的基礎上加了一些可操做的行爲而已。爲了直觀說明什麼是可操做通知,能夠參考下圖:

可操做通知爲用戶提供了在通知提示中方便執行操做的方式,在使用橫幅提示通知消息時,最多能夠有兩個操做,在使用彈窗提示通知消息是,最多能夠有四個操做。下面講解如何使用:

四:定義可操做通知的行爲

基本使用方法:

  1. 建立一個 UIMutableUserNotificationAction 對象,並按需求配置該對象的屬性,示例代碼:

    UIMutableUserNotificationAction *acceptAction = [[UIMutableUserNotificationAction alloc] init];
    // 爲該操做設置一個 id
    acceptAction.identifier = @"accept";
    // 設置該操做對應的 button 顯示的字符串
    acceptAction.title = @"Accept";
    // 指定是否須要應用處於運行狀態
    acceptAction.activationMode = UIUserNotificationActivationModeBackground;
    // 表示該操做是否有害,若設置爲 YES,則對應的button會有高亮
    acceptAction.destructive = NO;
    // 當鎖屏時收到可操做通知,該屬性表示是否必須解鎖才能執行該操做
    acceptAction.authenticationRequired = YES;
  2. 建立一個 UIMutableUserNotificationCategory 對象,並將自定義的操做經過 setActions: 的方式設置給 category 對象。代碼以下:

    // 這裏爲了測試,又新建了兩個 action,declineAction 和 maybeAction ,代碼可見 demo
    UIMutableUserNotificationCategory *inviteCategory = [[UIMutableUserNotificationCategory alloc] init];
    // 設置一個 ID,用於本地通知或遠程通知時指定該通知可執行的操做group
    inviteCategory.identifier = @"Action";
    // 爲彈窗模式設置 actions
    [inviteCategory setActions:@[acceptAction, maybeAction, declineAction] forContext:UIUserNotificationActionContextDefault];
    // 爲橫幅模式設置 actions
    [inviteCategory setActions:@[acceptAction, declineAction] forContext:UIUserNotificationActionContextMinimal];
  3. 註冊通知類型以及可操做的actions

    相似於本地通知和遠程通知,調用 registerUserNotificationSettings: 註冊通知,只是這裏的 setting 加入了咱們上面定義的 category。

    NSSet *categories = [NSSet setWithObjects:inviteCategory, nil];
    // 這裏 types 能夠自定義,若是 types 爲 0,那麼全部的用戶通知均會靜默的接收,系統不會給用戶任何提示(固然,App 能夠本身處理並給出提示)
    UIUserNotificationType types = (UIUserNotificationType) (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert);
    // 這裏 categories 可暫不深刻,本文後面會詳細講解。
    UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:types categories:categories];
    // 當應用安裝後第一次調用該方法時,系統會彈窗提示用戶是否容許接收通知
    [[UIApplication sharedApplication] registerUserNotificationSettings:mySettings];

若是將可執行通知用於遠程通知,那麼須要按照遠程通知的註冊方式獲取 token,可參考遠程通知的註冊。

發送可操做通知

以前說過,可操做通知只是在本地通知和遠程通知的基礎上加了自定義的操做,因此發送可操做通知就是發送本地通知或遠程通知。

不過,若是但願咱們自定義的 action 有效,在發送本地通知或遠程通知時須要進行一些改變:

  • 本地通知的可操做通知

    爲 UILocalNotification 對象設置咱們自定義的 category。以下:

    UILocalNotification *notification = [[UILocalNotification alloc] init];

    // Other configurations

    notification.category = @"Action";

    [[UIApplication sharedApplication] scheduleLocalNotification:notification];

  • 遠程通知的可操做通知

    在遠程通知的 Payload 中設置咱們自定義的 category,以下:

    {

    "aps" :  {
        "alert" : "You’re invited!",
        "category" : "Action"
    }

    }

處理可操做通知

處理可操做通知與處理本地通知和遠程通知相同,惟一的不一樣點就是當用戶執行了某個操做後,

應用能夠在後臺運行 application:handleActionWithIdentifier:forRemoteNotification:completionHandler: 處理通知(例如後臺更新數據等操做),咱們能夠在這個回調裏快速的執行操做:

- (void)application:(UIApplication *) application
              handleActionWithIdentifier: (NSString *) identifier
          // either forLocalNotification: (NSDictionary *) notification or
                   forRemoteNotification: (NSDictionary *) notification
                       completionHandler: (void (^)()) completionHandler {

    if ([identifier isEqualToString: @"accept"]) {
        [self handleAcceptActionWithNotification:notification];
    }

    // 執行自定義代碼完成後必須調用
    completionHandler();
}

對於本地通知咱們可使用 application:handleActionWithIdentifier:forLocalNotification:completionHandler: 。

可操做通知到底有什麼好處?

這裏舉個例子說明:

假如A向B發出了一個出席發佈會邀請,而且 App 是以遠程通知的方式接收到該信息,那麼當不使用可操做通知的時候,咱們須要作的事情主要包括:

  1. 用戶須要打開應用;
  2. App 查看遠程通知的內容是一個邀請,那麼 App 應該彈窗提示用戶是否接受該邀請;
  3. 用戶選擇後,App 經過 Http(也可使用其餘通訊協議) 將結果返回給服務器;
  4. 邀請通知處理完畢。

那麼,若是咱們使用可操做通知,能夠很簡單的作到這件事情:

  1. 用戶選擇接受或拒絕邀請(用戶無需打開 App);
  2. App 經過可操做通知的回調處理用戶操做結果,將結果發送給服務器;
  3. 邀請通知處理完畢。

    能夠看到,不管是從用戶角度仍是開發者角度,可操做通知都極大的方便了處理具備可操做動做的這類通知。

五:總結

到這裏已經講解完成了用戶通知的內容,文章包含了蘋果給出的用戶通知的基本全部用法.

相關文章
相關標籤/搜索