美文网首页v2panda的技术专题iOS进阶iOS之IM
iOS推送之远程推送(iOS Notification Of R

iOS推送之远程推送(iOS Notification Of R

作者: TIME_for | 来源:发表于2015-12-24 16:33 被阅读23696次

    注:此文只现在已经不能适配iOS10了,iOS10推送采用了新的方法,做iOS9及以下的系统可读此篇文章。

    最近公司项目升级重构(重写),除了本来我所负责的模块,最后临危受命接了推送(远程和本地)相关的模块,顺便把推送的相关知识复习了一遍。后期连续工作十几天加上最后一天的通(瞎)宵(熬)达(一)旦(夜),也算是不辱使命。此文除了讲解远程推送相关的基本知识外,也会涉及一些推送相关的奇淫技巧。另外本文主要讲解远程推送,后续会出一篇iOS推送之本地推送(iOS Notification Of Local Notification)的姊妹篇。

    此篇文章的逻辑如下图所示:

    图0-0 此篇文章的逻辑图

    远程推送原理

    学习一些东西前我认为最好能了解它的原理,这样以后我们遇到问题的时候,就可以很快速的找到错误之所在,如果对原理不感兴趣的同学可直接下翻到应用部分【远程推送应用】。

    iOS app大多数都是基于client/server模式开发的,client就是安装在我们设备上的app,server就是远程服务器,主要给我们的app提供数据,因为也被称为Provider。那么问题来了,当App处于Terminate状态的时候,当client与server断开的时候,client如何与server进行通信呢?是的,这时候Remote Notifications很好的解决了这个困境。苹果所提供的一套服务称之为Apple Push Notification service,就是我们所谓的APNs。

    推送消息传输路径: Provider-APNs-Client App

    我们的设备联网时(无论是蜂窝联网还是Wi-Fi联网)都会与苹果的APNs服务器建立一个长连接(persistent IP connection),当Provider推送一条通知的时候,这条通知并不是直接推送给了我们的设备,而是先推送到苹果的APNs服务器上面,而苹果的APNs服务器再通过与设备建立的长连接进而把通知推送到我们的设备上(参考图1-1,图1-2)。而当设备处于非联网状态的时候,APNs服务器会保留Provider所推送的最后一条通知,当设备转换为连网状态时,APNs则把其保留的最后一条通知推送给我们的设备;如果设备长时间处于非联网状态下,那么APNs服务器为其保存的最后一条通知也会丢失。Remote Notification必须要求设备连网状态下才能收到,并且太频繁的接收远程推送通知对设备的电池寿命是有一定的影响的。

    图1-1 Pushing a remote notification from a provider to a client app 图1-2 Pushing remote notifications from multiple providers to multiple devices

    deviceToken的生成

    当一个App注册接收远程通知时,系统会发送请求到APNs服务器,APNs服务器收到此请求会根据请求所带的key值生成一个独一无二的value值也就是所谓的deviceToken,而后APNs服务器会把此deviceToken包装成一个NSData对象发送到对应请求的App上。然后App把此deviceToken发送给我们自己的服务器,就是所谓的Provider。Provider收到deviceToken以后进行储存等相关处理,以后Provider给我们的设备推送通知的时候,必须包含此deviceToken。(参考图1-3,图1-4)

    图1-3 Managing the device token 图1-4 Sharing the device token

    这个时候你可能会问deviceToken到底是什么?有什么用?为什么是独一无二的?

    • 是什么:deviceToken其实就是根据注册远程通知的时候向APNs服务器发送的Token key,Token key中包含了设备的UDID和App的Bundle Identifier,然后苹果APNs服务器根据此Token key编码生成一个deviceToken。deviceToken可以简单理解为就是包含了设备信息和应用信息的一串编码。
    • 有什么用:上面提到Provider推送消息的时候必须带有此deviceToken,然后此消息就根据deviceToken(UDID + App's Bundle Identifier)找到对应的设备以及该设备上对应的应用,从而把此推送消息推送给此应用。
    • 唯一性:苹果APNs的编码技术和deviceToken的独特作用保证了他的唯一性。唯一性并不是说一台设备上的一个应用程序永远只有一个deviceToken,当用户升级系统的时候deviceToken是会变化的。

    <a id="远程推送应用"></a>远程推送应用

    注册远程通知(获取deviceToken)

    注册远程通知的方法

    一般都是在App启动完成的时候去注册远程通知注册方法调用一般都在didFinishLaunchingWithOptions:方法中

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // 在iOS8之前注册远程通知的方法,如果项目要支持iOS8以前的版本,必须要写此方法
        UIRemoteNotificationType types = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert;
    
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes:types];
    
        // iOS8之后注册远程通知的方法
        UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
        
        UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
        
        [[UIApplication sharedApplication] registerUserNotificationSettings:mySettings];
    }
    

    处理注册远程通知的回调方法

    // 注册成功回调方法,其中deviceToken即为APNs返回的token
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
        [self sendProviderDeviceToken:deviceToken]; // 将此deviceToken发送给Provider
    }
    // 注册失败回调方法,处理失败情况
    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
        
    }
    

    在iOS8之后增加了可操作通知类型,可操作通知允许开发者添加自定义跳转事件。这些高级功能此篇文章不讲解,有兴趣的同学可自己去了解UIUserNotificationAction UIMutableUserNotificationAction UIUserNotificationCategory UIMutableUserNotificationCategory这几个类。

    处理接收到远程通知消息(会回调以下方法中的某一个)

    application: didFinishLaunchingWithOptions:

    此方法在程序第一次启动是调用,也就是说App从Terminate状态进入Foreground状态的时候,根据方法内代码判断是否有推送消息。

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        //  userInfo为收到远程通知的内容
        NSDictionary *userInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
        if (userInfo) {
             // 有推送的消息,处理推送的消息
        }
        return YES;
    }
    
    application: didReceiveRemoteNotification:

    如果App处于Background状态时,只用用户点击了通知消息时才会调用该方法;如果App处于Foreground状态,会直接调用该方法。

    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
      
    }
    
    application: didReceiveRemoteNotification: fetchCompletionHandler:

    iOS7之前苹果是不支持多任务的,这也是iOS系统对硬件要求低,流畅性好的原因之一。iOS7之后,苹果开始支持多任务,即App可在后台做一些更新UI、下载数据的操作等。若要接收到远程推送的时候要在后台做一些事情则需要把后台远程推送模式打开。不适配iOS7之前系统的项目建议使用此后台模式,充分利用苹果推出的多任务模式,不枉费苹果的一片苦心啊!设置后台模式方法项目对应TARGETS-Capabilities-Background Modes-Remote Notifications具体设置方法如下图(图2-1)。

    图2-1 Setting App Background Modes

    此方法不论App处于Foreground状态还是处于Background状态,收到远程推送消息的时候都会立即调用此方法。此方法需要配置后台模式并且在推送负载中必须有content-available此key值,对应的value值为1(详细介绍参考下面【远程通知负载内容】)。

    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 
    fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    
        // 在此方法中一定要调用completionHandler这个回调,告诉系统是否处理成功
    
        UIBackgroundFetchResultNewData, // 成功接收到数据
        UIBackgroundFetchResultNoData,  // 没有接收到数据
        UIBackgroundFetchResultFailed   // 接受失败
        if (userInfo) {
            completionHandler(UIBackgroundFetchResultNewData);
        } else {
            completionHandler(UIBackgroundFetchResultNoData);
        }
    }
    
    可操作通知类型收到推送消息时回调方法
    // 此两个回调方法对应可操作通知类型,具体使用方法参考以上方法很容易理解,不在详细叙述
    - (void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier 
    forRemoteNotification:(NSDictionary *)userInfo 
    completionHandler:(void(^)())completionHandler {
      
    }
    
    - (void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier 
    forRemoteNotification:(NSDictionary *)userInfo withResponseInfo:(NSDictionary *)responseInfo 
    completionHandler:(void(^)())completionHandler {
    
    }
    

    客户端和服务端的交互

    说到这里我就随意吐槽一下推送,做推送个人感觉还是比较费劲的。而第一次启动App时询问用户是否接受推送消息的时候,大部分用户都会点击拒绝推送的吧,反正我是这样的。你辛辛苦苦做好了,想办法保证其推送准时性,想办法保证其推送到达率,结果用户一个拒绝,你所以的努力全都白费了啊,哈哈哈。

    我这里主要想说的就是:我们要把对应的.p12(个人信息交换证书)证书给服务端的开发人员就好了。具体可参看我另一篇文章不让苹果开发者账号折磨我中的团队开发证书的管理中的导出.p12章节。

    远程推送负载

    远程推送负载大小

    远程通知负载的大小根据Provider使用的API不同而不同。当使用HTTP/2 provider API时,负载最大为4096bytes,即4kB;当使用legacy binary interface时,负载最大为2048bytes,即2kB。当负载大小超过规定的负载大小时,APNs会拒绝发送此消息。

    <a id="远程推送负载内容"></a>远程推送负载内容

    内容格式必要要知道的啊,服务端一般会要我们客户端定义好格式给他们的。

    每一条通知的消息都会组成一个JSON字典对象,其格式如下所示,示例中的key值为苹果官方所用key。自定义字段的时候要避开这些key值。

    {
         "aps" : {  
            "alert"              :              {   // string or dictionary
              "title"          :   "string"
                "body"           :   "string",
                "title-loc-key"  :   "string or null"
                "title-loc-args" :   "array of strings or null"
                "action-loc-key" :   "string or null"
                "loc-key"        :   "string"
                "loc-args"       :   "array of strings"
                "launch-image"   :   "string"
            },
            "badge"             :    number,
            "sound"             :    "string"
            "content-available" :    number;
            "category"          :    "string"
         },
    }
    
    aps:推送消息必须有的key
    
    alert:推送消息包含此key值,系统就会根据用户的设置展示标准的推送信息
    badge:在app图标上显示消息数量,缺少此key值,消息数量就不会改变,消除标记时把此key对应的value设置为0
    sound:设置推送声音的key值,系统默认提示声音对应的value值为default
    content-available:此key值设置为1,系统接收到推送消息时就会调用不同的回调方法,iOS7之后配置后台模式
    category:UIMutableUserNotificationCategory's identifier 可操作通知类型的key值
    
    title:简短描述此调推送消息的目的,适用系统iOS8.2之后版本
    body:推送的内容
    title-loc-key:功能类似title,附加功能是国际化,适用系统iOS8.2之后版本
    title-loc-args:配合title-loc-key字段使用,适用系统iOS8.2之后版本
    action-loc-key:可操作通知类型key值,不详细叙述
    loc-key:参考title-loc-key
    loc-args:参考title-loc-args
    launch-image:点击推送消息或者移动事件滑块时,显示的图片。如果缺少此key值,会加载app默认的启动图片。
    
    

    当然以上key值并不是每条推送消息都必带的key值,应当根据需求来选择所需要的key值,除了以上系统所提供的key值外,你还可以自定义自己的key值,来作为消息推送的负载,自定义key值与aps此key值并列。如下格式:

    
    {
        "aps" : {
            "alert" : "Provider push messag.",
            "badge" : 9,
            "sound" : "toAlice.aiff"
        },
        "Id"   : 1314,               //  自定义key值
        "type" : "customType"        //  自定义key值
    }
    

    指定用户的推送

    对于要求用户登录的App,推送是可以指定用户的,同一条推送有些用户可以收到,但是有些用户又不能收到。说起来这个就要提到另外的一个token了,一般称之为userToken,userToken一般都是根据自己公司自定义的规则去生成的。userToken是以用户的账号加对应的密码生成的。这样结合上面提到的deviceToken,就可以做到根据不同的用户推送不同的消息。deviceToken找到对应某台设备和该设备上的应用,而userToken对应找到该用户。客户端在上报deviceToken的时候,要把userToken对应一起上报给服务端也就是Provider。

    浅谈推送第三方SDK

    关于第三方推送的SDK有很多,常见的有极光推送 百度推送 个推 友盟推送等等。其实推送的原理都是大同小异的,理解了苹果推送的原理,这些第三方SDK还在是基本原理上面进行了扩展。对于用不用第三方SDK其实对我们客户端影响不大,推送第三方SDK主要是方便了服务端开发者。主要表现为服务端开发者不需要去开发维护自己的推送服务器与 APNs 对接,不必自己维护更新 deviceToken。当然了,第三方SDK也会提供一些额外的附属功能例如JPush提供了应用内消息推送,这在类似于聊天的场景里很方便的。看完这段是不是发现集成推送的第三方SDK和客户端没什么关系,我们工作量不仅没有减少,反而增加了一点点啊。至于第三方SDK的其他功能,大家可自行去对应官网学习,这里不再过多描述。

    利用runtime实现推送消息万能跳转

    此段参考了@汉斯哈哈哈的一篇iOS 万能跳转界面方法万能跳转就是可以跳转到指定的任意一个界面,但是这个和服务端耦合性太强,使用的时候要慎重考虑,而且公司一般都是iOS,Android共用同一套推送规则很难让服务端在给你开一条新的推送规则,不便于维护,而且成本也是需要考虑的。写此段的目的就是当产品有这样的需求的时候还是可以参考一下的。

    定义推送规则

    // 客户端控制器的属性
    @interface YBViewController : UIViewController
    /** 频道Id */
    @property (nonatomic, copy) NSString *Id;
    /** 频道type */
    @property (nonatomic, copy) NSString *type;
    @end
    
    // 服务端推送数据格式
    {
        "aps"      :     { "alert" : "Provider push messag" },
        "class"    :     "YBViewController",
        "property" :     {
             "Id"   :   1314,
             "type" :   "customType"
        }
    }
    

    跳转逻辑

    // 接收到推送后跳转
    - (void)didReceiveRemoteNotificationAndPushToViewController:(NSDictionary *)userInfo {
        // 创建类
        NSString *class = userInfo[@"class"];
        const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
        Class newClass = objc_getClass(className);
        
        if (!newClass) {
            Class superClass = [NSObject class];
            newClass = objc_allocateClassPair(superClass, className, 0);
            objc_registerClassPair(newClass);
        }
        
        // 创建跳转控制器对象
        id destinationViewController = [[newClass alloc] init];
        
        // 对该对象赋值属性
        NSDictionary *propertys = userInfo[@"property"];
        [propertys enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            // 检测这个对象是否存在该属性
            if ([self checkIsExitPropertyWithdestinationViewController:destinationViewController verifyPropertyName:key]) {
                [destinationViewController setValue:obj forKey:key];
            }
        }];
        
        // 跳转
        UITabBarController *tabViewController = (UITabBarController *)self.window.rootViewController;
        UINavigationController *sourceViewController = (UINavigationController *)tabViewController.viewControllers[tabViewController.selectedIndex];
        [sourceViewController pushViewController:destinationViewController animated:YES];
        
    }
    
    // 检测对象是否存在该属性
    - (BOOL)checkIsExitPropertyWithdestinationViewController:(id)destinationViewController verifyPropertyName:(NSString *)verifyPropertyName {
        // 获取对象里的属性列表
        unsigned int outCount, i;
        objc_property_t *properties = class_copyPropertyList([destinationViewController class], &outCount);
        
        for (i = 0; i < outCount; i++) {
            objc_property_t property = properties[i];
            // 属性名转成字符串
            NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
            // 判断该属性是否存在
            if ([propertyName isEqualToString:verifyPropertyName]) {
                free(properties);
                return YES;
            }
        }
    
        free(properties);
        return NO;
    }
    

    总结

    好好理解远程推送的原理就会发现,其实远程推送并没有那么难做啊。上面的一些图片有些来源于苹果官方文档,有些是自己所截图。一些知识也是参考了苹果的官网文档。其中一些深入的推送相关知识普遍性不是太高,所以也没有提到,例如:可操作通知类型,通知显示国际化,自定义通知声音,Provider-APNs-Device详细连接情况及推送负载的底层数据格式等。如果你对这些知识很感兴趣也很欢迎私密我私下交流,共同进步。敬请期待本篇的姊妹篇iOS推送之本地推送(iOS Notification Of Local Notification)

    参考文献

    相关文章

      网友评论

      • 一抹相思泪成雨:问一下,想做收到消息跳转功能,iOS9的系统,如果杀死应用,这时候,点击远程推送的消息进入,应该走那个代理方法,目前,我的跳转都不走。。。
      • frandy123:向作者学习,感谢作者
      • 不明之人:假如这个方法 [self sendProviderDeviceToken:deviceToken]; // 将此deviceToken发送给Provider
        由于网络或者其他原因不成功,还会继续调用这个方法将此deviceToken发送给Provider吗?
        TIME_for:这个完全是自己处理的,没发送成功,Provider 没有拿到 deviceToken,那 Provider 就没法推送啊。
      • 天山雪莲_38324:受教了,谢谢!
      • a8a2e5ae6d3c:什么时候补充一下适配iOS10的呀。
      • Oort:你好,我集成了Google gcm推送苹果SDK 每次都能收到消息打印到控制台出来,但是没有通知是怎么回事?
        TIME_for:这个没有遇到过呢。。App在前台的时候,收到通知是不会显示通知的。不知道是不是因为这个。
      • 2018火:谢谢分享
        TIME_for:@腕理路 共同进步~~
      • zero000:如果不包含 "content-available"字段,那么收到通知的tetch方法会执行吗?
        47200923d724:哪个tetch方法?是fetch吧?
        TIME_for:@zero000 不会
      • Joe_lisa:远程通知,在前台,如果转化为本地通知,如何加角标
        TIME_for:@Joe_lisa 同样都是设置applicationIconBadgeNumber这个属性
      • VickyLanLan: :+1: !不错不错!学习了
        TIME_for:@VickyLanLan 共同进步~~
      • 杭研融合通信iOS:据我说知,devicetoken 不会携带bundle id的, 博主是从哪里获取到这个信息的?
        TIME_for:@杭研融合通信iOS 推送到达手机,是需要 bundle id 来确定要哪一个App响应这个推送信息的。
      • 花开半夏已成殇:请问下楼主有没有遇到过远程推送消息不在通知栏显示的???
        TIME_for:@花开半夏已成殇 这个倒是没有遇到过。
      • 一骑红尘妃子笑____:{
        "aps" : {
        "alert" : "Provider push messag.",
        "badge" : 9,
        "sound" : "toAlice.aiff"
        },
        "Id" : 1314, // 自定义key值
        "type" : "customType" // 自定义key值
        }

        请问推送中是不是通过sound 字段控制声音的,系统会根据sound 字段的内容,在收到推送的时候播放指定的声音,当然前提是本地有这个声音文件。另外,假如sound 字段为空,是播放默认声音还是静音模式?
        TIME_for:@一骑红尘妃子笑____ sound:是的,sound为空 系统默认提示声音对应的value值为default
      • Pusswzy:写的真不错
        TIME_for:@Pusswzy 谢谢~~
      • StoneLeon:App 杀死状态? 实现哪个方法?
        TIME_for:@StoneLeon 杀死状态,只能等App启动的时候,从 didFinishLaunchingWithOptions 方法中拿推送数据,然后在做处理
      • 聂飞缘:楼主,为什么我的app在被结束进程之后收不到推送,只能是app在后台或者在运行的时候才能收到
      • 071a958d5815:喜欢
        TIME_for:@071a958d5815 谢谢~~
      • HustBroventure:楼主最好说明下:如果实现了- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler方法,- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo就不会调用。很好的一篇文章啊。
        TIME_for:@HustBroventure 好的,我补充一下。谢谢指点~~
      • ynot16:你好,请问你当时是自己部署推送服务器吗?还是用第三方的,我这边要自己部署服务器,想请问你有没有相关经验或资料推荐下。谢谢。
        TIME_for:@ynotcc 服务端的不太清楚呢。
      • uncleRX:学习啦!
      • 3727db4452eb:分析的很透彻啊!👍🏼
        TIME_for:@3727db4452eb 谢谢~~,共同进步。
      • ef2099466631:楼主是否遇到这个问题:点击其中一条推送消息,然后其他的也跟着不见了。:joy::joy::joy:,看了下支付宝这些不会…
        TIME_for:@wanggw911 这个我也不太清楚呢,回头研究一下啊 :sweat:
      • danson:之前糊糊涂涂的做了这个 虽然成功了 一直不太清楚 看了这个 豁然开朗 感谢笔者
        TIME_for:@danson 共同交流,共同进步~~
      • key君:{
        "aps" : {
        "alert" : { // string or dictionary
        "title" : "string"
        "body" : "string",
        "title-loc-key" : "string or null"
        "title-loc-args" : "array of strings or null"
        "action-loc-key" : "string or null"
        "loc-key" : "string"
        "loc-args" : "array of strings"
        "launch-image" : "string"
        },
        "badge" : number,
        "sound" : "string"
        "content-available" : number;
        "category" : "string"
        },
        }


        这段配置是要写在哪里?写在AppDelegate.m文件?
        TIME_for:@key君 不能说无关,咱们要知道这些key值,需要根据key值去取服务端那边传过来的值得。
        key君:@Toyun 意思是这些是服务端要配置的参数,跟前端无关
        TIME_for:@key君 这个是服务端的传递消息的时候对应的key值。而这些key值,是让苹果占用的。
      • 低调_哲:推送负载中必须有content-available此key值,对应的value值为1

        这个地方是什么意思?表示不太懂 ~
        TIME_for:@低调_哲 就是@{@"content-available" : 1}这个意思啊
        TIME_for:@低调_哲 使用后台模式,推送的消息内容中,一定要带有这个字段。配套使用的。key为content-available,value为1,这个不难懂吧?
      • 马可萝卜:写得很好:smile:
        TIME_for:@郭杰_非专业代码男 谢谢~~共同进步
      • HJXu:[self sendProviderDeviceToken:deviceToken]问下楼主这个方法是你自己写的方法吗?我上面怎么没有这个方法啊,我是iOS9.0.还有说一个小小的错误,在判断是否回调到数据的时候的else里面应该是UIBackgroundFetchResultNoData吧
        TIME_for:@HJXu sendProviderDeviceToken:这个方法是自己写的,下面那个确实错了,没有太注意,感谢你的指正。
      • 75040dae73e8:分析的很透彻
        TIME_for:@C己 谢谢~~
      • d28645104c16:每天能看一篇这样的文章睡觉都踏实,楼主的第一张图是什么画的
        TIME_for:@ios雪松 谢谢~~,用的MindLine画的图,
      • Sugar_璟:赞赞赞,学习了
        TIME_for:@jvsugar的v 共同进步~~
      • 96dfced25580:写得非常棒,点赞,非常有用的总结。补充一下:DeviceToken不仅仅系统升级的时候会改变,系统重刷,应用删除再安装,DeviceToken都会改变。
        TIME_for:@一楼听风雨 可以啊
        d2f528492b55:@TIME_for 请问下 app的角标可以不用服务器推送 自己计算吗
        TIME_for:@仲系xbb 谢谢补充,涨知识了啊~~
      • caiwenshu:runtime那个看看就好了!跟后端耦合太高了
        TIME_for:@caiwenshu 是的,实际中用的较少,作为一种思路扩展一下。多谢你的建议~~
      • newbiecoder:学习了
        TIME_for:@newbiecoder 共同进步啊~~
      • a91290eb4f4c:思维导图不错
        TIME_for:@red_or_blue_pil 谢谢~~
      • 43bd89865d8c:挺不错的,感谢坐着,只是在DeviceToken中“系统会发送请求到ANPs服务器”应该是APNs吧
        TIME_for:@林梦兮 错误在ANPs,作者表示眼瞎了。非常抱歉,对于你指出的错误,再次表示感谢。
        TIME_for:@林梦兮 感谢拜读,APNs(Apple Push Notification service)是苹果推送相关的一套服务总称,而这套服务背后的服务器一般称之为APNs服务器,发送请求是到服务器上面了,不能说给这一套服务发送了一条请求啊。
      • dispath_once::+1::+1:
        TIME_for:@dispath_once :smile:
      • 076f37cc3cd0:写的很全面 学习学习
        TIME_for:@gojan 互相学习
      • 曾樑:赞赞赞
        TIME_for:@曾樑 谢谢鼓励~~

      本文标题:iOS推送之远程推送(iOS Notification Of R

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