美文网首页
ios~ APNs (Apple 远程通知服务)

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

作者: 阳光下的叶子呵 | 来源:发表于2021-09-07 11:36 被阅读0次
    注意事项:
    1. 需要打开通知权限,
    2. 推送证书、设置info.plist文件,设置自动生成的 .entitlements文件
      【本地和远程通知编程指南】:
      apple api文档: 设置远程通知服务器

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

    代码:

    //
    //  AppDelegate.m
    //
    
    #import "AppDelegate.h"
    #import <UserNotifications/UserNotifications.h>
    
    @interface AppDelegate ()<UNUserNotificationCenterDelegate>
    
    @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<UISceneSession *> *)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<array.count; i++) {
            //设置时区(跟随手机的时区)
            UILocalNotification *localNotification = [[UILocalNotification alloc] init];
            localNotification.timeZone = [NSTimeZone defaultTimeZone];
            if (localNotification) {
                //   设置推送时的显示内容
                localNotification.alertBody = array[i][@"site_name"];
                localNotification.alertAction = NSLocalizedString(@"All_open",@"");
                //   推送的铃声  不能超过30秒  否则会自定变为默认铃声
                localNotification.soundName = @"2.caf";
                //小图标数字
                localNotification.applicationIconBadgeNumber++;
                NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
                [formatter setDateFormat:@"HH:mm"];
                NSDate *date = [formatter dateFromString:array[i][@"timer"]];
                //通知发出的时间
                localNotification.fireDate = date;
            }
            //循环通知的周期   每天
            localNotification.repeatInterval = kCFCalendarUnitDay;
            
            //设置userinfo方便撤销
            NSDictionary * info = @{@"plan_name":array[i][@"plan_name"],@"type":array[i][@"type"],@"infoKey":array[i][@"infoKey"],@"site_name":array[i][@"site_name"]};
            localNotification.userInfo = info;
            //启动任务
            [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
        }
    }
    
    -(void) deleteLocalNotification:(NSDictionary *)dict
    {
        // 获取所有本地通知数组
        NSArray *localNotifications = [UIApplication sharedApplication].scheduledLocalNotifications;
        for (UILocalNotification *notification in localNotifications)
        {
            NSDictionary *userInfo = notification.userInfo;
            if ([dict[@"infoKey"] isEqualToString:userInfo[@"infoKey"]]) {
                [[UIApplication sharedApplication] cancelLocalNotification:notification];
            }
        }
    }
    
    

    相关文章

      网友评论

          本文标题:ios~ APNs (Apple 远程通知服务)

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