美文网首页
IOS基础:推送服务

IOS基础:推送服务

作者: 时光啊混蛋_97boy | 来源:发表于2020-10-28 15:23 被阅读0次

    原创:知识点总结性文章
    创作不易,请珍惜,之后会持续更新,不断完善
    个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
    温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

    目录

    • 一、Local Notifications(本地推送)
      • 1、本地推送流程
      • 2、通知的触发条件
      • 3、通知的内容
      • 4、对推送进行查、改、删
      • 5、UNUserNotificationCenterDelegate中的回调方法
    • 二、Remote Notifications(远程推送)
      • 1、远程推送流程
      • 2、准备工作
      • 3、AppDelegate中的方法
      • 4、App Server
    • 三、iOS 通知扩展
      • 1、准备工作
      • 2、通知服务扩展(UNNotificationServiceExtension)
      • 3、通知内容扩展(UNNotificationContentExtension)
    • 四、极光推送
      • 1、简介
      • 2、项目中极光SDK的配置
      • 3、JPUSHRegisterDelegate
      • 4、封装的便利方法
    • Demo
    • 参考文献

    一、Local Notifications(本地推送)

    1、本地推送流程

    本地推送通知是由本地应用触发的,是基于时间的通知形式,一般用于闹钟定时、待办事项等提醒功能。

    a、发送本地推送通知的步骤
    1. 创建一个触发器(trigger
    2. 创建推送的内容(UNMutableNotificationContent
    3. 创建推送请求(UNNotificationRequest
    4. 推送请求添加到推送管理中心(UNUserNotificationCenter)中
    b、步骤的代码实现
    - (void)simpleLocalNotificationDescribe
    {
        [self buildNotificationDescribe:@"最简单的本地通知(创建5秒后触发,建议回到桌面察看效果)"];
    }
    
    - (void)simpleLocalPushService
    {
        // 1.定时推送
        UNTimeIntervalNotificationTrigger *trigger = [self getTimeTrigger];
        
        // 2.推送的内容
        UNMutableNotificationContent *content = [self getSimpleContent];
        
        // 3.创建通知请求 UNNotificationRequest 将触发条件和通知内容添加到请求中
        NSString *requestIdentifer = @"Simple Local Notification";
        UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifer content:content trigger:trigger];
        
        // 4.将通知请求 add 到 UNUserNotificationCenter
        [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
            
            if (!error)
            {
                NSLog(@"简单的本地通知已添加成功!");
                
                // 此处省略一万行需求.......
            }
        }];
    }
    

    2、通知的触发条件

    苹果把本地通知跟远程通知合二为一。区分本地通知跟远程通知的类是UNNotificationTrigger,通过它,我们可以得到一些通知的触发条件。

    UNPushNotificationTrigger// 远程推送的通知类型
    UNTimeIntervalNotificationTrigger// (本地通知) 一定时间之后,重复或者不重复推送通知。我们可以设置timeInterval(时间间隔)和repeats(是否重复)
    UNCalendarNotificationTrigger//(本地通知) 一定日期之后,重复或者不重复推送通知 例如,你每天8点推送一个通知,只要dateComponents为8,如果你想每天8点都推送这个通知,只要repeats为YES就可以了
    UNLocationNotificationTrigger// (本地通知)地理位置的一种通知,当用户进入或离开一个地理区域来通知
    
    定时推送
    // 触发推送的时机。timeInterval:单位为秒(s)  repeats:是否循环提醒
    UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5 repeats:NO];
    
    定期推送
    // components代表日期,这里指在每周一的14点3分提醒
    NSDateComponents *components = [[NSDateComponents alloc] init];
    components.weekday = 2;
    components.hour = 14;
    components.minute = 3;
    UNCalendarNotificationTrigger *calendarTrigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:YES];
    
    定点推送
    // 使用CLRegion的子类CLCircularRegion创建位置信息
    CLLocationCoordinate2D center1 = CLLocationCoordinate2DMake(39.788857, 116.5559392);
    CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center1 radius:500 identifier:@"海峡国际社区"];
    
    // 进入地区、从地区出来或者两者都要的时候进行通知
    region.notifyOnEntry = YES;
    region.notifyOnExit = YES;
    
    // region 位置信息 repeats 是否重复
    UNLocationNotificationTrigger *locationTrigger = [UNLocationNotificationTrigger triggerWithRegion:region repeats:YES];
    

    3、通知的内容

    a、文字、图像、声音
    简单的本地通知
    下拉放大图像
    - (UNMutableNotificationContent *)getSimpleContent
    {
        // 推送的文本内容
        // UNNotificationContent的属性readOnly,而UNMutableNotificationContent的属性可以更改
        UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
        
        // 限制在一行,多出部分省略号
        content.title = @"时间提醒";
        content.subtitle = [NSString stringWithFormat:@"《请回答1988》第二季放映的时间提醒"];
        
        // body中printf风格的转义字符,比如说要包含%,需要写成%% 才会显示,\同样
        content.body = @"口若悬河、妙语迭出的精彩表演,从作者津津乐道的口吻可以看出,王尔德无疑是在顾影自怜,因为他自己正是这样的作秀高手,而他在社交圈中越练越“酷”的口才,在他的社会喜剧中得到了淋漓尽致的发挥,令观众如醉如痴!";
        
        content.badge = @5;
        // UNNotificationSound *customSound = [UNNotificationSound soundNamed:@""];// 自定义声音
        content.sound = [UNNotificationSound defaultSound];
        content.userInfo = @{@"useName":@"XieJiapei",@"age":@"22"};
        
        // 辅助图像,下拉通知会放大图像
        NSString *imageFilePath = [[NSBundle mainBundle] pathForResource:@"luckcoffee" ofType:@"JPG"];
        if (imageFilePath)
        {
            NSError* error = nil;
            UNNotificationAttachment *imageAttachment = [UNNotificationAttachment attachmentWithIdentifier:@"imageAttachment" URL:[NSURL fileURLWithPath:imageFilePath] options:nil error:&error];
            if (imageAttachment)
            {
                // 这里设置的是Array,但是只会取lastObject
                content.attachments = @[imageAttachment];
            }
        }
        
        return content;
    }
    
    b、视频
    下拉播放视频
    - (UNMutableNotificationContent *)getVideoContent
    {
        UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
        content.title = @"WWDC";
        content.subtitle = @"苹果开发者技术大会";
        content.body = @"下拉通知可直接播放";
        
        // 导入视频的时候,默认不是添加到bundle中,必须手动勾选Add to targets
        NSString *videoFilePath = [[NSBundle mainBundle] pathForResource:@"notification_video" ofType:@"m4v"];
        if (videoFilePath)
        {
            UNNotificationAttachment* videoAttachment = [UNNotificationAttachment attachmentWithIdentifier:@"videoAttachment" URL:[NSURL fileURLWithPath:videoFilePath] options:nil error:nil];
            
            if (videoAttachment)
            {
                // 这里设置的是Array,但是只会取lastObject
                content.attachments = @[videoAttachment];
            }
        }
        
        return content;
    }
    
    c、操作
    用户操作 输入文本
    - (UNMutableNotificationContent *)getActionContent
    {
        UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
        content.title = @"Apple";
        content.subtitle = @"Apple Developer";
        content.body = @"下拉放大图片";
        
        NSMutableArray *actionMutableArray = [[NSMutableArray alloc] initWithCapacity:3];
        
        // UNNotificationActionOptionAuthenticationRequired 需要解锁显示,点击不会进app
        UNNotificationAction *unUnlockAction = [UNNotificationAction actionWithIdentifier:@"IdentifierNeedUnUnlock" title:@"需要解锁" options: UNNotificationActionOptionAuthenticationRequired];
        
        // UNNotificationActionOptionDestructive 红色文字,点击不会进app
        UNNotificationAction *destructiveAction = [UNNotificationAction actionWithIdentifier:@"IdentifierRed" title:@"红色显示" options: UNNotificationActionOptionDestructive];
        
        // UNNotificationActionOptionForeground 黑色文字,点击会进app
        // UNTextInputNotificationAction是输入框Action,buttonTitle是输入框右边的按钮标题,placeholder是输入框占位符
        UNTextInputNotificationAction *inputTextAction = [UNTextInputNotificationAction actionWithIdentifier:@"IdentifierInputText" title:@"输入文本" options:UNNotificationActionOptionForeground textInputButtonTitle:@"发送" textInputPlaceholder:@"说说今天发生了啥......"];
        
        [actionMutableArray addObjectsFromArray:@[unUnlockAction, destructiveAction, inputTextAction]];
        
        if (actionMutableArray.count > 1)
        {
            /**categoryWithIdentifier方法
             * identifier:是这个category的唯一标识,用来区分多个category,这个id不管是Local Notification,还是remote Notification,一定要有并且要保持一致
             * actions:创建action的操作数组
             * intentIdentifiers:意图标识符 可在 <Intents/INIntentIdentifiers.h> 中查看,主要是针对电话、carplay 等开放的 API
             * options:通知选项 枚举类型 也是为了支持 carplay
             */
            UNNotificationCategory *categoryNotification = [UNNotificationCategory categoryWithIdentifier:@"categoryOperationAction" actions:actionMutableArray intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];
            
            // 将创建的 category 添加到通知中心
            [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObject:categoryNotification]];
            
            // category的唯一标识,Local Notification保持一致
            content.categoryIdentifier = @"categoryOperationAction";
        }
        
        return content;
    }
    

    4、对推送进行查、改、删

    a、更新通知

    Local Notification重新创建具有相同requestIdentifierlocal Notification request添加到推送center就可以了。Remote Notification 更新需要通过新的字段apps-collapse-id来作为唯一标示,APNS pusher暂不支持这个字段,不过github上有这样的工具:Knuff

    b、查找和删除通知
    //获取未送达的所有消息列表
    - (void)getPendingNotificationRequestsWithCompletionHandler:(void(^)(NSArray<UNNotificationRequest *> *requests))completionHandler;
    //删除所有未送达的特定id的消息
    - (void)removePendingNotificationRequestsWithIdentifiers:(NSArray<NSString *> *)identifiers;
    //删除所有未送达的消息
    - (void)removeAllPendingNotificationRequests;
    
    //获取已送达的所有消息列表
    - (void)getDeliveredNotificationsWithCompletionHandler:(void(^)(NSArray<UNNotification *> *notifications))completionHandler;
    //删除所有已送达的特定id的消息
    - (void)removeDeliveredNotificationsWithIdentifiers:(NSArray<NSString *> *)identifiers;
    //删除所有已送达的消息
    - (void)removeAllDeliveredNotifications;
    

    调用方式如下:

    - (void)removeNotificaiton
    {
        NSString *requestIdentifier = @"XieJiaPei";
        UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
        
        // 删除设备已收到特定id的所有消息推送
        [center removeDeliveredNotificationsWithIdentifiers:@[requestIdentifier]];
        
        // 删除设备已收到的所有消息推送
        [center removeAllDeliveredNotifications];
        
        // 获取设备已收到的消息推送
        [center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * _Nonnull notifications) {
            NSLog(@"获取设备已收到的消息推送");
        }];
    }
    

    5、UNUserNotificationCenterDelegate中的回调方法

    a、即将展示推送的通知时触发(app在前台获取到通知)
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
    {
        // 收到推送的请求
        UNNotificationRequest *request = notification.request;
        
        // 收到的内容
        UNNotificationContent *content = request.content;
        
        // 收到用户的基本信息
        NSDictionary *userInfo = content.userInfo;
        
        // 收到消息的角标
        NSNumber *badge = content.badge;
        
        // 收到消息的body
        NSString *body = content.body;
        
        // 收到消息的声音
        UNNotificationSound *sound = content.sound;
        
        // 推送消息的副标题
        NSString *subtitle = content.subtitle;
        
        // 推送消息的标题
        NSString *title = content.title;
        
        if ([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]])// 远程推送的通知
        {
            NSLog(@"远程推送的通知,收到用户的基本信息为: %@\n",userInfo);
        }
        else // 本地通知
        {
            NSLog(@"本地推送的通知:{\nbody:%@,\ntitle:%@,\nsubtitle:%@,\nbadge:%@,\nsound:%@,\nuserInfo:%@}",body,title,subtitle,badge,sound,userInfo);
        }
        
        // 不管前台后台状态下。推送消息的横幅都可以展示出来
        // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Banner三种类型可以设置
        completionHandler(UNNotificationPresentationOptionBadge|
                          UNNotificationPresentationOptionSound|
                          UNNotificationPresentationOptionBanner);
    }
    
    本地通知的输出结果
    2020-10-26 16:08:53.383283+0800 PushServiceDemo[71205:2358029] 简单的本地通知已添加成功!
    2020-10-26 16:08:58.393934+0800 PushServiceDemo[71205:2357722] 本地推送的通知:{
    body:口若悬河、妙语迭出的精彩表演,从作者津津乐道的口吻可以看出,王尔德无疑是在顾影自怜,因为他自己正是这样的作秀高手,而他在社交圈中越练越“酷”的口才,在他的社会喜剧中得到了淋漓尽致的发挥,令观众如醉如痴!,
    title:时间提醒,
    subtitle:《请回答1988》第二季放映的时间提醒,
    badge:5,
    sound:<UNNotificationSound: 0x6000038f1a40>,
    userInfo:{
        age = 22;
        useName = XieJiapei;
    }}
    
    b、用户点击推送消息时触发 (点击通知进入app时触发)
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
    {
        //  UNNotificationResponse 是普通按钮的Response
        NSString *actionIdentifierString = response.actionIdentifier;
        if (actionIdentifierString)
        {
            // 点击后展示文本2秒后隐藏
            UIView *windowView = [[[UIApplication sharedApplication] keyWindow] rootViewController].view;
            MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:windowView animated:YES];
            hud.label.text = [NSString stringWithFormat:@"用户点击了消息,id为:%@",actionIdentifierString];
            hud.mode = MBProgressHUDModeText;
            [hud hideAnimated:YES afterDelay:2];
            
            if ([actionIdentifierString isEqualToString:@"IdentifierNeedUnUnlock"])
            {
                NSLog(@"需要解锁");
            }
            else if ([actionIdentifierString isEqualToString:@"IdentifierRed"])
            {
                NSLog(@"红色显示,并且设置APP的Badge通知数字为0");
                
                // 设置APP的Badge通知数字
                [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
            }
        }
        
        //  UNTextInputNotificationResponse 是带文本输入框按钮的Response
        if ([response isKindOfClass:[UNTextInputNotificationResponse class]])
        {
            NSString *userSayString = [(UNTextInputNotificationResponse *)response userText];
            if (userSayString)
            {
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    
                    UIView *windowView = [[[UIApplication sharedApplication] keyWindow] rootViewController].view;
                    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:windowView animated:YES];
                    hud.label.text = userSayString;
                    hud.mode = MBProgressHUDModeText;
                    [hud hideAnimated:YES afterDelay:2];
                });
            }
        }
    
        // 系统要求执行这个方法
        completionHandler();
    }
    

    点击红色按钮的输出结果为:

    2020-10-27 16:20:02.797425+0800 PushServiceDemo[80030:2808431] 红色显示,并且设置APP的Badge通知数字为0
    

    二、Remote Notifications(远程推送)

    1、远程推送流程

    远程推送通知是通过苹果的APNsApple Push Notification service)发送到app,而APNs必须先知道用户设备的令牌(device token)。在启动时,app与APNs通信并接收device token,然后将其转发到App ServerApp Server将该令牌和要发送的通知消息发送至APNs

    苹果官方提供的远程推送通知的传递示意图如下:


    远程推送通知的传递过程

    各关键组件之间的交互细节:


    各关键组件之间的交互细节

    2、准备工作

    a、推送证书和权限
    1. 根据工程的Bundle Identifier,在苹果开发者平台中创建同名App ID,并勾选Push Notifications服务
    2. 在工程的Capabilities中启动Push Notifications
    3. 远程推送必须使用真机调试,因为模拟器无法获取得到device token

    想要为苹果开发软件并上架苹果商店,就需要参加苹果开发者计划,需要交纳年费(99和699两档),只要求自己编写的代码在苹果真机上跑起来,只需要注册成苹果开发者账户就可以了,不需要交钱,但是如果想调试推送、iCloud、IAP之类的功能,或者上架苹果商店,就需要交钱了。买不起~~~~无法调试呀🧐

    以后有开发者账户了再看看吧~生成APNs后端推送证书

    3、AppDelegate中的方法

    a、注册远程通知
    - (void)registerPushService
    {
        // 远程通知授权
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound) completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (granted)
            {
                NSLog(@"远程通知中心成功打开");
                
                // 必须在主线程注册通知
                dispatch_async(dispatch_get_main_queue(), ^{
                    // 注册远程通知
                    [[UIApplication sharedApplication] registerForRemoteNotifications];
                    
                    // 注册delegate
                    [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self];
                });
            }
            else
            {
                NSLog(@"远程通知中心打开失败");
            }
        }];
        
        // 获取注册之后的权限设置
        // 注意UNNotificationSettings是只读对象,不能直接修改
        [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
            NSLog(@"通知的配置信息:\n%@",settings);
        }];
    }
    

    输出结果为:

    2020-10-26 16:00:00.694842+0800 PushServiceDemo[71205:2357947] 通知的配置信息:
    <UNNotificationSettings: 0x6000038f0d20; authorizationStatus: Authorized, notificationCenterSetting: Enabled, soundSetting: Enabled, badgeSetting: Enabled, lockScreenSetting: Enabled, carPlaySetting: NotSupported, announcementSetting: NotSupported, criticalAlertSetting: NotSupported, alertSetting: Enabled, alertStyle: Banner, groupingSetting: Default providesAppNotificationSettings: No>
    2020-10-26 16:00:00.696243+0800 PushServiceDemo[71205:2357946] 远程通知中心成功打开
    
    b、App获取device token

    app将获取到的device token发送给App Server。只有苹果公司知道device token的生成算法,保证唯一,device token在app卸载后重装等情况时会变化,因此为确保device token变化后app仍然能够正常接收服务器端发送的通知,建议每次启动应用都将获取到的device token传给App Server

    // 远端推送需要获取设备的Device Token
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
    {
        // 解析NSData获取字符串
        NSString *deviceString = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];// 移除<>
        deviceString = [deviceString stringByReplacingOccurrencesOfString:@" " withString:@""];// 移除空格
        
        NSLog(@"设备的Device Token为:%@",deviceString);
    }
    
    // 获取设备的DeviceToken失败
    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
    {
        NSLog(@"获取设备的DeviceToken失败:%@\n",error.description);
    }
    

    4、App Server

    a、Pusher的下载地址

    NWPusher可以可以当做framework使用,也可以直接下载APP使用。

    b、Pusher的使用步骤

    使用Pusher工具模拟App Server将指定的device token和消息内容发送给APNs

    Pusher
    1. 选择p12格式的推送证书
    2. 设置是否为测试环境(默认勾选为测试环境,由于推送证书分为测试证书和生产证书,并且苹果的APNs也分为测试和生产两套环境,因此Pusher需要手动勾选推送环境)
    3. 输入device token
    4. 输入符合苹果要求格式的aps字符串
    5. 执行推送
    c、内容格式

    Payload输入内容就是我们需要传送的数据了:这个数据传输以JSON的格式存储。

    {"aps":{"alert":{"title":"通知的title","subtitle":"通知的subtitle","body":"通知的body","title-loc-key":"TITLE_LOC_KEY","title-loc-args":["t_01","t_02"],"loc-key":"LOC_KEY","loc-args":["l_01","l_02"]},"sound":"sound01.wav","badge":1,"mutable-content":1,"category": "realtime"},"msgid":"123"}
    
    • aps:value是我们需要传送的内容
    • alert:value就是弹出框需要展示的内容
    • badge:value就是APP icon,展示的信息个数
    • sound:value就是表示当有Push消息的时候,是否需要声音提示
    d、稍纵即逝你就收到了远端消息了

    可惜没开发者账户无法测试,连device token都拿不到。

    2020-10-27 15:06:34.967968+0800 PushServiceDemo[1272:220269] 获取设备的DeviceToken失败:Error Domain=NSCocoaErrorDomain Code=3000 "未找到应用程序的“aps-environment”的授权字符串" UserInfo={NSLocalizedDescription=未找到应用程序的“aps-environment”的授权字符串}
    

    三、iOS 通知扩展

    1、准备工作

    a、添加新的Target--> Notification Service/Content
    添加新的Target
    b、扩展工程的目录

    系统会自动创建一个 UNNotificationServiceExtension 的子类 NotificationService,通过完善这个子类,来实现你的需求。NotificationViewController直接继承于ViewController,因此可以在这个类中重写相关方法,来修改界面的相关布局及样式。

    工程目录
    c、扩展提供的方法
    Notification Service
    // 让你可以在后台处理接收到的推送,传递最终的内容给 contentHandler
    // 系统接到通知后,有最多30秒在这里重写通知内容(如下载附件并更新通知)
    - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler
    {
        self.contentHandler = contentHandler;
        self.bestAttemptContent = [request.content mutableCopy];
        
        self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
        
        self.contentHandler(self.bestAttemptContent);
    }
    
    // 在你获得的一小段运行通知代码的时间即将结束的时候
    // 如果仍然没有成功的传入内容,会走到这个方法,可以在这里传肯定不会出错的内容,或者默认传递原始的推送内容
    // 处理过程超时,则收到的通知直接展示出来
    - (void)serviceExtensionTimeWillExpire
    {
        self.contentHandler(self.bestAttemptContent);
    }
    
    Notification Content
    // 在这儿做界面初始化的工作
    // 不能插入新的event,纯代码addTarget的event无法使用
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    }
    
    // 获取通知信息,更新UI控件中的数据
    - (void)didReceiveNotification:(UNNotification *)notification {
        
        self.label.text = notification.request.content.body;
        self.titleLabelA.text = [NSString stringWithFormat:@"%@ + %@", notification.request.content.title, notification.request.content.subtitle];
        self.titleLabelB.text = [NSString stringWithFormat:@" = %ld", [notification.request.content.title integerValue] + [notification.request.content.subtitle integerValue]];
    }
    
    d、调整 info.plist

    使用自定义的NotificationContent的时候,需要对应extensioninfo.plist,因为推送通知内容中的category字段,与UNNotificationContentExtensioninfo.plistUNNotificationExtensionCategory字段的值要匹配,系统才能找到自定义的UI

    categoryIdentifier

    UNNotificationExtensionCategory默认是string类型,可以手动更改成array类型,array中的item(string)是categoryName。在收到通知的时候,我们可以让服务器把这个通知的categoryIdentifier带上,作用是我们可以根据视频、音乐、图片来分别自定义我们的通知内容。不同的分类标识符,也会在使用UNNotificationAction的时候帮助我们区分是什么类型的通知,方便我们对不同类型的通知做出不同的操作行为。我们目前在ServiceContentaps写死了categoryIdentifier,其实在收到系统推送时,每一个推送内容最好带上一个跟服务器约定好了的categoryIdentifier,这样方便我们根据categoryIdentifier来自定义不同类型的视图,以及action

    UNNotificationExtensionInitialContentSizeRatio

    UNNotificationExtensionInitialContentSizeRatio 这个值必须要有,类型是一个浮点类型,代表的是高度与宽度的比值。系统会使用这个比值,作为初始化view的大小。举个简单的例子来说,如果该值为1,则该视图为正方形。如果为0.5,则代表高度是宽度的一半。注意这个值只是初始化的一个值,在这个扩展添加后,可以重写frame,展示的时候,在我们还没打开这个视图预览时,背景是个类似图片占位的灰色,那个灰色的高度宽度之比,就是通过这个值来设定。

    UNNotificationExtensionDefaultContentHidden

    UNNotificationExtensionDefaultContentHidden 这个值可选,是一个BOOL值。当为YES时,会隐藏上方原本推送的内容视图,只会显示我们自定义的视图,因为在自定义视图的时候,我们可以取得推送内容,然后按照我们想要的布局,展示出来。如果为NO时(默认为NO),推送视图就会既有我们的自定义视图,也会有系统原本的推送内容视图(这里附件是不会显示的,只会显示body里面的文字哟)。这里需要隐藏默认消息框,所以添加UNNotificationExtensionDefaultContentHidden属性,Bool(YES)

    NSExtensionMainStoryboard

    至于NSExtensionMainStoryboard以及NSExtensionPointIdentifier,系统默认生成,大家直接用就好,如果需要更改的,只能更改使用的storyboard的名字(不过应该没人会把系统的删除再建立一个吧 O(∩_∩)O)

    最初的info.plist
    最初的info.plist
    修改成array后的info.plist
    修改成array后的info.plist
    修改后与Service和aps的info.plist
    修改后与Service和aps的info.plist

    2、通知服务扩展(UNNotificationServiceExtension)

    a、简介
    支持附带 Media Attachments

    本地推送和远程推送同时都可支持附带Media Attachments。不过远程通知需要实现通知服务扩展UNNotificationServiceExtension,在service extension里面去下载attachment,但是需要注意,service extension会限制下载的时间(30s),并且下载的文件大小也会同样被限制。这里毕竟是一个推送,而不是把所有的内容都推送给用户。所以你应该去推送一些缩小比例之后的版本。比如图片,推送里面附带缩略图,当用户打开app之后,再去下载完整的高清图。视频就附带视频的关键帧或者开头的几秒,当用户打开app之后再去下载完整视频。

    UNNotificationAttachment 支持的附件格式和大小限制
    • 音频5M(kUTTypeWaveformAudio/kUTTypeMP3/kUTTypeMPEG4Audio/kUTTypeAudioInterchangeFileFormat
    • 图片10M(kUTTypeJPEG/kUTTypeGIF/kUTTypePNG
    • 视频50M(kUTTypeMPEG/kUTTypeMPEG2Video/kUTTypeMPEG4/kUTTypeAVIMovie
    校验附件

    系统会在通知注册前校验附件,如果附件出问题,通知注册失败;校验成功后,附件会转入attachment data store;如果附件是在app bundle,则是会被copy来取代moveattachment data store的位置?利用代码测试获取在磁盘上的图片文件作为attachment,会发现注册完通知后,图片文件被移除,在app的沙盒中找不到该文件在哪里。

    b、NotificationService文件
    #import <UserNotifications/UserNotifications.h>
    
    @interface NotificationService : UNNotificationServiceExtension
    
    @end
    
    #import "NotificationService.h"
    #import <UIKit/UIKit.h>
    
    @interface NotificationService ()
    
    @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
    @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
    
    @end
    
    @implementation NotificationService
    
    @end
    
    c、Demo演示
    最多30秒重写通知内容
    - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler
    {
        self.contentHandler = contentHandler;
        self.bestAttemptContent = [request.content mutableCopy];
        
        // 修改通知的内容
        self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [ServiceExtension modified]", self.bestAttemptContent.title];
        
        // 设置UNNotificationAction
        [self getAction];
        
        // category的唯一标识,Remote Notification保持一致
        self.bestAttemptContent.categoryIdentifier = @"categoryOperationAction";
        
        // 加载网络请求
        NSDictionary *userInfo =  self.bestAttemptContent.userInfo;
        NSString *mediaUrl = userInfo[@"media"][@"url"];
        NSString *mediaType = userInfo[@"media"][@"type"];
        
        if (!mediaUrl.length)// 不存在url则使用基本的内容
        {
            self.contentHandler(self.bestAttemptContent);
        }
        else// 否则使用网络请求到的内容
        {
            // 创建附件资源
            // UNNotificationAttachment的url接收的是本地文件的url
            // 附件资源必须存在本地,如果是远程推送的网络资源需要提前下载到本地
            [self loadAttachmentForUrlString:mediaUrl withType:mediaType completionHandle:^(UNNotificationAttachment *attach) {
                
                if (attach)
                {
                    // 将附件资源添加到 UNMutableNotificationContent 中
                    self.bestAttemptContent.attachments = [NSArray arrayWithObject:attach];
                }
                self.contentHandler(self.bestAttemptContent);
            }];
        }
    }
    
    将获取到的内容传递给content扩展
    - (void)serviceExtensionTimeWillExpire
    {
        // 将获取到的内容传递给content扩展
        self.contentHandler(self.bestAttemptContent);
    }
    
    网络请求
    - (void)loadAttachmentForUrlString:(NSString *)urlStr withType:(NSString *)type completionHandle:(void(^)(UNNotificationAttachment *attach))completionHandler
    {
        __block UNNotificationAttachment *attachment = nil;
        // 附件的URL
        NSURL *attachmentURL = [NSURL URLWithString:urlStr];
        // 获取媒体类型的后缀
        NSString *fileExt = [self getfileExtWithMediaType:type];
        
        // 从网络下载媒体资源
        NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
        [[session downloadTaskWithURL:attachmentURL completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
            
            if (error)
            {
                NSLog(@"加载多媒体失败 %@", error.localizedDescription);
            }
            else
            {
                // 将下载好的媒体文件拷贝到目的路径
                NSFileManager *fileManager = [NSFileManager defaultManager];
                NSURL *localURL = [NSURL fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:fileExt]];
                [fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error];
                
                // 自定义推送UI需要
                NSMutableDictionary *dict = [self.bestAttemptContent.userInfo mutableCopy];
                // 这里使用图片测试
                [dict setObject:[NSData dataWithContentsOfURL:localURL] forKey:@"image"];
                self.bestAttemptContent.userInfo = dict;
                
                NSError *attachmentError = nil;
                // category的唯一标识,Remote Notification保持一致
                // URL 资源路径
                // options 资源可选操作 比如隐藏缩略图之类的
                attachment = [UNNotificationAttachment attachmentWithIdentifier:@"categoryOperationAction" URL:localURL options:nil error:&attachmentError];
                if (attachmentError)
                {
                    NSLog(@"%@", attachmentError.localizedDescription);
                }
            }
            
            // 将附件传递出去
            completionHandler(attachment);
        }] resume];
    }
    
    用于将媒体类型的后缀添加到文件路径上
    - (NSString *)getfileExtWithMediaType:(NSString *)mediaType
    {
        NSString *fileExt = mediaType;
        if ([mediaType isEqualToString:@"image"])
        {
            fileExt = @"jpg";
        }
        
        if ([mediaType isEqualToString:@"video"])
        {
            fileExt = @"mp4";
        }
        
        if ([mediaType isEqualToString:@"audio"])
        {
            fileExt = @"mp3";
        }
        
        return [@"." stringByAppendingString:fileExt];
    }
    
    用户操作
    - (void)getAction
    {
        NSMutableArray *actionMutableArray = [[NSMutableArray alloc] initWithCapacity:3];
        
        // UNNotificationActionOptionAuthenticationRequired 需要解锁显示,点击不会进app
        UNNotificationAction *unUnlockAction = [UNNotificationAction actionWithIdentifier:@"IdentifierNeedUnUnlock" title:@"需要解锁" options: UNNotificationActionOptionAuthenticationRequired];
        
        // UNNotificationActionOptionDestructive 红色文字,点击不会进app
        UNNotificationAction *destructiveAction = [UNNotificationAction actionWithIdentifier:@"IdentifierRed" title:@"红色显示" options: UNNotificationActionOptionDestructive];
        
        // UNNotificationActionOptionForeground 黑色文字,点击会进app
        // UNTextInputNotificationAction是输入框Action,buttonTitle是输入框右边的按钮标题,placeholder是输入框占位符
        UNTextInputNotificationAction *inputTextAction = [UNTextInputNotificationAction actionWithIdentifier:@"IdentifierInputText" title:@"输入文本" options:UNNotificationActionOptionForeground textInputButtonTitle:@"发送" textInputPlaceholder:@"说说今天发生了啥......"];
        
        NSArray *identifierArray = [[NSArray alloc] initWithObjects:@"IdentifierNeedUnUnlock", @"IdentifierRed", @"IdentifierInputText", nil];
        [actionMutableArray addObjectsFromArray:@[unUnlockAction, destructiveAction, inputTextAction]];
        
        if (actionMutableArray.count > 1)
        {
            /**categoryWithIdentifier方法
             * identifier:是这个category的唯一标识,用来区分多个category,这个id不管是Local Notification,还是remote Notification,一定要有并且要保持一致
             * actions:创建action的操作数组
             * intentIdentifiers:意图标识符 可在 <Intents/INIntentIdentifiers.h> 中查看,主要是针对电话、carplay 等开放的 API
             * options:通知选项 枚举类型 也是为了支持 carplay
             */
            UNNotificationCategory *categoryNotification = [UNNotificationCategory categoryWithIdentifier:@"categoryOperationAction" actions:actionMutableArray intentIdentifiers:identifierArray options:UNNotificationCategoryOptionCustomDismissAction];
            
            // 将创建的 category 添加到通知中心
            [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObject:categoryNotification]];
        }
    }
    
    d、APP Server的aps

    mutable-content这个键值为1,这意味着此条推送可以被 Service Extension 进行更改,也就是说要用Service Extension需要加上这个键值为1。

    {"aps":{"alert":{"title":"Title...","subtitle":"Subtitle...","body":"Body..."},"sound":"default","badge": 1,"mutable-content": 1,"category": "categoryOperationAction",},"msgid":"123","media":{"type":"image","url":"https://www.fotor.com/images2/features/photo_effects/e_bw.jpg"}}
    
    e、特别说明

    Notification Service Extension在使用时需要配置相关证书,我没有开发者账号,所以无法调试。🙂(买不起,贫穷限制了我的开发能力......)

    ❷ 要选择相应的target来运行工程。

    target

    ❸ 加断点调试怎么不走相应方法?一个朋友找了很长时间的原因发现是xcode的问题,那个朋友就是我......

    3、通知内容扩展(UNNotificationContentExtension)

    a、在展示通知时展示一个自定义的用户界面。

    这个就是个简单的storyboard文件,内部有一个View,这个View就是在上面的图层中的自定义View视图了。它与NotificationViewController所绑定。

    设置通知的界面

    这里使用纯代码方式来创建界面,所以需要删除MainInterface文件,然后在Notifications Contentinfo.plist中把NSExtensionMainStoryboard替换为NSExtensionPrincipalClass,并且value对应我们的类名NotificationViewController

    #define Margin      15
    
    @interface NotificationViewController () <UNNotificationContentExtension>
    
    @property (nonatomic, strong) UILabel *label;
    @property (nonatomic, strong) UILabel *subLabel;
    @property (nonatomic, strong) UILabel *hintLabel;
    @property (nonatomic, strong) UIImageView *imageView;
    
    @end
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        CGPoint origin = self.view.frame.origin;
        CGSize size = self.view.frame.size;
        
        self.label = [[UILabel alloc] initWithFrame:CGRectMake(Margin, Margin, size.width-Margin*2, 30)];
        self.label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        [self.view addSubview:self.label];
        
        self.subLabel = [[UILabel alloc] initWithFrame:CGRectMake(Margin, CGRectGetMaxY(self.label.frame)+10, size.width-Margin*2, 30)];
        self.subLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        [self.view addSubview:self.subLabel];
        
        self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(Margin, CGRectGetMaxY(self.subLabel.frame)+10, 100, 100)];
        [self.view addSubview:self.imageView];
        
        self.hintLabel = [[UILabel alloc] initWithFrame:CGRectMake(Margin, CGRectGetMaxY(self.imageView.frame)+10, size.width-Margin*2, 20)];
        [self.hintLabel setText:@"我是hintLabel"];
        [self.hintLabel setFont:[UIFont systemFontOfSize:14]];
        [self.hintLabel setTextAlignment:NSTextAlignmentLeft];
        [self.view addSubview:self.hintLabel];
        self.view.frame = CGRectMake(origin.x, origin.y, size.width, CGRectGetMaxY(self.imageView.frame)+Margin);
    
        // 设置控件边框颜色
        [self.label.layer setBorderColor:[UIColor redColor].CGColor];
        [self.label.layer setBorderWidth:1.0];
        [self.subLabel.layer setBorderColor:[UIColor greenColor].CGColor];
        [self.subLabel.layer setBorderWidth:1.0];
        [self.imageView.layer setBorderWidth:2.0];
        [self.imageView.layer setBorderColor:[UIColor blueColor].CGColor];
        [self.view.layer setBorderWidth:2.0];
        [self.view.layer setBorderColor:[UIColor cyanColor].CGColor];
    }
    
    b、Demo演示
    接收到通知的内容
    // 生成时默认实现了UNNotificationContentExtension协议的方法
    - (void)didReceiveNotification:(UNNotification *)notification
    {
        self.label.text = notification.request.content.title;
        self.subLabel.text = [NSString stringWithFormat:@"%@ [ContentExtension modified]", notification.request.content.subtitle];
        
        // 提取附件
        UNNotificationAttachment *attachment = notification.request.content.attachments.firstObject;
        if ([attachment.URL startAccessingSecurityScopedResource])
        {
            NSData *imageData = [NSData dataWithContentsOfURL:attachment.URL];
            [self.imageView setImage:[UIImage imageWithData:imageData]];
            [attachment.URL stopAccessingSecurityScopedResource];
        }
    }
    
    用户操作
    // 点击通知进入app时触发(杀死/切到后台唤起)
    - (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption))completion
    {
        
        [self.hintLabel setText:[NSString stringWithFormat:@"触发了%@", response.actionIdentifier]];
        
        if ([response.actionIdentifier isEqualToString:@"IdentifierNeedUnUnlock"])
        {
            NSLog(@"点击了解锁");
        }
        else if([response.actionIdentifier isEqualToString:@"IdentifierRed"])
        {
            NSLog(@"点击了红色");
        }
        else if([response.actionIdentifier isEqualToString:@"IdentifierInputText"])
        {
            UNTextInputNotificationResponse *textInputResponse = (UNTextInputNotificationResponse *)response;
            [self.hintLabel setText:[NSString stringWithFormat:@"用户输入的文字是:%@", textInputResponse.userText]];
        }
        else
        {
            NSLog(@"啥?");
        }
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            // 必须设置completion,否则通知不会消失
            // UNNotificationContentExtensionResponseOptionDismiss 直接让该通知消失
            // UNNotificationContentExtensionResponseOptionDismissAndForwardAction 消失并传递按钮信息给AppDelegate,是否进入App看Att的设置
            completion(UNNotificationContentExtensionResponseOptionDismiss);
        });
    }
    
    c、控制媒体文件的播放
    枚举UNNotificationContentExtensionMediaPlayPauseButtonType
    typedef NS_ENUM(NSUInteger, UNNotificationContentExtensionMediaPlayPauseButtonType) {  
        // 没有播放按钮
        UNNotificationContentExtensionMediaPlayPauseButtonTypeNone,
        // 有播放按钮,点击播放之后,按钮依旧存在,类似音乐播放的开关
        UNNotificationContentExtensionMediaPlayPauseButtonTypeDefault,
        // 有播放按钮,点击后,播放按钮消失,再次点击暂停播放后,按钮恢复
        UNNotificationContentExtensionMediaPlayPauseButtonTypeOverlay,
    }
    
    设置播放按钮的属性
    // 设置播放按钮的属性
    @property (nonatomic, readonly, assign) UNNotificationContentExtensionMediaPlayPauseButtonType mediaPlayPauseButtonType;
    // 设置播放按钮的frame
    @property (nonatomic, readonly, assign) CGRect mediaPlayPauseButtonFrame;
    // 设置播放按钮的颜色
    @property (nonatomic, readonly, copy) UIColor *mediaPlayPauseButtonTintColor;
    
    // 开始跟暂停播放
    - (void)mediaPlay;
    - (void)mediaPause;
    

    这些属性都是readonly的,所以直接用self.属性去修改肯定是报错的,所以我们能用的就只有get方法了。

    根据button的类型,我们可以联想到,如果button没有,这个播放开始暂停的方法也没用了。如果有button,自然我们就有了播放的操作,我们得出了一定要重写它的frame,来确定他的位置。设置颜色,来设置它的显示颜色。设置button的类型,让他显示出来。

    // 返回默认样式的button
    - (UNNotificationContentExtensionMediaPlayPauseButtonType)mediaPlayPauseButtonType
    {
        return UNNotificationContentExtensionMediaPlayPauseButtonTypeDefault;
    }
    
    // 返回button的frame
    - (CGRect)mediaPlayPauseButtonFrame
    {
        return CGRectMake(100, 100, 100, 100);
    }
    
    // 返回button的颜色
    - (UIColor *)mediaPlayPauseButtonTintColor
    {
        return [UIColor blueColor];
    }
    
    开始跟暂停播放的方法
    // 开始播放
    - (void)mediaPlay
    {
        NSLog(@"mediaPlay,开始播放");
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.extensionContext mediaPlayingPaused];
        });
    }
    
    // 暂停播放
    - (void)mediaPause
    {
        NSLog(@"mediaPause,暂停播放");
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.extensionContext mediaPlayingStarted];
        });
    }
    

    四、极光推送

    1、简介

    a、极光推送的概念

    极光推送(JPush)是独立的第三方云推送平台,致力于为全球移动应用开发者提供移动消息推送服务。极光,是国内领先的移动大数据的服务商。拥有开发者服务、广告服务和数据服务三大产品体系,开发者服务助力app精细运营,覆盖极光推送、极光IM、极光短信、极光统计、社会化分享、极光认证,广告服务,数据服务。说白了,极光推送类似于我们每天在手机上接收到的消息。那我们怎么在自己的app中实现这种功能呢?

    b、准备证书和权限
    ❶ 创建APP ID

    APP ID 是每一个IOS应用的全球唯一标识。无论代码怎么改,图标和应用名称怎么换,只要bundle id没变,ios系统就认为这是同一个应用。每开发一个新应用,首先都需要到member center->identifier->APP IDS去创建一个bundle idExplicit App ID的格式是com.domainname.appname,这种id只能用在一个app上。每一个新应用都要创建一个,其中domainname可以使用公司的缩写,全拼。

    1、登录苹果开发者网站,登录开发者账户。添加新的AppID,并填写相关的NameBundle ID

    创建APP ID

    2、为创建的APP ID 开启Push Notification功能,已有的appID也可以继续添加Push Notification功能。

    添加Push Notification功能

    3、完成以上操作后依次点击Continue,点击Register,完成APP ID的注册。

    ❷ 配置极光推送端需要的两种证书:开发证书,生产证书。
    1. 打开系统里自带的“钥匙串访问”,创建CSRCertificate Signing Request)文件 。填写用户邮箱和常用名称,并选择存储到磁盘,证书文件后缀为.certSigningRequest
    钥匙串访问 钥匙串访问
    1. 点击苹果开发者网站账户左侧的development/Production,上传请求生成的CSR文件。生成证书后,点击downLoad将证书下载到本地中,后缀为.cer文件。点击生成的证书,在钥匙串中打开,导出为.p12文件,并存储到本地。
    配置开发/生产证书 在钥匙串中打开,导出为.p12文件 生成的证书
    ❸ 把配置好的证书,传递到极光开发平台上。

    在极光官网申请好的极光推送账号里创建应用。点击极光开发者服务,找到推送设置,选择iOS,用下载好的开发证书和生产证书导入,填写设置好的p12证书密码,点击保存会生成一个appkey。集成极光推送SDK到项目里的时候会用到此appkey

    c、极光推送的消息形式

    通知(APNS):手机的通知栏(状态栏)上会显示的一条通知信息。
    自定义消息(应用内消息):不会被 SDK 展示到通知栏上。自定义消息主要用于应用的内部业务逻辑。朋友圈红点就可以用这个。极光推送采用的是长连接,所以自定义消息在网络正常、App处于前台的情况下会马上收到。
    本地通知:SDK集成苹果实现本地通知。

    d、极光推送的实现原理

    通过我们的App服务器或极光Web端调用极光的API能发起极光推送。举个例子,用户A(userIdA)发消息给用户B(userIdB)。这里只考虑两个都绑定好了deviceToken等,不存在离线消息。

    苹果原生态下的流程图
    苹果原生态下的流程图
    极光下的流程图
    极光下的流程图
    e、JPush APNS通知的意义

    iOS平台上推送通知,只有APNS这个官方的通道,是可以随时送达的。一般开发者都是自己部署应用服 务器向APNS Server推送。JPush推送相比直接向APNS推送有什么好处呢?

    减少开发及维护成本
    • 应用开发者不需要去开发维护自己的推送服务器与APNS对接
    • 集成了JPush SDK后不必自己维护更新device token
    • 通过JPushWebPortal直接推送,也可以调用JPushHTTP协议API来完成,开发工作量大大减少
    减少运营成本
    • 极光推送支持一次推送,同时向Android和iOS平台。支持统一的API与推送界面
    • 极光推送提供标签、别名绑定机制,以及提供了非常细分的用户分群方式,运营起来非常简单、直观
    提供应用内推送
    • 除了使得APNS推送更简单,也另外提供应用内消息推送,这在类似于聊天的场景里很有必要

    2、项目中极光SDK的配置

    a、导入极光SDK

    方法1:可以通过CocoaPods进行导入JPush
    方法2:手动导入可以参考极光文档-iOS SDK集成指南

    b、进入项目中的appDelegate导入头文件,遵循代理。
    #import "JPUSHService.h"// 引入JPush功能所需头文件
    #import <UserNotifications/UserNotifications.h>// 注册APNs所需头文件
    
    @interface JpushManager ()<JPUSHRegisterDelegate,UNUserNotificationCenterDelegate>
    
    @end
    
    c、在didFinishLaunchingWithOptions中进行JPush的相关初始化设置
    初始化推送
    - (void)setupJPushWithLaunchingOption:(NSDictionary *)launchingOption appKey:(NSString *)appKey channel:(NSString *)channel apsForProduction:(BOOL)isProduction advertisingIdentifier:(NSString *)advertisingId;
    {
        // 添加APNs代码 注册极光
        JPUSHRegisterEntity *entity = [[JPUSHRegisterEntity alloc] init];
        entity.types = JPAuthorizationOptionAlert|JPAuthorizationOptionBadge|JPAuthorizationOptionSound;
        [JPUSHService registerForRemoteNotificationConfig:entity delegate:self];
        
        // 可以添加自定义categories
        NSSet<UNNotificationCategory *> *categories;
        entity.categories = categories;
        
        // IDFA为设备广告标示符,用于广告投放。通常不会改变,不同App获取到都是一样的。但如果用户完全重置系统((设置程序 -> 通用 -> 还原 -> 还原位置与隐私) ,这个广告标示符会重新生成。
        // IDFA用于同一设备下的不同app信息共享,如不需要使用,advertisingIdentifier 可为nil
        // NSString *advertisingId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
        [JPUSHService setupWithOption:launchingOption appKey:appKey channel:channel apsForProduction:isProduction advertisingIdentifier:advertisingId];
        
        // 获取极光推送注册ID(RegistrationID)
        // 原生是采用deviceToken来标识设备唯一性。在极光中采用RegistrationID
        // 其生成原则优先采用IDFA(如果设备未还原IDFA,卸载App后重新下载,还是能被识别出老用户),次采用deviceToken
        // 集成了 JPush SDK 的应用程序在第一次 App 启动后,成功注册到 JPush 服务器时,JPush 服务器会给客户端返回唯一的该设备的标识 -—— RegistrationID
        [JPUSHService registrationIDCompletionHandler:^(int resCode, NSString *registrationID) {
            if(resCode == 0)
            {
                NSLog(@"registrationID 获取成功为:%@",registrationID);
                
                // 设置别名
                // 一个设备只能有一个别名(Alias),但能有多个标签。所以别名可以用userId,针对一个用户
                // 标签(Tag)可以用用户所处分组,方便针对目标用户推送,针对一批用户
                [JPUSHService setAlias:[[NSUserDefaults standardUserDefaults] valueForKey:@"userId"]  completion:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
                    
                    NSLog(@"设置别名");
                    
                } seq:0];
            }
            else
            {
                NSLog(@"registrationID 获取失败,code为:%d",resCode);
            }
        }];
    }
    
    didFinishLaunching中极光推送的配置
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {  
        // 极光推送
        [self configureJpushWithLaunchingOption:launchOptions];
    
        return YES;
    }
    
    - (void)configureJpushWithLaunchingOption:(NSDictionary *)launchingOption
    {
        // 初始化推送
        [[JpushManager shareManager] setupJPushWithLaunchingOption:launchingOption appKey:JPushAppKey channel:JPushChannel apsForProduction:isProduction advertisingIdentifier:nil];
        
        // 设置角标为0
        [[JpushManager shareManager] setBadge:0];
        
        __weak __typeof(self)weakSelf = self;
        [JpushManager shareManager].afterReceiveNoticationHandle = ^(NSDictionary *userInfo){
            NSLog(@"接收到消息后处理消息");
            
            [weakSelf getMessageToHandle];
        };
    }
    
    d、注册DevieceToken
    // 在appdelegate注册设备处调用
    - (void)registerDeviceToken:(NSData *)deviceToken
    {
        [JPUSHService registerDeviceToken:deviceToken];
    }
    
    // 远端推送需要获取设备的Device Token
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
    {  
        NSLog(@"设备的Device Token为:%@", deviceToken);
        
        // 极光推送注册 DeviceToken
        [[JpushManager shareManager] registerDeviceToken:deviceToken];
    }
    

    3、JPUSHRegisterDelegate

    a、收到通知消息后展示
    - (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger))completionHandler
    {
        NSDictionary *userInfo = notification.request.content.userInfo;
        if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]])
        {
           [JPUSHService handleRemoteNotification:userInfo];
            
            if (self.afterReceiveNoticationHandle)
            {
                self.afterReceiveNoticationHandle(userInfo);
            }
        }
        
        // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Banner三种类型可以设置
        completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionBanner);
    }
    
    b、点击通知进入App
    - (void)jpushNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
    {
        NSDictionary * userInfo = response.notification.request.content.userInfo;
        UNNotificationRequest *request = response.notification.request;// 收到推送的请求
        UNNotificationContent *content = request.content;// 收到推送的消息内容
        NSNumber *badge = content.badge;// 推送消息的角标
        NSString *body = content.body;// 推送消息体
        UNNotificationSound *sound = content.sound;// 推送消息的声音
        NSString *subtitle = content.subtitle;// 推送消息的副标题
        NSString *title = content.title;// 推送消息的标题
        
        NSLog(@"点击通知栏,收到远程通知的用户信息为:%@", userInfo);
        NSLog(@"解析后信息为:{\nbody:%@,\ntitle:%@,\nsubtitle:%@,\nbadge:%@,\nsound:%@,\nuserInfo:%@\n}",body,title,subtitle,badge,sound,userInfo);
        
        // 清空Jpush中存储的badge值
        [self setBadge:0];
        
        if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]])// 远程通知
        {
            NSLog(@"远程通知");
        }
        else// 本地通知
        {
            NSLog(@"本地通知");
        }
        
        [JPUSHService handleRemoteNotification:userInfo];
    
        // 点击消息进行跳转到消息的详情界面中
        // [self goToMssageViewControllerWith:userInfo];
        
        // 系统要求执行这个方法
        completionHandler();
    }
    
    c、点击通知打开设置APP
    - (void)jpushNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(UNNotification *)notification
    {
        if (notification && [notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]])
        {
            NSLog(@"通知界面进入应用");
        }
        else
        {
            NSLog(@"设置界面进入应用");
        }
    }
    

    4、封装的便利方法

    a、设置角标
    - (void)setBadge:(int)badge
    {
        [[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge];
        [JPUSHService setBadge:badge];
    }
    
    b、设置别名
    - (void)setAlias:(NSString *)aliasName
    {
        [JPUSHService getAlias:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
            NSLog(@"旧的别名为:iResCode == %ld,iAlias == %@",(long)iResCode,iAlias);
            
            if (![iAlias isEqualToString:aliasName])
            {
                [JPUSHService setAlias:aliasName completion:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
                    
                    NSLog(@"设置新的别名:callBackTextView %@",[NSString stringWithFormat:@"iResCode:%ld, \niAlias: %@, \nseq: %ld\n", (long)iResCode, iAlias, (long)seq]);
                    
                } seq:0];
                
            }
            
        } seq:0];
    }
    
    c、删除别名
    - (void)deleteAlias
    {
        [JPUSHService deleteAlias:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
            NSLog(@"删除别名");
        } seq:0];
    }
    
    d、接收到消息后的处理
    // 收到推送的消息后的回调
    typedef void(^AfterReceiveNoticationHandle)(NSDictionary *userInfo);
    
    /** 接收到消息后的处理 */
    @property(copy,nonatomic) AfterReceiveNoticationHandle afterReceiveNoticationHandle;
    
    // 传递消息
    NSDictionary *userInfo = notification.request.content.userInfo;
    if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]])
    {
       [JPUSHService handleRemoteNotification:userInfo];
        
        if (self.afterReceiveNoticationHandle)
        {
            self.afterReceiveNoticationHandle(userInfo);
        }
    }
    
    __weak __typeof(self)weakSelf = self;
    [JpushManager shareManager].afterReceiveNoticationHandle = ^(NSDictionary *userInfo){
        NSLog(@"接收到消息后处理消息");
        
        [weakSelf getMessageToHandle];
    };
    
    // 接收到消息后处理消息
    - (void)getMessageToHandle
    {
        NSLog(@"这条消息价值百万英镑!!!")
    }
    

    Demo

    Demo在我的Github上,欢迎下载。
    PushServiceDemo

    参考文献

    相关文章

      网友评论

          本文标题:IOS基础:推送服务

          本文链接:https://www.haomeiwen.com/subject/hevvmktx.html