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. 邀请通知处理完毕。

    能够看到,不管是从用户角度仍是开发者角度,可操做通知都极大的方便了处理具备可操做动做的这类通知。

五:总结

到这里已经讲解完成了用户通知的内容,文章包含了苹果给出的用户通知的基本全部用法.

相关文章
相关标签/搜索