iOS 消息推送

[TOC]

一、工作机制:

APNS 是 Apple Push Notification Service 的缩写,是苹果服务器。

image

工作流程如下所示:

  • 首先app向iOS注册推送消息
  • iOS收到app的注册后,向APNS Server索要deviceToken,app接收返回的deviceToken
  • app把接收到的deviceToken传给我们的服务器
  • 当我们的服务器需要推送消息时,就把要推送的消息和deviceToken发送给APNS Server
  • APNS Server服务将推送的消息发送给app

二、app代码实现:

1、注册消息推送:

#import <UserNotifications/UserNotifications.h>

// 注册APNS推送
- (void)registerRemoteNotification {
    if (@available(iOS 10.0, *)) {
        UNUserNotificationCenter *notificationCenter = [UNUserNotificationCenter currentNotificationCenter];
        // 必须写代理,不然无法监听通知的接收与点击事件
        notificationCenter.delegate = self;
        // 请求授权
        [notificationCenter requestAuthorizationWithOptions:UNAuthorizationOptionAlert
                                          completionHandler:^(BOOL granted, NSError * _Nullable error) {
                                              if (granted && !error) {
                                                  // 用户同意授权,注册远程推送
                                                  [[UIApplication sharedApplication] registerForRemoteNotifications];
                                              } else {
                                                  // 用户不同意授权
                                              }
                                          }];
        // 获取用户同意的或者更改的推送权限设置
        [notificationCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
            NSLog(@"%@",settings);
        }];
        
    } else {
        // iOS8 ~ iOS10
        if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
            UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert categories:nil];
            [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
            
        } else {
            [[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert];
        }
    }
}

2、把拿到到deviceToken传给我们的服务器:

// 获取deviceToken成功
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    
    // 把deviceToken转为NSString
    NSString *deviceStr = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
    deviceStr = [deviceStr stringByReplacingOccurrencesOfString:@" " withString:@""];
    
    // 把deviceToken传给我们自己的服务器
    
}

// 获取deviceToken失败
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    NSLog(@"error:%@",error.description);
}

3、app接收到远程推送的消息:

#pragma mark iOS10 收到通知(本地和远程)

// App接收通知时
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler  API_AVAILABLE(ios(10.0)){
    // 收到推送的请求
    UNNotificationRequest *request = notification.request;
    // 收到推送的内容
    UNNotificationContent *content = request.content;
    // 收到推送的基本信息
    NSDictionary *userInfo = content.userInfo;
    // 收到推送消息的角标
    NSNumber *badge = content.badge;
    // 推送消息的声音
    UNNotificationSound *sound = content.sound;
    // 推送消息的副标题
    NSString *subtitle = content.subtitle;
    // 推送消息的标题
    NSString *title = content.title;
    // 推送消息的类型
    if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        // 远程通知
    } else if ([request.trigger isKindOfClass:[UNTimeIntervalNotificationTrigger class]]) {
        // 本地通知,一定时间之后,重复或者不重复推送通知 我们可以设置timeInterval(时间间隔)和repeats(是否重复)。
    } else if ([request.trigger isKindOfClass:[UNCalendarNotificationTrigger class]]) {
        // 本地通知,一定日期之后,重复或者不重复推送通知 例如,你每天8点推送一个通知,只要dateComponents为8,如果你想每天8点都推送这个通知,只要repeats为YES就可以了
    } else if ([request.trigger isKindOfClass:[UNLocationNotificationTrigger class]]) {
        // 本地通知,地理位置的一种通知,当用户进入或离开一个地理区域来通知。
    }
    
    // 执行下面方法,选择提醒用户的方式
    completionHandler(UNNotificationPresentationOptionBadge|
                      UNNotificationPresentationOptionSound|
                      UNNotificationPresentationOptionAlert);
}

// App通知的点击事件,只会是用户点击消息才会触发,如果使用户长按(3DTouch)、Action等并不会触发。
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)(void))completionHandler  API_AVAILABLE(ios(10.0)){
    // 收到推送的请求
    UNNotificationRequest *request = response.notification.request;
    
    // 。。。
    
    // 系统要求执行这个方法
    completionHandler();
}



#pragma mark iOS10 之前收到通知

// iOS10以下收到本地推送通知
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
    
}

// iOS7及以上收到通知
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    
    completionHandler(UIBackgroundFetchResultNewData);
}

// iOS6及以下收到通知
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    
}

三、本地推送:

本地推送这里主要分两个版本来介绍,一个是iOS 10及以后,一个是iOS 10以前;

1、iOS 10及以上本地推送生成流程:

  • 创建一个触发器 trigger

  • 创建推送的内容 UNMutableNotificationContent

  • 创建推送请求 UNNotificationRequest

  • 推送请求添加到推送管理中心 UNUserNotificationCenter

    - (void)postLocalNotificatonUpper10 API_AVAILABLE(ios(10.0)) {
        
        // 1. 创建一个触发器(trigger),这里以 UNTimeIntervalNotificationTrigger 为例
        UNTimeIntervalNotificationTrigger *timeTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:30 repeats:NO];
        
        // 2. 创建推送的内容 UNMutableNotificationContent
        UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
        content.title = @"title";
        content.subtitle = @"subtitle";
        content.body = @"body";
        content.badge = @2;
        content.sound = [UNNotificationSound defaultSound];
        content.userInfo = @{@"key1" : @"value1", @"key2" : @"value2"};
        
    //    // 推送交互操作
    //    content.categoryIdentifier = @"Dely_locationCategory";
    //    [self addNotificationAction];
    
        // 3. 创建推送请求 UNNotificationRequest
        UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"identifier"
                                                                              content:content
                                                                              trigger:timeTrigger];
        
        // 4. 推送请求添加到推送管理中心 UNUserNotificationCenter
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        [center addNotificationRequest:request
                 withCompletionHandler:^(NSError * _Nullable error) {
                     if (!error) {
                         NSLog(@"推送已添加成功");
                     }
                 }];
    }
    

2、iOS 10以下本地推送生成流程:

  • 创建本地通知对象 UILocalNotification
  • 把本地通知对象加入到app日程表中
- (void)postLocalNotificatonBelow10 {
    /*
     fireDate:启动时间
     timeZone:启动时间参考的时区
     repeatInterval:重复推送时间(NSCalendarUnit类型),0代表不重复
     repeatCalendar:重复推送时间(NSCalendar类型)
     alertBody:通知内容
     alertAction:解锁滑动时的事件
     alertLaunchImage:启动图片,设置此字段点击通知时会显示该图片
     alertTitle:通知标题,适用iOS8.2之后
     applicationIconBadgeNumber:收到通知时App icon的角标
     soundName:推送是带的声音提醒,设置默认的字段为UILocalNotificationDefaultSoundName
     userInfo:发送通知时附加的内容
     category:此属性和注册通知类型时有关联,(有兴趣的同学自己了解,不详细叙述)适用iOS8.0之后
     
     region:带有定位的推送相关属性,具体使用见下面【带有定位的本地推送】适用iOS8.0之后
     regionTriggersOnce:带有定位的推送相关属性,具体使用见下面【带有定位的本地推送】适用iOS8.0之后
     */
    
    // 1. 创建本地通知对象 UILocalNotification
    UILocalNotification *localNotification = [[UILocalNotification alloc] init];
    localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:30];
    localNotification.alertBody = @"通知显示内容";
    localNotification.alertAction = @"解锁滑动是的事件";
    localNotification.applicationIconBadgeNumber = 1;
    localNotification.soundName = UILocalNotificationDefaultSoundName;
    
    // 2. 把本地通知对象加入到app日程表中
    [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
    
    // 立即发送通知
//    [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
    
}

四、iOS 10推送交互操作:

iOS 10中,可以允许推送添加交互操作 action,这些 action 使得app可以在前台或者后台执行一些逻辑代码。这是推送功能的一个拓展,可通过3D-Touch触发,或者右滑会出现view和clear选项来触发。

  • 创建action
  • 创建category
  • 把category添加到通知中心
// 添加推送交互操作
- (void)addNotificationAction API_AVAILABLE(ios(10.0)) {
    /*
     1.需要解锁显示,点击不会进app
     UNNotificationActionOptionAuthenticationRequired
     2.点击不会进app
     UNNotificationActionOptionDestructive
     3.点击会进app
     UNNotificationActionOptionForeground
     */
    // 1. 创建action
    UNNotificationAction *lookAction =
    [UNNotificationAction actionWithIdentifier:@"action.join"
                                         title:@"接收邀请"
                                       options:UNNotificationActionOptionAuthenticationRequired];
    UNNotificationAction *joinAction =
    [UNNotificationAction actionWithIdentifier:@"action.look"
                                         title:@"查看邀请"
                                       options:UNNotificationActionOptionForeground];
    UNNotificationAction *cancelAction =
    [UNNotificationAction actionWithIdentifier:@"action.cancel"
                                         title:@"取消"
                                       options:UNNotificationActionOptionDestructive];
    
    // 2. 创建category
    // * identifier 标识符
    // * actions 操作数组
    // * intentIdentifiers 意图标识符 可在 <Intents/INIntentIdentifiers.h> 中查看,主要是针对电话、carplay 等开放的 API。
    // * options 通知选项 枚举类型 也是为了支持 carplay
    UNNotificationCategory *category =
    [UNNotificationCategory categoryWithIdentifier:@"Dely_locationCategory"
                                           actions:@[lookAction, joinAction, cancelAction]
                                 intentIdentifiers:@[]
                                           options:UNNotificationCategoryOptionCustomDismissAction];
    
    // 3. 把category添加到通知中心
    [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObject:category]];
    
}

// 添加推送文本输入交互操作
- (void)addTextNotificationAction API_AVAILABLE(ios(10.0)) {
    // 创建 UNTextInputNotificationAction 比 UNNotificationAction 多了两个参数
    // * buttonTitle 输入框右边的按钮标题
    // * placeholder 输入框占位符
    UNTextInputNotificationAction *inputAction =
    [UNTextInputNotificationAction actionWithIdentifier:@"action.input"
                                                  title:@"输入"
                                                options:UNNotificationActionOptionForeground
                                   textInputButtonTitle:@"发送"
                                   textInputPlaceholder:@"tell me loudly"];
    
    // 注册 category
    UNNotificationCategory *notificationCategory =
    [UNNotificationCategory categoryWithIdentifier:@"Dely_locationCategory"
                                           actions:@[inputAction]
                                 intentIdentifiers:@[]
                                           options:UNNotificationCategoryOptionCustomDismissAction];
    
    [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObject:notificationCategory]];
}
  • 事件的操作
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)(void))completionHandler  API_AVAILABLE(ios(10.0)){
    // iOS 10推送交互事件的操作,在这里处理
    //点击或输入action
    NSString* actionIdentifierStr = response.actionIdentifier;
    
    //输入
    if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) {
        NSString* userSayStr = [(UNTextInputNotificationResponse *)response userText];
    }
    
    //点击
    if ([actionIdentifierStr isEqualToString:@"action.join"]) {
    } else if ([actionIdentifierStr isEqualToString:@"action.look"]) {
    }
    
    
    // 系统要求执行这个方法
    completionHandler();
}

注意,远程推送一定要保证 category 的键值对是一致的

{
  "aps" : {
    "alert" : {
      "title" : "iOS远程消息,我是主标题!-title",
      "subtitle" : "iOS远程消息,我是主标题!-Subtitle",
      "body" : "Dely,why am i so handsome -body"
    },
    "category" : "Dely_locationCategory",
    "badge" : "2"
  }
}

五、Reference

iOS开发,本地推送的使用

iOS 10 消息推送(UserNotifications)秘籍总结(一)

iOS 10 消息推送(UserNotifications)秘籍总结(二)

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