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.