IOS基础使用:推送服务

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的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
  • 参考文献

一、简介

日常生活中会有很多种情形需要通知,比如:新闻提醒、定时吃药、定期体检、到达某个地方提醒用户等等,这些功能在 UserNotifications 中都提供了相应的接口。

iOS推送分为Local Notifications(本地推送) 和 Remote Notifications(远程推送)。本地推送是App本地创建通知,加入到系统的Schedule里,如果触发器条件达成时会推送相应的消息内容。

在远程推送中,Provider是指某个APP的Push服务器。APNSApple Push Notification ServiceApple Push服务器)的缩写,是苹果的服务器。APNS Pusher应用程序把要发送的消息、目标iPhone的标识(deviceToken)打包,发给APNSAPNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发到iPhoneiPhone把发来的消息传递给相应的应用程序, 并且按照设定弹出Push通知。

如果你的App有远端推送的话,那你需要用开发者账号新建一个push证书。再在Capabilities中打开Push Notifications 开关,打开后会自动在项目里生成entitlements文件。


二、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、准备工作

  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

Pusher的下载地址

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

Pusher的使用步骤

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

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

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:我们需要传送的内容
  • alert:弹出框需要展示的内容
  • badge:展示的信息个数
  • sound:表示当有Push消息的时候,是否需要声音提示
稍纵即逝你就收到了远端消息了

可惜没开发者账户无法测试,连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
{
    // 将获取到的内容传递给content扩展
    self.contentHandler(self.bestAttemptContent);
}
Notification Content

在这儿做界面初始化的工作。

- (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

@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
{
    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的相关初始化设置
在didFinishLaunching方法中调用极光推送的配置方法
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{  
    // 极光推送
    [self configureJpushWithLaunchingOption:launchOptions];

    return YES;
}
声明配置过程中使用到的全局常量
static NSString * const JPushAppKey = @"e98bc4beea9b2988976bae04";// 极光appKey
static NSString * const JPushChannel = @"Publish channel";// 固定的
// NO为开发环境,YES为生产环境。虚拟机和真机调试属于开发环境。测试包、企业包和App Store属于生产环境
BOOL isProduction = !DEBUG;
BOOL kFUserJPush = NO;
实现极光推送的配置方法
- (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];
    };
}
初始化推送
- (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);
        }
    }];
}

d、注册DevieceToken
远端推送需要获取设备的Device Token
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{  
    NSLog(@"设备的Device Token为:%@", deviceToken);
    
    // 极光推送注册 DeviceToken
    [[JpushManager shareManager] registerDeviceToken:deviceToken];
}
在appdelegate注册设备处调用极光推送注册 DeviceToken
- (void)registerDeviceToken:(NSData *)deviceToken
{
    [JPUSHService registerDeviceToken:deviceToken];
}

3、JPUSHRegisterDelegate

a、收到通知消息后展示
// 收到推送的消息后的回调
typedef void(^AfterReceiveNoticationHandle)(NSDictionary *userInfo);

/** 接收到消息后的处理 */
@property(copy,nonatomic) AfterReceiveNoticationHandle afterReceiveNoticationHandle;
- (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、接收到消息后的处理
__weak __typeof(self)weakSelf = self;
[JpushManager shareManager].afterReceiveNoticationHandle = ^(NSDictionary *userInfo){
    NSLog(@"接收到消息后处理消息");
    
    [weakSelf getMessageToHandle];
};

// 接收到消息后处理消息
- (void)getMessageToHandle
{
    NSLog(@"这条消息价值百万英镑!!!")
}

Demo

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

参考文献

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容