笔者这几天刚完成《小印记》的推送功能,今天特分享一下在作的过程当中实际解决的问题。若是读者学到了有用的东西,但愿能前往App Store下载《小印记》支持一下笔者,谢谢!前端
《小印记》iOS源码分享--网络层封装篇xcode
须要服务器代码的请移步:《小印记》源码分享--极光推送服务器篇服务器
笔者使用的是用的比较普遍的极光推送,还有其余的什么百度推送、友盟推送等其实原理是同样的,至于选择哪一个全凭读者喜爱。说一下本文将要解决的几个问题:网络
APP处于前台运行状态时,提示远程推送消息并保存通知内容;app
APP处于后台运行状态时,提示远程推送消息并保存通知内容;ide
APP处于退出状态时,提示远程推送消息并保存通知内容;fetch
发消息给指定的用户。网站
关于推送证书的配置网上有不少的详细教程,这里再也不赘述,推荐一篇比较好的博客-> iOS 推送通知 功能简单实现 。照着里面的步骤完成后,咱们获得了这么几个文件:
四个证书
前两个用于xcode的调试与发布
后两个用于极光推送的证书配置
两个配置文件
一个带已配置远程推送的APP IDs文件
iOS的代码配置笔者推荐最好去极光推送的官网去下载-> Demo 。这里既然说是源码分享,因此贴上笔者的代码,仅供参考:
AppDelegate.h
static NSString *appKey = @"*******************"; static NSString *channel = @"App Store"; static BOOL isProduction = TRUE;
AppDelegate.m
#import "AppDelegate.h" #import "JKChooseRootVCHelper.h" #import "JKUtilsHelper.h" #import "JKRemoteNoticeModel.h" // 引入JPush功能所需头文件 #import "JPUSHService.h" // iOS10注册APNs所需头文件 #ifdef NSFoundationVersionNumber_iOS_9_x_Max #import <UserNotifications/UserNotifications.h> #endif - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; // 选择根控制器 [JKChooseRootVCHelper chooseRootViewController:self.window]; [self.window makeKeyAndVisible]; /******* 极光推送配置 ********/ // 获取自定义消息 NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; [defaultCenter addObserver:self selector:@selector(networkDidReceiveMessage:) name:kJPFNetworkDidReceiveMessageNotification object:nil]; // notice: 3.0.0及之后版本注册能够这样写,也能够继续用以前的注册方式 JPUSHRegisterEntity * entity = [[JPUSHRegisterEntity alloc] init]; entity.types = JPAuthorizationOptionAlert|JPAuthorizationOptionBadge|JPAuthorizationOptionSound; if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) { // 点击通知横幅启动app时获取APNS内容(代理方法也能够处理),能够在这里跳转到指定的界面 // NSDictionary *remoteNotification = [launchOptions objectForKey: UIApplicationLaunchOptionsRemoteNotificationKey]; // NSInteger unReadCount = [[NSUserDefaults standardUserDefaults] integerForKey:kUnReadCount]; if (unReadCount == 0) { [UIApplication sharedApplication].applicationIconBadgeNumber = 0; } } [JPUSHService registerForRemoteNotificationConfig:entity delegate:self]; // 如不须要使用IDFA,advertisingIdentifier 可为nil [JPUSHService setupWithOption:launchOptions appKey:appKey channel:channel apsForProduction:isProduction advertisingIdentifier:nil]; /**************************/ return YES; } // 注册APNs成功并上报DeviceToken - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [JPUSHService registerDeviceToken:deviceToken]; JKLog(@"--Device Token: %@", deviceToken); } // 实现注册APNs失败接口(可选) - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { //Optional JKLog(@"--did Fail To Register For Remote Notifications With Error: %@", error); } #pragma mark- JPUSHRegisterDelegate // 前台收到 APNs 通知后就会走这个方法,iOS 10 Support - (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger))completionHandler { // Required NSDictionary * userInfo = notification.request.content.userInfo; // UNNotificationRequest *request = 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; // 推送消息的标题 if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) { [JPUSHService handleRemoteNotification:userInfo]; JKLog(@"--iOS10 前台收到远程通知:%@", userInfo); // [self logDic:userInfo] } else { // 判断为本地通知 // JKLog(@"--iOS10 前台收到本地通知:{\nbody:%@,\ntitle:%@,\nsubtitle:%@,\nbadge:%@,\nsound:%@,\nuserInfo:%@\n}", body,title,subtitle,badge,sound,userInfo); } completionHandler(UNNotificationPresentationOptionAlert); // 须要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型能够选择设置 // UNNotificationPresentationOptionBadge、UNNotificationPresentationOptionSound、UNNotificationPresentationOptionAlert } // 在前台点击通知消息后走该方法(即后台收到通知后,点击通知的回调方法),iOS 10 Support - (void)jpushNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler { // Required 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; // 推送消息的标题 if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) { [JPUSHService handleRemoteNotification:userInfo]; JKLog(@"--iOS10 前台收到远程通知:%@", userInfo); // [self logDic:userInfo] } else { // 判断为本地通知 // JKLog(@"--iOS10 前台收到本地通知:{\nbody:%@,\ntitle:%@,\nsubtitle:%@,\nbadge:%@,\nsound:%@,\nuserInfo:%@\n}", body,title,subtitle,badge,sound,userInfo); } completionHandler(); // 系统要求执行这个方法 }
而后还得打开xcode的远程推送设置:
其实,完成上面的步骤就已经解决问题1了,如今APP在前端运行时就能收到远程推送通知并能获取通知内容,可是处于后台运行时,APP虽然能提示消息却没法获取通知内容,更别说APP处于退出状态了,因此接下来就解决剩下的两个问题。
首先咱们要明白远程推送通知的几种类型,远程推送通知分为 普通推送/后台推送/静默推送 3 种类型。上面介绍的其实就是普通推送,然后台推送则容许开发者在 App 处于后台的状况下,执行一些代码,咱们能够用这种方式来获取通知内容。具体作法以下:
1)首先在xcode里开启Remote notifications:
2)在 AppDelegate.m 中处理后台推送通知:
// 后台推送处理 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { // Required, iOS 7 Support [JPUSHService handleRemoteNotification:userInfo]; JKLog(@"--后台收到远程通知:%@", userInfo); // 保存通知消息 [[JKDBManagerHelper sharedInstance] insertRemoteNotice:[JKRemoteNoticeModel mj_objectWithKeyValues:userInfo]]; completionHandler(UIBackgroundFetchResultNewData); }
3)发送通知时勾选 content-available 选项:
通过这三步,问题2 就能获得解决了 ^O^
解决这个问题前咱们须要了解 应用内消息(即自定义消息 )与推送通知的区别,对比于推送通知,应用内消息:
因此,要想在APP处于退出状态时也能获取通知内容,就须要用到应用内消息,具体作法以下:
1)在 AppDelegate.m 中处理应用内消息:
// 获取自定义消息内容 // 自定义消息无需考虑环境和证书问题 // JPush 的应用内消息,会免费保留 5 条离线消息(付费可保留100条) - (void)networkDidReceiveMessage:(NSNotification *)notification { NSDictionary * userInfo = [notification userInfo]; JKLog(@"--收到自定义远程通知:%@", userInfo); NSString *content = [userInfo valueForKey:@"content"]; NSString *title = [userInfo valueForKey:@"title"]; NSDictionary *contentDic = [JKUtilsHelper dictionaryWithJsonString:content]; NSDictionary *titleDic = [JKUtilsHelper dictionaryWithJsonString:title]; NSDictionary *alertDict = [NSDictionary dictionaryWithObjectsAndKeys:contentDic, @"alert", nil]; NSDictionary *noticeDict = [NSDictionary dictionaryWithObjectsAndKeys:@"", @"_j_msgid", alertDict, @"aps", titleDic, @"extra", nil]; JKLog(@"--格式化后的数据:%@", noticeDict); // 保存通知消息 [[JKDBManagerHelper sharedInstance] insertRemoteNotice:[JKRemoteNoticeModel mj_objectWithKeyValues:noticeDict]]; // 改成本地通知提示 // UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; }
特别说明:笔者这里将 alert 的 title/subtitle/body 封装到了content 中,附加字段封装到了 title 中。
2)须要解决一个重复添加通知内容的问题。由于普通推送、后台推送、自定义消息都能获取通知内容,若是都对接收的通知内容进行保存则会出现重复添加的问题。因此笔者在服务器发出一条远程推送消息时,会附加一个customId(也就是自定义消息ID),若是本地已经保存了某个customId,就再也不保存了。
3)在极光推送网站或本身的服务器发送一条附带customId的消息。
好了,问题3 也顺利解决了
这个问题很简单,看极光推送官网的文档里面有说明 标签与别名API(iOS),说下具体步骤。 1)在用户登陆和注册成功后的回调里设置标签和别名:
// 设置标签与别名 NSMutableArray *setsArray = [NSMutableArray array]; NSDictionary *userInfo = responseObject[@"result"]; _userId = [NSString stringWithFormat:@"%d", [userInfo[@"id"] intValue]]; if ([userInfo[@"gender"] intValue] == 0) { _gender = @"man"; } else { _gender = @"woman"; } if ([userInfo[@"role_type"] intValue] == 0) { _roleType = @"common"; } else { _roleType = @"special"; } if ([userInfo[@"vip"] intValue] == 1) { [setsArray addObject:@"vip"]; } [setsArray addObject:_gender]; [setsArray addObject:_roleType]; [setsArray addObject:@"iOS"]; NSSet *sets = [NSSet setWithArray:setsArray]; [JPUSHService setTags:sets alias:_userId fetchCompletionHandle:^(int iResCode, NSSet *iTags, NSString *iAlias) { if (iResCode == 0) { JKLog(@"--success to set tags and alias:%@, %@", iTags, iAlias); } else { JKLog(@"--failed to set tags and alias:%d", iResCode); [JKProgressHUDHelper showMessage:@"远程推送设置失败,请从新登陆!"]; [JKProgressHUDHelper hideHUDAfterDelay:1.5]; } }];
2)在用户退出的地方清空设置:
[JPUSHService setTags:[NSSet set] alias:@"" fetchCompletionHandle:^(int iResCode, NSSet *iTags, NSString *iAlias) { if (iResCode == 0) { JKLog(@"--success to clear tags and alias:%@, %@", iTags, iAlias); } }];
3)发送消息的时候指定Alias或Tags,就只有指定的用户能收到消息了。
到此,问题4也解决了。指定用户发送消息的功能运用的场景有不少,好比回复评论后提醒对方、指定VIP用户进行推送等等。
最后附上《小印记》截图,但愿读者多多支持