ios~ APNs (Apple 远程通知服务)

注意事项:
  1. 需要打开通知权限,
  2. 推送证书、设置info.plist文件,设置自动生成的 .entitlements文件
    【本地和远程通知编程指南】:
    apple api文档: 设置远程通知服务器

    • 官网:生成远程通知负载
    • 官网:使用 APNs 注册您的应用程序
  • 创建远程通知负载
  • 配置远程通知支持
    • 客户端技术:一文带你了解 iOS 消息推送机制
    • APNS苹果远程推送

他人的demo:github
APNs (Apple远程推送通知服务)远程通知
本地通知、远程通知
本地通知和远程通知 (使用APNs).

代码:

//
//  AppDelegate.m
//

#import "AppDelegate.h"
#import 

@interface AppDelegate ()

@end

@implementation AppDelegate
// 远程推送APNS优点:长连接、离线状态也可以、安装identifier分组通知

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // 检查网络
    [self checkNetword];

    //注册远程推送服务
    //1.enable the Push Notifications capability in your Xcode project
    
    CGFloat version = [[[UIDevice currentDevice] systemVersion] floatValue];
    
    //设置通知类型
    if (version >= 10.0)
        {
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        center.delegate = self;
        /// 在设备可以接受任何通知之前,调用请求授权会产生提示(是否打开通知,“允许”、“不允许”):requestAuthorization
        [center requestAuthorizationWithOptions:UNAuthorizationOptionCarPlay | UNAuthorizationOptionSound | UNAuthorizationOptionBadge | UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error) {
            
            if (granted) {
                NSLog(@" iOS 10 request notification success");
            }else{
                NSLog(@" iOS 10 request notification fail");
            }
        }];
        }
    else if (version >= 8.0)
        {
        UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeSound | UIUserNotificationTypeBadge | UIUserNotificationTypeAlert categories:nil];
        [application registerUserNotificationSettings:setting];
        }else
            {     //iOS <= 7.0
                UIRemoteNotificationType type = UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound;
                [application registerForRemoteNotificationTypes:type];
            }
    
    //2.注册app 适用于iOS 8+
    [[UIApplication sharedApplication] registerForRemoteNotifications];
    // APNs内容获取,如果apn有值,则是通过远程通知点击进来
    NSDictionary *apn = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
    
    return YES;
}

// 新打开/后台到前台/挂起到运行 均调用  (挂起比如来电/双击home)
- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

    // App icon上的徽章清零 (APNs)
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];

}

/// 2. 注册成功调用的方法 (远程推送通知):成功获取deviceToken,你需要将令牌deviceToken发送到后端推送服务器,这样你就可以发送通知到此设备上了
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    // token 不要本地缓存,当你重新启动、用户换个手机、升级系统都会返回新的token
    // 安全的加密上传到我们的服务器❗️❗️❗️
    NSString *deviceTokenStr = [self deviceTokenStrWithDeviceToken:deviceToken];
    
    NSLog(@"注册远程通知 成功 deviceToken:%@, deviceTokenStr:%@", deviceToken, deviceTokenStr);

    if (deviceToken) {
        // (将器转换成字符串,发送后台)
        /// 为了实现这一点,可以将数组分离到其组件中,再将这些组件转换为十六进制字符串,然后将它们重新连接到字符串
        NSMutableString *deviceTokenString = [NSMutableString string];
        const char *bytes = deviceToken.bytes;
        NSInteger count = deviceToken.length;
        for (int i = 0; i < count; i++) {
            [deviceTokenString appendFormat:@"%02x", bytes[i]&0x000000FF];
        }

        //NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
       // [defaults setObject: deviceTokenString forKey: @"phoneToken"];
       //[defaults synchronize];

        //上传远程通知deviceToken到我们的后台服务器
        
    }
}

///2. 注册失败调用的方法 (远程推送通知):未获得为设备提供的令牌deviceToken时调用该方法,并显示为什么注册失败
-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    //失败之后,找个合适机会再试一次
    NSLog(@"注册远程通知 失败 error:%@", error);
    
}
#pragma mark - app收到通知调用的方法
#pragma mark - ios 10+  “后台通知:在APP未运行的情况下,也可以保持及时更新”
// ios 10+ : Asks the delegate how to handle a notification that arrived while the app was running in the foreground.
/// 仅当App在前台运行时:此时用户正在使用 App,收到推送消息时默认不会弹出消息提示框,  准备呈现通知时, 才会调用该委托方法.
/// 一般在此方法里选择将通知显示为声音, 徽章, 横幅, 或显示在通知列表中.
/// @param center 用户通知中心
/// @param notification 当前通知
/// @param completionHandler 回调通知选项: 横幅, 声音, 徽章...
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {

    UNNotificationRequest *request = notification.request;
    UNNotificationContent *conten = request.content;
    NSDictionary *userInfo = conten.userInfo;

    if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        NSLog(@"即将展示远程通知");
    }else {
        NSLog(@"即将展示本地通知");
    }
    NSLog(@"title:%@, subtitle:%@, body:%@, categoryIdentifier:%@, sound:%@, badge:%@, userInfo:%@", conten.title, conten.subtitle, conten.body, conten.categoryIdentifier, conten.sound, conten.badge, userInfo);
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:[[NSString stringWithFormat:@"%@", conten.badge] integerValue]];
    // 以下是在App前台运行时, 仍要显示的通知选项
    completionHandler(UNNotificationPresentationOptionAlert + UNNotificationPresentationOptionSound + UNNotificationPresentationOptionBadge);

}

// ios 10+ : 用户点击远程通知启动app,此时用户点击推送消息会将 App 从后台唤醒,(后台进入)“提醒通知”
/// 当用户通过点击通知打开App/关闭通知或点击通知按钮时, 调用该方法.
/// (必须在application:didFinishLaunchingWithOptions:里设置代理)
/// @param center 用户通知中心
/// @param response 响应事件
/// @param completionHandler 处理完成的回调
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
{
    UNNotificationRequest *request = response.notification.request;
    UNNotificationContent *conten = request.content;
    NSDictionary *userInfo = conten.userInfo;

    if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        NSLog(@"点击了远程通知");
    }else {
        NSLog(@"点击了本地通知");
    }
    NSLog(@"title:%@, subtitle:%@, body:%@, categoryIdentifier:%@, sound:%@, badge:%@, userInfo:%@, actionIdentifier:%@", conten.title, conten.subtitle, conten.body, conten.categoryIdentifier, conten.sound, conten.badge, userInfo, response.actionIdentifier);
  
    // Always call the completion handler when done。
    // 无论是不是预期数据,都要在最后调用completionHandler(),告诉系统,你已经完成了通知打开处理
    completionHandler();
    
}

#pragma mark - ios 7,8,9
// 基于iOS7及以上的系统版本,如果是使用 iOS 7 的 Remote Notification 特性那么此函数将被调用
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    
    NSLog(@"%@",userInfo);
    
}

// 将deviceToken转换成字符串
- (NSString *)deviceTokenStrWithDeviceToken:(NSData *)deviceToken {

    NSString *tokenStr;
    
    if (deviceToken) {
        if ([[deviceToken description] containsString:@"length = "]) {  // iOS 13 DeviceToken 适配。
            NSMutableString *deviceTokenString = [NSMutableString string];
            const char *bytes = deviceToken.bytes;
            NSInteger count = deviceToken.length;
            for (int i = 0; i < count; i++) {
                [deviceTokenString appendFormat:@"%02x", bytes[i]&0x000000FF];
            }
            tokenStr = [NSString stringWithString:deviceTokenString];
        }else {
            tokenStr = [[[[deviceToken description]stringByReplacingOccurrencesOfString:@"<" withString:@""]stringByReplacingOccurrencesOfString:@">" withString:@""]stringByReplacingOccurrencesOfString:@" " withString:@""];
        }
    }
    
    return tokenStr;
}

// 检查联网状态 (为了使国行手机在第一次运行App时弹出网络权限弹框, 故需要请求网络连接)
- (void)checkNetword {
    
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:3];
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
    [task resume];
}
/**
 
 3.上传payload和device token到APNS
 
 (1、服务器有两种方式建立和apns的安全连接(token和证书)
 (2、服务器发送POST请求:必须包含以下信息
 (3、证书建立连接的话:证书和CSR文件绑定,CSR文件作为私钥加密证书,证书当做公钥用来和APNS交互。我们服务器安装这两种证书,证书有效期1年。
 
 
 The JSON payload that you want to send
 
 The device token for the user’s device
 
 Request-header fields specifying how to deliver the notification
 
 For token-based authentication, your provider server’s current authentication token(大多是证书)
 
 HEADERS
 - END_STREAM
 + END_HEADERS
 :method = POST
 :scheme = https
 :path = /3/device/00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0
 host = api.sandbox.push.apple.com
 apns-id = eabeae54-14a8-11e5-b60b-1697f925ec7b
 apns-push-type = alert
 apns-expiration = 0
 apns-priority = 10
 DATA
 + END_STREAM
 { "aps" : { "alert" : "Hello" } }
 
 
 
 */

/**
 4.创造一个新的远程通知
 大小限制在4~5KB之间
 json payload:aps字段告诉怎么显示,是弹框、声音或者badge
 可以自定义key,和aps字典同级
 {
 “aps” : {
     “alert” : {
         “title” : “Game Request”,
         “subtitle” : “Five Card Draw”
         “body” : “Bob wants to play poker”,
     },
     "badge" : 9,
     "sound" : "bingbong.aiff"
     “category” : “GAME_INVITATION”
 },
 “gameID” : “12345678”
 }
 
 
 */

#pragma mark - UISceneSession lifecycle

- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options  API_AVAILABLE(ios(13.0)){
    // Called when a new scene session is being created.
    // Use this method to select a configuration to create the new scene with.
    return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}


- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions  API_AVAILABLE(ios(13.0)){
    // Called when the user discards a scene session.
    // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
    // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}



@end
  • 本地通知:

代码:

-(void)HandleTimerName
{
    NSMutableArray *arr = [[NSMutableArray alloc] init];
    for (int i =0; i<1; i++) {
        NSDictionary *dic=@{@"site_name":@"您有单词学习计划,请开始学习吧~",
                                   @"timer":self.btnRemindTime.titleLabel.text,
                                   @"infoKey":self.bookUuid,
                                @"type":@"-1",
                            @"plan_name":self.nameTextField.text
                            
                                   };
        [arr addObject:dic];
    }
    [self deleteLocalNotification:[arr objectAtIndex:0]];
    [self addLocalNotification:arr];
}

- (void)addLocalNotification:(NSArray *)array
{
    // 设置一个按照固定时间的本地推送
    NSDate *now = [NSDate date];
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    NSDateComponents *components = [[NSDateComponents alloc] init];
    NSInteger unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitWeekday | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
    components = [calendar components:unitFlags fromDate:now];
    
    //   通过循环  将每一个时间都设置成本地推送
    for (int i=0; i

你可能感兴趣的:(ios~ APNs (Apple 远程通知服务))