¿Cómo puedo convertir mi token de dispositivo (NSData) en un NSString?

157

Estoy implementando notificaciones push. Me gustaría guardar mi token APNS como una cadena.

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

La primera línea de código imprime nulo. el segundo imprime la ficha. ¿Cómo puedo obtener mi newDeviceToken como NSString?

Sheehan Alam
fuente
¿Cuál es la salida del segundo NSLog, el que imprime newDeviceToken?
Rob Mayoff
NO use la descripción
Fattie

Respuestas:

39

utilizar este :

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

NSLog(@"The generated device token string is : %@",deviceTokenString);
kulss
fuente
134
Parece una mala idea usar la descripción: nada asegura que la versión posterior de iOS no cambie la implementación y el resultado de esta llamada.
madewulf
16
De hecho, esta es una muy mala idea.
David Snabel-Caunt
21
@madewulf muy amable de su parte para señalar la forma en que es una idea terrible como a la descripción del uso .. habría sido aún mejor si han sugerido una alternativa
Abbood
66
La solución aquí debajo con [deviceToken bytes] se ajusta a la factura.
madewulf
37
Resulta que a partir de Swift 3 / iOS 10, .description en un token de dispositivo devuelve "32 bytes". Así que sí, no uses esto.
Victor Luft el
231

Si alguien está buscando una manera de hacer esto en 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 introduce el Datatipo, con semántica de valor. Para convertir el deviceTokenen una cadena, puede hacer lo siguiente:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
    print(token)
}
Sascha
fuente
118
¿Por qué esto tiene que ser tan complicado? ¿Qué tiene de malo que el sistema operativo nos dé una cadena ya que eso es lo que todos necesitan? Gracias por esta solución
Piwaf
3
@ Sascha Espero que apruebe mi edición a su respuesta muy útil :)
jrturton
16
Refactoré: let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() qiita.com/mono0926/items/3cf0dca3029f32f54a09
mono
2
No recomiendo usar .description ya que no se garantiza que sea estable. Mira mi respuesta aquí: stackoverflow.com/questions/9372815/…
swift taylor
77
¿Puedes explicar qué hace "%02.2hhx?
Miel
155

Alguien me ayudó con esto. Estoy de paso

- (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
fuente
55
Esta es la mejor solución, ya que encondig bytes como hexadecimal, implica que puede contarlo;)
loretoparisi
44
En XCode 5 tuve que lanzar el deviceToken para que se compilara: const unsigned * tokenBytes = (const unsigned *) [bytes de DeviceToken];
Ponytech
3
Los tokens pronto serán mayores de 32 bytes, por lo que será necesario que se haga un bucle sobre cada byte, en lugar de ocho enteros codificados.
Tom Dalling
55
¿Sería esta una mejor solución? 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
Important: APNs device tokens are of variable length. Do not hard-code their size.Dice Apple.
erkanyildiz
141

Podrías usar esto

- (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
fuente
11
Esta debería ser la respuesta aceptada, ya que es mucho más segura que usarla description.
DrMickeyLauer
8
Esta es la única respuesta correcta en Objective-C que manejará el próximo aumento en el tamaño del token.
Tom Dalling
Convino en que esta es probablemente la forma más segura ya que no asume ningún tamaño / longitud de token en particular.
Ryan H.
Funciona en iOS 10.
Tjalsma
2
He usado [token appendFormat:@"%02.2hhx", data[i]];como Amazon SNS requiere minúsculas.
Manuel Schmitzberger
43

Para aquellos que quieran en Swift 3 y el método más fácil

func extractTokenFromData(deviceToken:Data) -> String {
    let token = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
    return token.uppercased();
}
Anand
fuente
1
Escribí el mismo código :) Esta es la versión más rápida, y solo funciona
Quver
1
@ Y, ¿puedes explicar qué está pasando en este códigodeviceToken.reduce("", {$0 + String(format: "%02X", $1)})
Ramakrishna
1
Utiliza la función de reducción de swift que serializa los datos en una cadena hexadecimal y luego en una cadena. Para entender más sobre la función de reducción, lea useyourloaf.com/blog/swift-guide-to-map-filter-reduce
Anand
15

Explicación de %02.2hhxen la respuesta de alta votación :

  • %: Presenta el xespecificador de conversión.
  • 02: El ancho mínimo del valor convertido es 2. Si el valor convertido tiene menos bytes que el ancho del campo, se rellenará 0a la izquierda.
  • .2: Proporciona el número mínimo de dígitos que deben aparecer para el xespecificador de conversión.
  • hh: Especifica que el xespecificador de conversión se aplica a un argumento con carácter firmado o sin signo (el argumento se habrá promocionado de acuerdo con las promociones de enteros, pero su valor se convertirá en carácter con signo o sin signo antes de imprimir).
  • x: El argumento sin signo se convertirá al formato hexadecimal sin signo en el estilo "dddd"; se usan las letras "abcdef". La precisión especifica el número mínimo de dígitos a aparecer; Si el valor que se está convirtiendo puede representarse en menos dígitos, se expandirá con ceros a la izquierda. La precisión predeterminada es 1. El resultado de convertir cero con una precisión explícita de cero no será caracteres.

Para obtener más detalles, consulte la especificación IEEE printf .


Basado en la explicación anterior, creo que es mejor cambiar %02.2hhxa %02xo %.2x.

Para Swift 5, los siguientes métodos son factibles:

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

La prueba es la siguiente:

let deviceToken = (0..<32).reduce(Data(), {$0 + [$1]})
print(deviceToken.reduce("", {$0 + String(format: "%.2x", $1)}))
// Print content:
// 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
jqgsninimo
fuente
Gracias por esta respuesta ¿Funciona esto también con iOS 12? ¿O solo depende de la versión Swift?
Markus
1
@ Markus Esto funciona en iOS 12, solo depende de la versión Swift.
jqgsninimo
14

Es mi solución y funciona bien en mi aplicación:

    NSString* newToken = [[[NSString stringWithFormat:@"%@",deviceToken] 
stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""];
  • convertir NSDataa NSStringconstringWithFormat
  • recortar el "<>"
  • eliminar los espacios
Zeb
fuente
10
Esto solo llama implícitamente -description, por lo que no es más seguro que la respuesta aceptada.
jszumski
¿Puedes por favor vincular tu fuente? No puedo encontrar información al respecto en ningún lado. Gracias.
Zeb
¡Lo encontré! Creo que es un poco diferente. Usar el atributo de descripción directamente no es seguro porque podría cambiar en futuras versiones, pero si lo usa A TRAVÉS de un método NSString, difícilmente tendrá problemas.
Zeb
55
No, esto realmente llama descriptional dispositivo Tomado como dice jszumski.
Jonny
1
@Zeb No es seguro confiar en descriptionsi lo llamas directamente o si lo usas a través de otro método, porque el formato de la cadena devuelta podría cambiarse en cualquier momento. La solución correcta está aquí: stackoverflow.com/a/16411517/108105
Tom Dalling
10

Creo que la conversión de deviceToken a hexadecimal no tiene sentido. ¿Por qué? Lo enviará a su backend, donde se transformará de nuevo a bytes para ser enviado a APNS. Entonces, use el método de NSDatabase64EncodedStringWithOptions , empújelo al servidor y luego use datos codificados en base64 inversa :) Eso es mucho más fácil :)

NSString *tokenString = [tokenData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
Oleg Shanyuk
fuente
@ jeet.chanchawat por favor no agregue código a las respuestas de otros usuarios. No queremos poner palabras en su boca, especialmente al agregar Swift a una respuesta de Objective-C. En cambio, agregue su propia respuesta.
JAL
2
Simplemente no quería plagiar la respuesta de @Oleg Shanyuk. Como es solo la traducción en otro idioma basado en su respuesta, merece el futuro con votos. Si agrego otra respuesta, me dará votos positivos para la respuesta, que es la investigación de otra persona. Espero que esto justifique la EDIT.
jeet.chanchawat
10

En iOS 13 descriptionse romperá, así que usa esto

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

Para mayor claridad, analicemos esto y expliquemos cada parte:

El método de mapa opera en cada elemento de una secuencia. Como los datos son una secuencia de bytes en Swift, el cierre pasado se evalúa para cada byte en deviceToken. El inicializador String (formato :) evalúa cada byte en los datos (representados por el parámetro anónimo $ 0) utilizando el especificador de formato% 02x, para producir una representación hexadecimal de 2 dígitos con relleno de cero del byte / entero de 8 bits. Después de recopilar cada representación de byte creada por el método de mapa, join () concatena cada elemento en una sola cadena.

La descripción de no usar PS da una cadena diferente en iOS 12 e iOS 13 y no es segura según el alcance futuro. Los desarrolladores no deberían haberse basado en un formato específico para la descripción de un 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 más información lea esto .

SuryaKantSharma
fuente
10

En iOS 13, la descripción estará en un formato diferente. Utilice amablemente el siguiente código para obtener el token del 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
fuente
Solución perfecta para iOS 13. Gracias Vishnu
Manish
1
Actualmente no se compila; lengthen el bucle for debe cambiarse a len. Aparentemente un cambio demasiado pequeño para que yo pueda hacer una edición ... ¡Pero si no funciona perfectamente!
Anders Friis
eres un salvavidas
Moeez Akram
3

Esta es una solución un poco más corta:

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
fuente
3

Versión Swift funcional

Un trazador de líneas:

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

Aquí hay un formulario de extensión reutilizable y auto documentable:

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

Alternativamente, use en reduce("", combine: +)lugar de joinWithSeparator("")ser visto como un maestro funcional por sus pares.


Editar: cambié String ($ 0, radix: 16) a String (formato: "% 02x", $ 0), porque los números de un dígito necesitaban tener un cero de relleno

(Todavía no sé cómo marcar una pregunta como un duplicado de esta otra , así que acabo de publicar mi respuesta nuevamente)

NiñoScript
fuente
Funciona para mí, gracias.
Hasya
3

2020

token como texto ...

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

o si lo prefieres

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

(el resultado es el mismo)

Fattie
fuente
2

Lanzando mi respuesta en la pila. Evite usar el análisis de cadenas; Los documentos no garantizan que NSData.description siempre funcione de esa manera.

Implementación de 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
fuente
1

He intentado probar dos métodos diferentes con formato "%02.2hhx"y"%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)

y el resultado es que el más rápido es "%02x"en promedio 2.0 vs 2.6 para la versión reducida:

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

El uso de updateAccumulatingResult es más eficiente que los otros enfoques que se encuentran aquí, así que esta es la forma más rápida de stringificar sus Databytes:

func application(_ application: UIApplication,
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.reduce(into: "") { $0 += String(format: "%.2x", $1) }
    print(token)
}
Alex Curylo
fuente
Alex, no sería% 02.2hhx
Fattie
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
fuente
0

¿Qué pasa con la solución de una línea?

C objetivo

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

Rápido

let token = data.description.componentsSeparatedByCharactersInSet(NSCharacterSet.alphanumericCharacterSet().invertedSet).joinWithSeparator("")
Nikolay Shubenkov
fuente
2
Esta es la solución simple y la mejor. Gracias
Emmy
0

Así es como lo haces en 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
}
Papá borracho
fuente
-1
NSString *tokenString = [[newDeviceToken description] stringByReplacingOccurrencesOfString:@"[<> ]" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, [[newDeviceToken description] length])];
Genja Grishin
fuente
gran solución A partir de hoy, puede estar implícito en credentials.token.description.replacingOccurrences (of: "[<>]", with: "", options: .regularExpression, range: nil)
Frank
-1

Rápido:

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

Utilice excelente categoría!

// archivo .h

@interface NSData (DeviceToken)

- (NSString *)stringDeviceToken;

@end    

// archivo .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;
}

@final

// AppDelegate.m

#import "NSData+DeviceToken.h"

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

¡Funciona bien!

LLIAJLbHOu
fuente
No confíe en el uso de "descripción", su formato podría cambiar en el futuro. Es solo para fines de visualización.
Michael Peterson
-3

Swift 3:

Si alguien está buscando una forma de obtener el token del dispositivo en Swift 3. Utilice el fragmento modificado a continuación.

    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
fuente
2
No recomiendo usar .description ya que no se garantiza que siga siendo el mismo. Vea mi respuesta aquí: stackoverflow.com/questions/9372815/…
swift taylor
-4
var token: String = ""
for i in 0..<deviceToken.count {
    token += String(format: "%02.2hhx", deviceToken[i] as CVarArg)
}

print(token)
Abdul Yasin
fuente
1
El uso de la descripción no es seguro, ya que no se garantiza que dé los mismos resultados en el futuro.
Sahil Kapoor
-4

La solución @kulss publicada aquí, aunque carece de elegancia pero tiene la virtud de la simplicidad, ya no funciona en iOS 13, ya descriptionque funcionará de manera diferente para NSData. Sin debugDescriptionembargo, aún puedes usarlo .

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

Pruebe este a menos que los datos estén terminados en nulo.

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

Naveed Ahmad
fuente
Intenté ese, no funciona. Lo he comentado en mi fragmento de código.
Sheehan Alam
@SheehanAlam Este tipo lo logró. Echa un vistazo a cómo se está convirtiendo en cadena. stackoverflow.com/questions/4994302/…
Naveed Ahmad
-9
NSString *tokenstring = [[NSString alloc] initWithData:token encoding:NSUTF8StringEncoding];
Ravikant
fuente
Esto funciona cuando los datos son una cadena, sin embargo, el deviceToken no es una cadena.
Simon Epskamp