iOS-DeviceToken变化之谜

一.介绍一下问题的背景

最近在搞远程推送的时候,忽然发现,有时候,当某一台机器需要推送一条信息的时候,这台机器可能会收到同样的信息若干条.就去找问题所在.然而更换了证书,或者配置文件之后,故障依然存在.我就认为这不是我的问题,是后台服务器的问题(后台的兄弟们,无辜躺枪),就去了解了一下后台推送的相关流程.之前只是了解一下苹果远程推送的原理,不是很了解我们后台服务器端需要做些什么事情.

简述一下我目前的理解.当app启动时,我们在appDelegate里面注册远程通知,然后苹果服务器返回一个deviceToken给我们,在appDelegate的其中一个代理方法

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken

在该方法里,我们获取到的NSData类型的devToken就是苹果服务器根据我们这一台设备的UDID和app的bundleID混编而成的deviceToken,我们需要将这个deviceToken传送给我们的服务器端,或者登陆用户的时候作为参数传给服务器.这样一个用户对象就绑定了一个deviceToken.当需要给这个用户推送消息的时候,我们自己的后台服务器,就会找这个用户对应的deviceToken和要发送的推送内容,直接发送到苹果的apns服务器,然后由苹果的apns服务器将消息推送到该deviceToekn对应的手机上.

后来我就让后台的兄弟查到该用户竟然对应了多个deviceToken,不过当这一账号同时在多个设备上登陆的时候,可能会绑定多个deviceToken的,但问题是测试机一共就两个,不会存在绑定八九个deviceToken的.我就在想这个deviceToken会不会发生变化.

二.问题所在

在网上搜索到一些知识,也查询了一下官方文档里面对deviceToken的解释,deviceToken会发生变化,但是仅仅在用户在新的设备上登陆或者更新设备操作系统的时候会发生变化.更重要的是,我的上司领导也很肯定的告诉我,同一台设备,同一款软件,而且还是在没有修改软件的bundleID的情况下,deviceToken是不会发生变化的.

后来测试时候就无意中发现,每当我将运行在真机上的demo卸载再重新运行的时候,deviceToken竟然会是发生变化的,而且还是无规律的发生变化.这让我发现了故障的所在.竟然卸载重装会让deviceToken发生变化.后来我分别用iOS7.0系统和iOS8.0的真机测试,发现在这两款系统上,卸载重装,苹果返回的deviceToken不会发生变化.而只有iOS9.0以后的系统版本会发生变化.而且如果每次启动都请求注册的话,只要你没有卸载重装,那么返回的deviceToken是不会发生变化的.只有当你卸载重装的时候才会发生变化.

三.解决方案

先说我尝试过的一种解决方案吧,我将第一次安装软件时候所获得的deviceToken,存储在钥匙串(keychain)内.以后不论什么时候卸载重装软件,只有软件一启动,那么就从keychain内读取保存的deviceToken.然后利用这个deviceToken去进行推送服务.但是我自己在用网络上的那个可以模拟推送的mac小demo(名字叫做PushMeBaby)时,发现如果卸载重装软件后,keychain内保存的旧的deviceToken竟然是无效的,而新获得的deviceToken才是有效的.这让我感到很无奈,保存在keychain的方法没有奏效.

后来考虑到在传递给自己后台服务器时候,怎么才可能保证用一个用户下,一个设备下仅仅保存一个deviceToken,每当这个设备的deviceToken发生变化的时候,就替换该设备对应的deviceToken.

最终的解决方案就是,获取设备的UUID(被苹果禁用的是UDID) + keychain + DeviceToken来解决这个问题.

当软件第一次安装时候,获取设备的UUID 存储到keychain中,那么只要你不刷机,那么这个保存在keychain中的UUID一直存在,即使你升级操作系统也会存在(我正好升级试了一下),这样我们就能保证设备编码的唯一性,在向我们自己的后台服务器传参数时,将这个UUID和获得的deviceToken一起传递过去,让后台做个校验,查询该用户属性下的UUID设备对应的deviceToken是否发生变化,如果发生变化,就替换.这样保证了该用户在这一台设备上绑定了一个deviceToken,这样推送的时候就不会造成可能会推送多条信息的bug.

四.下面附上封装的UUID和keychain的代码,稍微修改一下即可使用.(注意:必须需要导入Security.framework框架)

//  UuidObject.h

#import@interface UuidObject : NSObject

+ (NSString *)getUUID;

@end
//  UuidObject.m

#import "UuidObject.h"

#import "KeyChainStore.h"

@implementation UuidObject

+(NSString *)getUUID

{

NSString * strUUID = (NSString *)[KeyChainStore load:@"your_app_bundleID"];

//首次执行该方法时,uuid为空

if ([strUUID isEqualToString:@""] || !strUUID)

{

//生成一个uuid的方法

CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);

strUUID = (NSString *)CFBridgingRelease(CFUUIDCreateString (kCFAllocatorDefault,uuidRef));

//将该uuid保存到keychain

[KeyChainStore save:KEY_USERNAME_PASSWORD data:strUUID];

}

return strUUID;

}

@end
//KeyChainStore.h

#import@interface KeyChainStore : NSObject

+ (void)save:(NSString *)service data:(id)data;

+ (id)load:(NSString *)service;

+ (void)deleteKeyData:(NSString *)service;

@end
//KeyChainStore.m

#import "KeyChainStore.h"

@implementation KeyChainStore

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {

return [NSMutableDictionary dictionaryWithObjectsAndKeys:

(id)kSecClassGenericPassword,(id)kSecClass,

service, (id)kSecAttrService,

service, (id)kSecAttrAccount,

(id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,

nil];

}

+ (void)save:(NSString *)service data:(id)data {

//Get search dictionary

NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];

//Delete old item before add new item

SecItemDelete((CFDictionaryRef)keychainQuery);

//Add new object to search dictionary(Attention:the data format)

[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];

//Add item to keychain with the search dictionary

SecItemAdd((CFDictionaryRef)keychainQuery, NULL);

}

+ (id)load:(NSString *)service {

id ret = nil;

NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];

//Configure the search setting

//Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue

[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];

[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];

CFDataRef keyData = NULL;

if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {

@try {

ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];

} @catch (NSException *e) {

NSLog(@"Unarchive of %@ failed: %@", service, e);

} @finally {

}

}

if (keyData)

CFRelease(keyData);

return ret;

}

+ (void)deleteKeyData:(NSString *)service {

NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];

SecItemDelete((CFDictionaryRef)keychainQuery);

}

@end
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现iOS推送,需要遵循以下步骤: 1. 创建APNS证书 首先,你需要在苹果开发者中心创建一个APNS证书。这个证书将用于安全地将消息发送到iOS设备。 2. 获取设备Token 每个iOS设备都有一个唯一的设备Token,用于标识该设备。你需要在你的应用中获取该设备Token,并将其发送到你的服务器。 3. 编写PHP代码 使用PHP编写代码,以便将消息发送到APNS服务器。你需要使用APNS证书和设备Token来建立连接,并将消息发送到APNS服务器。 以下是一个简单的PHP代码示例: ``` <?php // Put your device token here (without spaces): $deviceToken = 'YOUR_DEVICE_TOKEN_HERE'; // Put your private key's passphrase here: $passphrase = 'YOUR_PASSPHRASE_HERE'; // Put your alert message here: $message = 'Hello, world!'; //////////////////////////////////////////////////////////////////////////////// $ctx = stream_context_create(); stream_context_set_option($ctx, 'ssl', 'local_cert', 'YOUR_APNS_CERTIFICATE.pem'); stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase); // Open a connection to the APNS server $fp = stream_socket_client( 'ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx); if (!$fp) exit("Failed to connect: $err $errstr" . PHP_EOL); echo 'Connected to APNS' . PHP_EOL; // Create the payload body $body['aps'] = array( 'alert' => $message, 'sound' => 'default' ); // Encode the payload as JSON $payload = json_encode($body); // Build the binary notification $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload; // Send the notification to the server $result = fwrite($fp, $msg, strlen($msg)); if (!$result) echo 'Message not delivered' . PHP_EOL; else echo 'Message successfully delivered' . PHP_EOL; // Close the connection to the server fclose($fp); ?> ``` 在上面的代码中,你需要将以下变量替换为你自己的值: - YOUR_DEVICE_TOKEN_HERE:你的设备Token。 - YOUR_PASSPHRASE_HERE:你的APNS证书密码。 - YOUR_APNS_CERTIFICATE.pem:你的APNS证书文件名和路径。 - $message:你要发送的消息。 4. 测试推送 运行PHP代码并测试推送。如果一切正常,你应该会收到一个推送通知。 注意:在生产环境中,你需要将APNS服务器地址更改为“gateway.push.apple.com”。在示例代码中,我们使用的是开发环境的APNS服务器地址“gateway.sandbox.push.apple.com”。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值