Jak przekonwertować token urządzenia (NSData) na NSString?


157

Wdrażam powiadomienia push. Chciałbym zapisać mój token APNS jako ciąg.

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

Pierwsza linia kodu wypisuje wartość null. druga drukuje token. Jak mogę uzyskać mój newDeviceToken jako NSString?


Jaki jest wynik drugiego NSLog, tego, który drukuje newDeviceToken?
rob mayoff,


NIE używaj opisu
Fattie

Odpowiedzi:


40

Użyj tego :

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

NSLog(@"The generated device token string is : %@",deviceTokenString);

134
Używanie opisu wydaje się złym pomysłem: nic nie gwarantuje, że późniejsza wersja iOS nie zmieni implementacji i wyniku tego wywołania.
madewulf

16
Rzeczywiście, to naprawdę zły pomysł.
David Snabel-Caunt

21
@madewulf bardzo miło z twojej strony, że wskazałeś, że użycie opisu jest tak okropnym pomysłem. Byłoby jeszcze lepiej, gdybyś zasugerował alternatywę
abbood

6
Rozwiązanie tutaj poniżej z [deviceToken bajtów] pasuje do rachunku.
madewulf

37
Okazuje się, że od wersji Swift 3 / iOS 10 .description na tokenie urządzenia zwraca „32 bajty”. Więc tak, nie używaj tego.
Victor Luft,

231

Jeśli ktoś szuka sposobu na zrobienie tego w 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)")
}

Edycja: dla Swift 3

Swift 3 wprowadza Datatyp z semantyką wartości. Aby przekonwertować deviceTokenciąg na ciąg, możesz wykonać następujące czynności:

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

118
Dlaczego to musi być tak skomplikowane, co jest nie tak z systemem operacyjnym, który podaje nam ciąg znaków, skoro tego wszyscy potrzebują? Dziękuję za to rozwiązanie.
Piwaf

3
@Sascha Mam nadzieję, że zaaprobujesz moją zmianę w swojej bardzo przydatnej odpowiedzi :)
jrturton

16
Refaktoryzowałem: let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() qiita.com/mono0926/items/3cf0dca3029f32f54a09
mono

2
Nie polecam używania .description, ponieważ nie gwarantuje to stabilności. Sprawdź moją odpowiedź tutaj: stackoverflow.com/questions/9372815/ ...
swift taylor

7
Czy możesz wyjaśnić, co robi "%02.2hhx?
Miód

155

Ktoś mi w tym pomógł, po prostu przechodzę dalej

- (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
To najlepsze rozwiązanie, ponieważ zapisywanie bajtów jako hex oznacza, że ​​można to policzyć;)
loretoparisi

4
Na XCode 5 musiałem rzucić deviceToken, aby skompilować: const unsigned * tokenBytes = (const unsigned *) [deviceToken bytes];
Ponytech

3
Wkrótce tokeny będą większe niż 32 bajty, więc zamiast ośmiu zakodowanych na stałe liczb całkowitych będzie to pętla na każdym bajcie.
Tom Dalling,

5
Czy to byłoby lepsze rozwiązanie? 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.Apple mówi.
erkanyildiz

141

Możesz tego użyć

- (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
To powinna być akceptowana odpowiedź, ponieważ jest o wiele bezpieczniejsza niż używanie description.
DrMickeyLauer

8
To jedyna poprawna odpowiedź w celu C, która poradzi sobie z nadchodzącym wzrostem rozmiaru tokena.
Tom Dalling

Uzgodniono, że jest to prawdopodobnie najbezpieczniejszy sposób, ponieważ nie zakłada żadnego konkretnego rozmiaru / długości tokena.
Ryan H.

Działa w iOS 10.
Tjalsma

2
Użyłem, [token appendFormat:@"%02.2hhx", data[i]];ponieważ Amazon SNS wymaga małych liter.
Manuel Schmitzberger

43

Dla tych, którzy chcą w Swift 3 i najłatwiejszej metodzie

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

1
Napisałem ten sam kod :) To jest najbardziej szybka wersja i tylko ta działa
Quver

1
@Anand czy u proszę wyjaśnić, co dzieje się w tym kodziedeviceToken.reduce("", {$0 + String(format: "%02X", $1)})
Ramakrishna

1
Używa funkcji redukującej swift, która serializuje dane do ciągu szesnastkowego, a następnie do ciągu. Aby dowiedzieć się więcej na temat funkcji redukcji, przeczytaj useyourloaf.com/blog/swift-guide-to-map-filter-reduce
Anand

15

Wyjaśnienie %02.2hhxw wysokiej głosowanie odpowiedź :

  • %: Wprowadza specyfikator xkonwersji.
  • 02: Minimalna szerokość konwertowanej wartości wynosi 2. Jeżeli przekonwertowana wartość ma mniej bajtów niż szerokość pola, należy ją dopełnić 0po lewej stronie.
  • .2: Podaje minimalną liczbę cyfr, które mają pojawić się w x konwersji.
  • hh: Określa, że ​​specyfikator xkonwersji ma zastosowanie do argumentu ze znakiem lub bez znaku (argument będzie promowany zgodnie z promocjami w postaci liczb całkowitych, ale jego wartość zostanie przekonwertowana na znak ze znakiem lub znak bez znaku przed wydrukowaniem).
  • x: Argument bez znaku zostanie przekonwertowany na format szesnastkowy bez znaku w stylu „dddd”; używane są litery „abcdef”. Precyzja określa minimalną liczbę cyfr, które mają się pojawić; jeżeli przekształcaną wartość można przedstawić za pomocą mniejszej liczby cyfr, należy ją rozszerzyć o zera na początku. Domyślna dokładność wynosi 1. Wynik konwersji zera z wyraźną precyzją zera nie może zawierać żadnych znaków.

Więcej informacji można znaleźć w specyfikacji IEEE printf .


Na podstawie powyższego wyjaśnienia myślę, że lepiej zmienić %02.2hhxna %02xlub %.2x.

W przypadku Swift 5 możliwe są wszystkie następujące metody:

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

Test wygląda następująco:

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

Dziękuję za tę odpowiedź. Czy to działa również z iOS 12? Czy zależy to tylko od wersji Swift?
Markus

1
@Markus Działa w iOS 12, zależy tylko od wersji Swift.
jqgsninimo

14

To moje rozwiązanie i działa dobrze w mojej aplikacji:

    NSString* newToken = [[[NSString stringWithFormat:@"%@",deviceToken] 
stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""];
  • przekonwertować NSDatana NSStringzstringWithFormat
  • przyciąć „<>”
  • usuń spacje

10
To po prostu niejawnie wywołuje -description, więc nie jest bezpieczniejsze niż zaakceptowana odpowiedź.
jszumski

Czy możesz połączyć swoje źródło? Nigdzie nie mogę znaleźć informacji o tym. Dzięki.
Zeb

Znalazłem to! Myślę, że jest trochę inaczej. Bezpośrednie użycie atrybutu description nie jest bezpieczne, ponieważ może się zmienić w przyszłych wersjach, ale jeśli użyjesz go PRZEZ metodę NSString, prawie nie będziesz mieć problemów.
Zeb

5
Nie, to naprawdę wywołuje descriptiondeviceToken, jak mówi jszumski.
Jonny

1
@Zeb Nie można polegać na tym, descriptionczy wywołasz go bezpośrednio, czy użyjesz go inną metodą, ponieważ format zwracanego ciągu można zmienić w dowolnym momencie. Prawidłowe rozwiązanie jest tutaj: stackoverflow.com/a/16411517/108105
Tom Dalling

10

Myślę, że konwersja deviceToken na ciąg bajtów szesnastkowych nie ma sensu. Czemu? Wyślesz go do swojego zaplecza, gdzie zostanie przekształcony z powrotem w bajty do przekazania do APNS. Tak więc, użyj metody NSDatabase64EncodedStringWithOptions , wypchnij ją na serwer, a następnie użyj odwrotnych danych dekodowanych base64 :) To jest o wiele łatwiejsze :)

NSString *tokenString = [tokenData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];

@ jeet.chanchawat proszę nie dodawać kodu do odpowiedzi innych użytkowników. Nie chcemy wkładać im słów do ust, zwłaszcza gdy dodajemy Swifta do odpowiedzi Objective-C. Zamiast tego dodaj własną odpowiedź.
JAL

2
Po prostu nie chciałem plagiatować odpowiedzi @Olega Shanyuka. Ponieważ jest to tylko tłumaczenie w innym języku, oparte na jego odpowiedzi, więc zasługuje na głosy w górę. Jeśli dodam inną odpowiedź, otrzymam głosy za odpowiedź, która jest badaniem kogoś innego. Mam nadzieję, że to uzasadnia EDYCJĘ.
jeet.chanchawat

10

W iOS 13 descriptionsię zepsuje, więc użyj tego

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

Dla jasności podzielmy to i wyjaśnijmy każdą część:

Metoda map działa na każdym elemencie sekwencji. Ponieważ Data jest sekwencją bajtów w Swift, przekazane zamknięcie jest oceniane dla każdego bajtu w deviceToken. Inicjator String (format :) ocenia każdy bajt w danych (reprezentowany przez anonimowy parametr $ 0) przy użyciu specyfikatora formatu% 02x w celu utworzenia dwucyfrowej, szesnastkowej reprezentacji bajtu / 8-bitowej liczby całkowitej z zerami. Po zebraniu każdej reprezentacji bajtowej utworzonej przez metodę map, join () łączy każdy element w jeden ciąg.

PS nie używaj opisu daje inny ciąg w iOS 12 i iOS 13 i nie jest bezpieczny w przyszłym zakresie. Programiści nie powinni polegać na określonym formacie opisu obiektu.

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

Aby uzyskać więcej informacji, przeczytaj This .


10

W iOS 13 opis będzie miał inny format. Użyj poniższego kodu, aby pobrać token urządzenia.

- (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];
}

Idealne rozwiązanie dla iOS 13. Dzięki Vishnu
Manish

1
Obecnie nie kompiluje się - lengthw pętli for należy zmienić na len. Najwyraźniej zbyt mała zmiana, abym mógł dokonać edycji .. Ale poza tym działa idealnie!
Anders Friis


3

To jest trochę krótsze rozwiązanie:

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])];

3

Funkcjonalna wersja Swift

Jedna wkładka:

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

Oto rozszerzenie do wielokrotnego użytku, samodokumentujące się:

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

Alternatywnie, użyj reduce("", combine: +)zamiast joinWithSeparator("")być postrzeganym przez rówieśników jako wzorzec funkcjonalny.


Edycja: Zmieniłem String ($ 0, radix: 16) na String (format: "% 02x", $ 0), ponieważ liczby jednocyfrowe potrzebne do wypełnienia zerami

(Jeszcze nie wiem, jak oznaczyć pytanie jako duplikat tego drugiego , więc ponownie opublikowałem odpowiedź)


U mnie działa, dzięki.
Hasya

3

2020

token jako tekst ...

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

lub jeśli wolisz

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

(wynik jest taki sam)


2

Rzucam moją odpowiedź na stos. Unikaj analizowania ciągów; Dokumentacja nie gwarantuje, że NSData.description zawsze będzie działać w ten sposób.

Wdrożenie 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()
    }
}

1

Próbowałem przetestować dwie różne metody z formatami "%02.2hhx"i"%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)

a wynik jest taki, że najszybsza jest "%02x"średnia 2,0 vs 2,6 dla wersji zredukowanej:

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

1

Korzystanie z metody updateAccumulatingResult jest bardziej wydajne niż różne inne podejścia, które można znaleźć tutaj, więc oto najszybszy sposób na zdefiniowanie Databajtów:

func application(_ application: UIApplication,
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.reduce(into: "") { $0 += String(format: "%.2x", $1) }
    print(token)
}

Alex, czy nie byłoby to% 02.2hhx
Fattie

0

Dla Swift:

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

println( deviceTokenString )

0

A co z rozwiązaniem jednoprzewodowym?

Cel C

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

Szybki

let token = data.description.componentsSeparatedByCharactersInSet(NSCharacterSet.alphanumericCharacterSet().invertedSet).joinWithSeparator("")

2
To jest proste i najlepsze rozwiązanie. Dzięki
Emmy

0

Oto, jak to zrobić w 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
}

-1
NSString *tokenString = [[newDeviceToken description] stringByReplacingOccurrencesOfString:@"[<> ]" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, [[newDeviceToken description] length])];

świetne rozwiązanie Na dzień dzisiejszy można to założyć na credentials.token.description.replacingOccurrences (of: "[<>]", z: "", opcje: .regularExpression, range: nil)
Frank,

-1

Szybki:

let tokenString = deviceToken.description.stringByReplacingOccurrencesOfString("[ <>]", withString: "", options: .RegularExpressionSearch, range: nil)

-2
-(NSString *)deviceTokenWithData:(NSData *)data
{
    NSString *deviceToken = [[data description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
    deviceToken = [deviceToken stringByReplacingOccurrencesOfString:@" " withString:@""];
    return deviceToken;
}

-2

Szybki

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

    }

-2

Użyj doskonałej kategorii!

// plik .h

@interface NSData (DeviceToken)

- (NSString *)stringDeviceToken;

@end    

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

@koniec

// AppDelegate.m

#import "NSData+DeviceToken.h"

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

Działa w porządku!


Nie polegaj na używaniu „opisu”, jego format może się zmienić w przyszłości. Służy wyłącznie do wyświetlania.
Michael Peterson

-3

Swift 3:

Jeśli ktoś szuka sposobu na zdobycie tokena urządzenia w Swift 3. Użyj poniższego zmodyfikowanego fragmentu.

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

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

    print(deviceTokenString)

2
Nie polecam używania .description, ponieważ nie ma gwarancji, że pozostanie bez zmian. Zobacz moją odpowiedź tutaj: 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)

1
Używanie opisu nie jest bezpieczne, ponieważ nie gwarantuje się, że w przyszłości da takie same wyniki.
Sahil Kapoor

-4

Opublikowane tutaj rozwiązanie @kulss, choć brakuje mu elegancji, ale ma cnotę prostoty, nie działa już w iOS 13, ponieważ descriptionbędzie działać inaczej dla NSData. Nadal możesz używać debugDescription.

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


-9
NSString *tokenstring = [[NSString alloc] initWithData:token encoding:NSUTF8StringEncoding];

Działa to, gdy dane są ciągiem, jednak deviceToken nie jest ciągiem.
Simon Epskamp,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.