Como converter meu token de dispositivo (NSData) em um NSString?

157

Estou implementando notificações push. Eu gostaria de salvar meu token APNS como uma string.

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

A primeira linha de código imprime nulo. o segundo imprime o token. Como posso obter meu newDeviceToken como um NSString?

Sheehan Alam
fonte
Qual é a saída do segundo NSLog, aquele que imprime newDeviceToken?
22812 rob Mayoff
Duplicar: stackoverflow.com/questions/1305225/…
NiñoScript 1/16
não use a descrição
Fattie

Respostas:

39

usa isto :

NSString * deviceTokenString = [[[[deviceToken description]
                         stringByReplacingOccurrencesOfString: @"<" withString: @""] 
                        stringByReplacingOccurrencesOfString: @">" withString: @""] 
                       stringByReplacingOccurrencesOfString: @" " withString: @""];

NSLog(@"The generated device token string is : %@",deviceTokenString);
kulss
fonte
134
Parece uma má idéia usar a descrição: nada garante que a versão posterior do iOS não altere a implementação e o resultado dessa chamada.
madewulf
16
De fato, essa é uma péssima idéia.
David Snabel-Caunt
21
@madewulf muito agradável de você para mostrar como é uma terrível idéia para a descrição da utilização .. que teria sido ainda melhor se você sugeriu uma alternativa
abbood
6
A solução aqui abaixo com [deviceToken bytes] se encaixa na conta.
madewulf
37
Acontece que no Swift 3 / iOS 10, a descrição em um token de dispositivo retorna "32 bytes". Então sim, não use isso.
Victor Luft
231

Se alguém estiver procurando uma maneira de fazer isso no Swift:

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)")
}

Editar: Para Swift 3

Swift 3 apresenta o Datatipo, com semântica de valores. Para converter deviceTokenem uma String, você pode fazer o seguinte:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
    print(token)
}
Sascha
fonte
118
Por que isso tem que ser tão complicado, o que há de errado com o sistema operacional nos fornecer uma string, já que é disso que todos precisam? Obrigado por esta solução.
Piwaf
3
@Sascha Espero que aprovam a minha edição para a sua resposta muito útil :)
jrturton
16
Eu refatorado: let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() qiita.com/mono0926/items/3cf0dca3029f32f54a09
mono
2
Não recomendo usar .description, pois não é garantido que seja estável. Confira a minha resposta aqui: stackoverflow.com/questions/9372815/...
taylor swift
7
Você pode explicar o que "%02.2hhxfaz?
Querida
155

Alguém me ajudou com isso. Só estou passando

- (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];
}
Shubhank
fonte
5
Esta é a melhor solução, uma vez encondig bytes como hex, implica que você pode contá-lo;)
loretoparisi
4
No XCode 5, tive que converter o deviceToken para compilar: const unsigned * tokenBytes = (const unsigned *) [deviceToken bytes];
Ponytech # 6/13
3
Os tokens terão mais de 32 bytes em breve, portanto será necessário um loop sobre cada byte, em vez de oito números inteiros codificados.
precisa
5
Essa seria uma solução melhor? 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 23/03
9
Important: APNs device tokens are of variable length. Do not hard-code their size.Apple diz.
Erkanyildiz 18/10/2016
141

Você poderia usar isso

- (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];
}
Vlad Polyanskiy
fonte
11
Essa deve ser a resposta aceita, pois é muito mais segura do que usar description.
precisa saber é o seguinte
8
Essa é a única resposta correta no Objective-C que lidará com o próximo aumento no tamanho do token.
quer
Concordou que esta é provavelmente a maneira mais segura, pois não assume nenhum tamanho / comprimento de token específico.
Ryan H.
Funciona no iOS 10.
Tjalsma
2
Eu usei [token appendFormat:@"%02.2hhx", data[i]];como Amazon SNS requer letras minúsculas.
Manuel Schmitzberger 18/07/19
43

Para quem deseja o Swift 3 e o método mais fácil

func extractTokenFromData(deviceToken:Data) -> String {
    let token = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
    return token.uppercased();
}
Anand
fonte
1
Eu escrevi o mesmo código :) Esta é a versão mais rápida e só funciona.
Quver 10/10
1
@Anand u pode explicar o que é happing neste códigodeviceToken.reduce("", {$0 + String(format: "%02X", $1)})
Ramakrishna
1
Ele usa a função de redução de swift que serializa Data em sequência hexadecimal e depois em String. Para entender mais sobre reduzir a função ler useyourloaf.com/blog/swift-guide-to-map-filter-reduce
Anand
15

Explicação da resposta na%02.2hhx votação alta :

  • %: Apresenta o xespecificador de conversão.
  • 02: A largura mínima do valor convertido é 2. Se o valor convertido tiver menos bytes que a largura do campo, deverá ser preenchido 0à esquerda.
  • .2: Fornece o número mínimo de dígitos a serem exibidos para o xespecificador de conversão.
  • hh: Especifica que o xespecificador de conversão se aplica a um argumento de caractere assinado ou não assinado (o argumento será promovido de acordo com as promoções de números inteiros, mas seu valor será convertido em caractere assinado ou não assinado antes da impressão).
  • x: O argumento não assinado deve ser convertido para o formato hexadecimal não assinado no estilo "dddd"; as letras "abcdef" são usadas. A precisão especifica o número mínimo de dígitos a serem exibidos; se o valor que está sendo convertido puder ser representado em menos dígitos, ele será expandido com zeros à esquerda. A precisão padrão é 1. O resultado da conversão de zero com uma precisão explícita de zero não deve conter caracteres.

Para mais detalhes, consulte a especificação IEEE printf .


Com base na explicação acima, acho melhor mudar %02.2hhxpara %02xou %.2x.

Para o Swift 5, os seguintes métodos são todos viáveis:

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)})

O teste é o seguinte:

let deviceToken = (0..<32).reduce(Data(), {$0 + [$1]})
print(deviceToken.reduce("", {$0 + String(format: "%.2x", $1)}))
// Print content:
// 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
jqgsninimo
fonte
Obrigado por esta resposta. Isso funciona também com o iOS 12? Ou isso depende apenas da versão Swift?
Markus
1
@ Markus Isso funciona no iOS 12, depende apenas da versão Swift.
jqgsninimo
14

É a minha solução e funciona bem no meu aplicativo:

    NSString* newToken = [[[NSString stringWithFormat:@"%@",deviceToken] 
stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""];
  • converter NSDatapara NSStringcomstringWithFormat
  • apare o "<>"
  • remover os espaços
Zeb
fonte
10
Isso apenas chama implicitamente -description, por isso não é mais seguro que a resposta aceita.
Jszumski
Você pode vincular sua fonte? Não consigo encontrar informações sobre isso em nenhum lugar. THX.
Zeb
Encontrei! Eu acho que é um pouco diferente. Usar o atributo description diretamente não é seguro, pois pode ser alterado em versões futuras, mas se você usá-lo através de um método NSString, dificilmente terá problemas.
Zeb
5
Não, isso realmente chama o descriptiondeviceToken, como jszumski diz.
Jonny
1
@Zeb Não é seguro depender descriptionse você o chama diretamente ou usa-o através de outro método, porque o formato da string retornada pode ser alterado a qualquer momento. A solução correta está aqui: stackoverflow.com/a/16411517/108105
Tom Dalling
10

Eu acho que converter deviceToken para string de bytes hexadecimais não faz sentido. Por quê? Você o enviará ao seu back-end, onde será transformado novamente em bytes para ser enviado ao APNS. Portanto, use o método NSDatabase64EncodedStringWithOptions , envie-o para o servidor e, em seguida, use dados decodificados base64 reversos :) Isso é muito mais fácil :)

NSString *tokenString = [tokenData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
Oleg Shanyuk
fonte
@ jeet.chanchawat não adicione código às respostas de outros usuários. Não queremos colocar palavras na boca deles, especialmente ao adicionar Swift a uma resposta Objective-C. Em vez disso, adicione sua própria resposta.
JAL
2
Só não queria plagiar a resposta do @Oleg Shanyuk. Como é apenas a tradução em outro idioma, construída com base em sua resposta, ele merece o futuro de votos. Se eu adicionar outra resposta, isso me dará votos positivos para a resposta que é pesquisa de outra pessoa. Espero que isso justifique a edição.
7266 jeet.chanchawat
10

No iOS 13 descriptionserá interrompido, use este

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

Para maior clareza, vamos detalhar isso e explicar cada parte:

O método map opera em cada elemento de uma sequência. Como Data é uma sequência de bytes no Swift, o fechamento passado é avaliado para cada byte no deviceToken. O inicializador String (format :) avalia cada byte nos dados (representado pelo parâmetro anônimo $ 0) usando o especificador de formato% 02x, para produzir uma representação hexadecimal de 2 dígitos, preenchida com zero, do byte / número inteiro de 8 bits. Depois de coletar cada representação de bytes criada pelo método map, join () concatena cada elemento em uma única string.

O PS não usar descrição fornece uma sequência diferente no iOS 12 e iOS 13 e não é seguro conforme o escopo futuro. Os desenvolvedores não deveriam confiar em um formato específico para a descrição de um objeto.

// 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 }"

Para mais informações, leia Isto .

SuryaKantSharma
fonte
10

No iOS 13, a descrição estará em formato diferente. Por favor, use o código abaixo para buscar o token do dispositivo.

- (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];
}
Vishnu Prakash
fonte
Solução perfeita para o ios 13. Obrigado Vishnu
Manish
1
Atualmente não compila - lengthno loop for deve ser alterado para len. Aparentemente, uma alteração muito pequena para eu fazer uma edição .. Mas, além disso, funciona perfeitamente!
Anders Friis
você é um salva-vidas
Moeez Akram
3

Esta é uma solução um pouco mais curta:

NSData *token = // ...
const uint64_t *tokenBytes = token.bytes;
NSString *hex = [NSString stringWithFormat:@"%016llx%016llx%016llx%016llx",
                 ntohll(tokenBytes[0]), ntohll(tokenBytes[1]),
                 ntohll(tokenBytes[2]), ntohll(tokenBytes[3])];
k06a
fonte
3

Versão Swift funcional

Um forro:

let hexString = UnsafeBufferPointer<UInt8>(start: UnsafePointer(data.bytes),
count: data.length).map { String(format: "%02x", $0) }.joinWithSeparator("")

Aqui está um formulário de extensão reutilizável e auto-documentável:

extension NSData {
    func base16EncodedString(uppercase uppercase: Bool = false) -> String {
        let buffer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(self.bytes),
                                                count: self.length)
        let hexFormat = uppercase ? "X" : "x"
        let formatString = "%02\(hexFormat)"
        let bytesAsHexStrings = buffer.map {
            String(format: formatString, $0)
        }
        return bytesAsHexStrings.joinWithSeparator("")
    }
}

Como alternativa, use em reduce("", combine: +)vez de joinWithSeparator("")ser visto como um mestre funcional por seus colegas.


Edit: eu mudei String ($ 0, radix: 16) para String (formato: "% 02x", $ 0), porque os números de um dígito precisavam ter um preenchimento zero

(Ainda não sei como marcar uma pergunta como duplicada dessa outra , então acabei de postar minha resposta novamente)

NiñoScript
fonte
Funciona para mim, obrigado.
hasya
3

2020

token como texto ...

let tat = deviceToken.map{ data in String(format: "%02.2hhx", data) }.joined()

ou se você preferir

let tat2 = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()

(resultado é o mesmo)

Fattie
fonte
2

Jogando minha resposta na pilha. Evite usar a análise de string; Não é garantido pelos documentos que o NSData.description sempre funcione dessa maneira.

Implementação do Swift 3:

extension Data {
    func hexString() -> String {
        var bytesPointer: UnsafeBufferPointer<UInt8> = UnsafeBufferPointer(start: nil, count: 0)
        self.withUnsafeBytes { (bytes) in
            bytesPointer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(bytes), count:self.count)
        }
        let hexBytes = bytesPointer.map { return String(format: "%02hhx", $0) }
        return hexBytes.joined()
    }
}
taylor rápido
fonte
1

Eu tentei testar dois métodos diferentes com formato "%02.2hhx"e"%02x"

    var i :Int = 0
    var j: Int = 0
    let e: Int = Int(1e4)
    let time = NSDate.timeIntervalSinceReferenceDate
    while i < e {
        _ =  deviceToken.map { String(format: "%02x", $0) }.joined()
        i += 1
    }
    let time2 = NSDate.timeIntervalSinceReferenceDate
    let delta = time2-time
    print(delta)

    let time3 = NSDate.timeIntervalSinceReferenceDate
    while j < e {
        _ =  deviceToken.reduce("", {$0 + String(format: "%02x", $1)})
        j += 1
    }
    let time4 = NSDate.timeIntervalSinceReferenceDate
    let delta2 = time4-time3
    print(delta2)

e o resultado é que o mais rápido é, "%02x"em média, 2,0 vs 2,6 para a versão reduzida:

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

O uso de updateAccumulatingResult é mais eficiente do que as várias outras abordagens encontradas aqui, então aqui está a maneira mais rápida de especificar seus Databytes:

func application(_ application: UIApplication,
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.reduce(into: "") { $0 += String(format: "%.2x", $1) }
    print(token)
}
Alex Curylo
fonte
Alex, não seria% 02.2hhx
Fattie 01/03
0

Para Swift:

var characterSet: NSCharacterSet = NSCharacterSet( charactersInString: "<>" )
    var deviceTokenString: String = ( deviceToken.description as NSString )
    .stringByTrimmingCharactersInSet( characterSet )
    .stringByReplacingOccurrencesOfString( " ", withString: "" ) as String

println( deviceTokenString )
Adarsh ​​GJ
fonte
0

Que tal uma solução de linha?

Objetivo C

NSString *token = [[data.description componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet]invertedSet]]componentsJoinedByString:@""];

Rápido

let token = data.description.componentsSeparatedByCharactersInSet(NSCharacterSet.alphanumericCharacterSet().invertedSet).joinWithSeparator("")
Nikolay Shubenkov
fonte
2
Esta é a solução simples e a melhor. Graças
Emmy
0

Veja como você faz isso no Xamarin.iOS

public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
    var tokenStringBase64 = deviceToken.GetBase64EncodedString(NSDataBase64EncodingOptions.None);
    //now you can store it for later use in local storage
}
Papai bêbado
fonte
-1
NSString *tokenString = [[newDeviceToken description] stringByReplacingOccurrencesOfString:@"[<> ]" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, [[newDeviceToken description] length])];
Genja Grishin
fonte
grande solução A partir de hoje, pode ser implified para credentials.token.description.replacingOccurrences (de: "[<>]", com "", opções: .regularExpression, intervalo: nil)
Frank
-1

Rápido:

let tokenString = deviceToken.description.stringByReplacingOccurrencesOfString("[ <>]", withString: "", options: .RegularExpressionSearch, range: nil)
Tony
fonte
-2
-(NSString *)deviceTokenWithData:(NSData *)data
{
    NSString *deviceToken = [[data description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
    deviceToken = [deviceToken stringByReplacingOccurrencesOfString:@" " withString:@""];
    return deviceToken;
}
Mallikarjuna SB
fonte
-2

Rápido

    // make sure that we have token for the devie on the App
    func application(application: UIApplication
        , didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {

            var tokenStr = deviceToken.description
            tokenStr = tokenStr.stringByReplacingOccurrencesOfString("<", withString: "", options: [], range: nil)
            tokenStr = tokenStr.stringByReplacingOccurrencesOfString(">", withString: "", options: [], range: nil)
            tokenStr = tokenStr.stringByReplacingOccurrencesOfString(" ", withString: "", options: [], range: nil)



            print("my token is: \(tokenStr)")

    }
Vinod Joshi
fonte
-2

Use excelente categoria!

// arquivo .h

@interface NSData (DeviceToken)

- (NSString *)stringDeviceToken;

@end    

// arquivo .m

#import "NSData+DeviceToken.h"

@implementation NSData (DeviceToken)

- (NSString *)stringDeviceToken {
    const unsigned *deviceTokenBytes = [deviceToken bytes];
    NSString *deviceToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
                     ntohl(deviceTokenBytes[0]), ntohl(deviceTokenBytes[1]), ntohl(deviceTokenBytes[2]),
                     ntohl(deviceTokenBytes[3]), ntohl(deviceTokenBytes[4]), ntohl(deviceTokenBytes[5]),
                     ntohl(deviceTokenBytes[6]), ntohl(deviceTokenBytes[7])];
    return deviceToken;
}

@fim

// AppDelegate.m

#import "NSData+DeviceToken.h"

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    NSString *token = deviceToken.stringDeviceToken;
}

Funciona bem!

LLIAJLbHOu
fonte
Não confie no uso de "descrição", pois o formato pode mudar no futuro. É apenas para fins de exibição.
Michael Peterson
-3

Swift 3:

Se alguém estiver procurando uma maneira de obter o token do dispositivo no Swift 3. Use o snippet modificado abaixo.

    let characterSet: CharacterSet = CharacterSet( charactersIn: "<>" )

    let deviceTokenString: String = (deviceToken.description as NSString)
        .trimmingCharacters(in: characterSet as CharacterSet)
        .replacingOccurrences(of: " ", with: "")
        .uppercased()

    print(deviceTokenString)
Laksh Gandikota
fonte
2
Não recomendo usar .description, pois não é garantido que permaneça o mesmo. Veja minha resposta aqui: stackoverflow.com/questions/9372815/...
taylor swift
-4
var token: String = ""
for i in 0..<deviceToken.count {
    token += String(format: "%02.2hhx", deviceToken[i] as CVarArg)
}

print(token)
Abdul Yasin
fonte
1
O uso da descrição não é seguro, pois não é garantido o mesmo resultado no futuro.
Sahil Kapoor
-4

A solução @kulss postada aqui, embora sem elegância, mas com a virtude da simplicidade, não funciona mais no iOS 13, pois descriptionfuncionará de maneira diferente para o NSData. Você ainda pode usar debugDescription.

NSString * deviceTokenString = [[[[deviceToken debugDescription]
                     stringByReplacingOccurrencesOfString: @"<" withString: @""] 
                    stringByReplacingOccurrencesOfString: @">" withString: @""] 
                   stringByReplacingOccurrencesOfString: @" " withString: @""];
johnyu
fonte
-7

Tente este, a menos que os dados sejam nulos.

NSString* newStr = [[NSString alloc] initWithData:newDeviceToken encoding:NSUTF8StringEncoding];

Namaded Ahmad
fonte
Eu tentei esse, não funciona. Eu tenho comentado no meu snippet de código.
Sheehan Alam
@SheehanAlam Esse cara conseguiu. Veja como está se convertendo em string. stackoverflow.com/questions/4994302/…
Naveed Ahmad
-9
NSString *tokenstring = [[NSString alloc] initWithData:token encoding:NSUTF8StringEncoding];
Ravikant
fonte
Isso funciona quando os dados são uma sequência, no entanto, o deviceToken não é uma sequência.
Simon Epskamp