如何将我的设备令牌(NSData)转换为NSString?

187

我正在实现推送通知。我想将我的APNS令牌保存为字符串。

- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)newDeviceToken
{
    NSString *tokenString = [NSString stringWithUTF8String:[newDeviceToken bytes]]; //[[NSString alloc]initWithData:newDeviceToken encoding:NSUTF8StringEncoding];
    NSLog(@"%@", tokenString);
    NSLog(@"%@", newDeviceToken);
}

第一行代码打印null。第二行打印了token。如何将我的newDeviceToken作为NSString获取?


第二个 NSLog 的输出是什么,即打印 newDeviceToken 的那个? - rob mayoff
请勿重复:https://dev59.com/o3M_5IYBdhLWcg3wmkeu#35730103 - NiñoScript
do NOT use description - Fattie
31个回答

270

如果有人想用Swift实现这个功能:

Swift 3引入了Data类型,具有值语义。要将deviceToken转换为字符串,可以按照以下步骤进行:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
    print(token)
}

使用NSData的旧答案:

func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
    let tokenChars = UnsafePointer<CChar>(deviceToken.bytes)
    var tokenString = ""

    for i in 0..<deviceToken.length {
        tokenString += String(format: "%02.2hhx", arguments: [tokenChars[i]])
    }

    print("tokenString: \(tokenString)")
}

141
为什么这要这么复杂,操作系统直接给我们一个字符串不是所有人都需要吗?谢谢您提供的解决方案。 - Piwaf
3
@ Sascha,我希望你能批准我对你非常有用的回答所做的修改 :) - jrturton
16
我重构了代码:let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() http://qiita.com/mono0926/items/3cf0dca3029f32f54a09 - mono
2
我不建议使用.description,因为它不能保证稳定性。请查看我的答案:https://dev59.com/rmox5IYBdhLWcg3wLhei#39754930 - swift taylor
10
"%02.2hhx" 是一个格式化字符串,用于将一个无符号字符型变量转换成两位十六进制数并以字符串形式输出。其中%02表示输出的十六进制数占两位,不足两位时前面补零;.2表示精确到小数点后两位(这里是整数,所以实际上没有小数部分);hh表示输入参数是一个无符号字符型变量。 - mfaani
显示剩余9条评论

164

有人帮助了我。我只是在转达。

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

    const unsigned *tokenBytes = [deviceToken bytes];
    NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
                         ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
                         ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
                         ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];

    [[MyModel sharedModel] setApnsToken:hexToken];
}

5
这是最好的解决方案,因为将字节编码为十六进制意味着你可以进行计数 ;) - loretoparisi
4
在XCode 5中,我需要将deviceToken进行强制转换才能使其编译通过:const unsigned *tokenBytes = (const unsigned *)[deviceToken bytes]; - Ponytech
3
Token很快就会超过32个字节,因此这将需要一个循环来遍历每个字节,而不是八个硬编码整数。 - Tom Dalling
5
这是否是更好的解决方案?const unsigned *tokenBytes = [deviceToken bytes]; NSMutableString *hexToken = [NSMutableString string]; for (NSUInteger byteCount = 0; byteCount * 4 < [deviceToken length]; byteCount++) { [hexToken appendFormat:@"%08x", ntohl(tokenBytes[byteCount])]; } - Harro
9
重要提示:APNs设备令牌长度不固定,不要硬编码其大小。苹果公司表示。 - erkanyildiz
显示剩余9条评论

155

您可以使用此功能

- (NSString *)stringWithDeviceToken:(NSData *)deviceToken {
    const char *data = [deviceToken bytes];
    NSMutableString *token = [NSMutableString string];

    for (NSUInteger i = 0; i < [deviceToken length]; i++) {
        [token appendFormat:@"%02.2hhX", data[i]];
    }

    return [token copy];
}

11
这个答案应该被采纳,因为它比使用“description”更安全。 - DrMickeyLauer
8
这是Objective-C中唯一正确的答案,可以处理即将到来的令牌大小增加。 - Tom Dalling
同意这可能是最安全的方式,因为它不假定任何特定的令牌大小/长度。 - Ryan H.
适用于iOS 10。 - Tjalsma
2
我使用了[token appendFormat:@"%02.2hhx", data[i]];,因为Amazon SNS要求小写。 - Manuel Schmitzberger

42

对于那些想要使用 Swift 3 来实现最简单的方法的人们

func extractTokenFromData(deviceToken:Data) -> String {
    let token = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
    return token.uppercased();
}

1
我写了相同的代码 :) 这是最流畅的版本,只有这个能工作。 - Quver
1
@Anand,您能解释一下这段代码deviceToken.reduce("", {$0 + String(format: "%02X", $1)})在做什么吗? - Ramakrishna
1
它使用Swift的reduce函数将Data序列化为十六进制字符串,然后转换为String。要了解有关reduce函数的更多信息,请阅读https://useyourloaf.com/blog/swift-guide-to-map-filter-reduce/。 - Anand

33

注意 - 当使用iOS 13或更高版本的SDK进行编译时,此方法将无效。

请使用以下方法:

NSString * deviceTokenString = [[[[deviceToken description]
                         stringByReplacingOccurrencesOfString: @"<" withString: @""] 
                        stringByReplacingOccurrencesOfString: @">" withString: @""] 
                       stringByReplacingOccurrencesOfString: @" " withString: @""];
        
NSLog(@"The generated device token string is : %@",deviceTokenString);

139
使用描述似乎不是一个好主意:没有任何保证后续版本的iOS不会更改这个调用的实现和结果。 - madewulf
18
确实,这是一个非常糟糕的想法。 - David Snabel-Caunt
21
很棒的你指出使用描述是一个糟糕的想法,如果你提供了一些替代建议就更好了。 - abbood
6
这里提供的解决方案,使用 [deviceToken bytes] 符合要求。 - madewulf
39
事实证明,在Swift 3/iOS 10中,对设备令牌进行 .description 操作会返回"32字节"。因此,不要使用这个。 - Victor Luft
显示剩余11条评论

28
在这个高票answer中,“%02.2hhx”的解释如下:
  • %:引入x转换说明符。
  • 02:转换后的值的最小宽度为2。如果转换后的值的字节数少于字段宽度,则左侧将用0填充。
  • .2:给出x转换说明符要显示的最小数字个数。
  • hh:指定x转换说明符适用于signed char或unsigned char参数(根据整数提升,该参数将已被提升,但其值在打印之前将被转换为signed char或unsigned char)。
  • x:无符号参数将以无符号十六进制格式转换为"dddd"样式;使用字母"abcdef"。精度指定要显示的最小数字个数;如果要转换的值可以用较少的数字表示,则将使用前导零进行扩展。默认精度为1。使用显式精度为零将零转换的结果为空字符。
更多详情请参阅IEEE printf specification

基于上述解释,我认为最好将%02.2hhx更改为%02x%.2x

对于Swift 5,以下方法都是可行的:

deviceToken.map({String(format: "%02x", $0)}).joined()

deviceToken.map({String(format: "%.2x", $0)}).joined()

deviceToken.reduce("", {$0 + String(format: "%02x", $1)})

deviceToken.reduce("", {$0 + String(format: "%.2x", $1)})

测试如下:
let deviceToken = (0..<32).reduce(Data(), {$0 + [$1]})
print(deviceToken.reduce("", {$0 + String(format: "%.2x", $1)}))
// Print content:
// 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f

谢谢您的回答。这个方法在iOS 12上也适用吗?还是只取决于Swift版本? - Markus
1
@Markus 这个在iOS 12上可以运行,只与Swift版本有关。 - jqgsninimo

17

iOS 13 的描述格式已更改,请使用以下代码获取设备令牌。

- (NSString *)fetchDeviceToken:(NSData *)deviceToken {
    NSUInteger len = deviceToken.length;
    if (len == 0) {
        return nil;
    }
    const unsigned char *buffer = deviceToken.bytes;
    NSMutableString *hexString  = [NSMutableString stringWithCapacity:(len * 2)];
    for (int i = 0; i < len; ++i) {
        [hexString appendFormat:@"%02x", buffer[i]];
    }
    return [hexString copy];
}

完美的iOS 13解决方案。谢谢Vishnu。 - Manish
1
目前它无法编译 - for 循环中的 length 应更改为 len。 对我来说似乎是一个微小的更改,不能编辑..但其他方面都完美! - Anders Friis

13
这是我自己的解决方案,已在我的应用中成功运行:
    NSString* newToken = [[[NSString stringWithFormat:@"%@",deviceToken] 
stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""];
  • 使用 stringWithFormatNSData 转换为 NSString
  • 删除 "<>"
  • 去除空格

11
这只是隐式地调用了-description方法,因此它并没有比被接受的答案更安全。 - jszumski
你能否请提供你的源代码链接?我在任何地方都找不到相关信息。谢谢。 - Zeb
找到了! 我认为它有点不同。直接使用description属性是不安全的,因为它可能会在未来版本中更改,但如果你通过NSString方法使用它,你几乎不会遇到问题。 - Zeb
6
不,这确实像jszumski所说的那样在设备标记上调用了“描述”(description)方法。 - Jonny
嗨Jonny,也许你误解了我在先前评论中的意思:description方法可以更改,但如果您通过另一种方法使用它,那么它将是安全的,因为“他们”会适应它。顺便说一下,这里的每个解决方案都直接或间接地使用了description字段,所以如果您有更好的解决方案,我们将非常感激。 - Zeb
1
@Zeb 不管你是直接调用description还是通过其他方法使用它,都不安全,因为返回的字符串格式随时可能发生变化。正确的解决方案在这里:https://dev59.com/rmox5IYBdhLWcg3wLhei#16411517 - Tom Dalling

11

我认为将deviceToken转换为十六进制字节字符串没有意义。为什么呢?因为您将发送它到后端,那里将把它转换回字节以推送到APNS。所以,使用NSData的方法base64EncodedStringWithOptions,将其推送到服务器,然后使用反向的base64解码数据 :) 那样会更容易 :)

NSString *tokenString = [tokenData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];

@jeet.chanchawat,请不要在其他用户的回答中添加代码。我们不希望替他们说话,特别是在将Swift添加到Objective-C答案时。相反,请添加您自己的答案。 - JAL
2
我只是不想抄袭@Oleg Shanyuk的答案。因为这只是在他的答案基础上用另一种语言进行的翻译,所以他应该得到未来的赞。如果我添加另一个答案,那么这将使我获得别人研究成果的赞。希望这可以证明我的编辑是合理的。 - jeet.chanchawat

11

在iOS 13中,description会出现问题,请使用这个。

let deviceTokenString = deviceToken.map { String(format: "%02x", $0) }.joined()
为了清晰明了起见,让我们分解并解释每个部分: map方法作用于序列的每个元素。因为在Swift中Data是一个字节序列,所以该闭包对deviceToken中的每个字节进行评估。 String(format:)初始化器使用%02x格式说明符评估数据中的每个字节(由匿名参数$0表示),生成一个零填充的、2位十六进制表示的字节/8位整数。 在map方法创建每个字节表示后,joined()将每个元素连接成单个字符串。
附注:不要使用描述在iOS 12和iOS 13中提供不同的字符串,并且未来的范围不安全。开发人员不应该依赖于对象描述的特定格式。
// iOS 12
(deviceToken as NSData).description // "<965b251c 6cb1926d e3cb366f dfb16ddd e6b9086a 8a3cac9e 5f857679 376eab7C>"

// iOS 13
(deviceToken as NSData).description // "{length = 32, bytes = 0x965b251c 6cb1926d e3cb366f dfb16ddd ... 5f857679 376eab7c }"

了解更多信息,请阅读这里


如何用JavaScript编写此代码。 - Dhrupal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接