iOS开发技能集锦iOS常用

iOS 推送(苹果原生态)

2018-09-08  本文已影响276人  Hsusue

前言

推送对App的重要性不言而喻,是每一个iOS开发者必修的技能。网上的资料对于初学者并不友好(至少对于我来说),其中有许多坑。并且由于要配置证书,只能真机调试等,学起来更是难上加难。这篇文章是从刚开始接触推送的起点写起。最近有点忙,有些地方没有细究,只是暂时知道了能实现什么,并且贴出了一些文章的链接,方便进一步研究,如果有错误的地方请指出来。

image

这篇先探究苹果原生态推送,下篇再写极光推送和IMiOS —— 极光推送和极光IM

目录

  1. 苹果原生态推送

  2. 推送知识点及疑惑的地方

  3. 推送消息调用方法的时机,以及系统能做到的更多骚扩展。

苹果原生态推送

远程推送整体流程

image

实现步骤

  1. 在项目 target 中,打开Capabilitie -> Push Notifications,并会自动在项目中生成 .entitlement 文件。打开Capablitie -> Background Modes -> Remote notifications。

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

#ifdef NSFoundationVersionNumber_iOS_9_x_Max
#import <UserNotifications/UserNotifications.h>
#endif

并且遵循协议UNUserNotificationCenterDelegate(该协议是iOS10.0+用,后面再提到)。

  1. 向APNs服务器注册deviceToken

在程序首次调用以下方法时,会请求推送权限。

首先用户要允许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的方法,能直接输入。


image

iOS10的方法,能增加按钮事件。


image

iOS10的方法,自定义界面。


image
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();
}

这些方法还有坑,笔者放到本文最后的调用时机里再说。


到这里已经把整个流程大致描绘出来了。

这里补充一些知识点,和提出一些疑惑的地方。

补充的知识点

补充点小知识点,应该能更好地理解原理。

1⃣️仍然能够推送及接收(通知中心通知、顶部横幅、刷新 App 右上角的小圆点即 badge [以下简称角标] 等都会由系统来控制和展示)。

2⃣️(收到推送时,是无法在 App 的代码中获取到通知内容的。因为沙盒机制,此时 App 的任何代码都不可能被执行。)这个观点是在另一篇文章看到的,但笔者实测发现并不是这么回事。处于后台收到推送是会执行代码的。

所以就算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,如果采用后台或静默推送,就只在静默中处理;

不是静默推送。在前台收到,就红点显示,通知栏不要显示。如果不在前台,通知栏显示,点击后可跳转到对应聊天框。

// 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
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

参考

上一篇下一篇

猜你喜欢

热点阅读