Как я могу преобразовать мой токен устройства (NSData) в строку NSString?

157

Я внедряю push-уведомления. Я хотел бы сохранить свой APNS-токен в виде строки.

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

Первая строка кода печатает ноль. вторая печатает токен. Как я могу получить свой newDeviceToken как NSString?

Шихан Алам
источник
Что выводит второй NSLog, тот, который печатает newDeviceToken?
Роб Майофф
Дубликат: stackoverflow.com/questions/1305225/…
NiñoScript
НЕ используйте описание
Толстяк

Ответы:

39

использовать это :

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

NSLog(@"The generated device token string is : %@",deviceTokenString);
kulss
источник
134
Кажется плохой идеей использовать описание: ничто не гарантирует, что более поздняя версия iOS не изменит реализацию и результат этого вызова.
Madewulf
16
Действительно, это действительно плохая идея.
Дэвид Снабел-Каунт
21
@madewulf очень мило с вашей стороны указать, как это ужасная идея - использовать описание ... было бы еще лучше, если бы вы предложили альтернативу
abbood
6
Решение здесь с [deviceToken bytes] отвечает всем требованиям.
madewulf
37
Оказывается, начиная с Swift 3 / iOS 10, .description на токене устройства возвращает «32 байта». Так что да, не используйте это.
Виктор Люфт
231

Если кто-то ищет способ сделать это в 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)")
}

Изменить: для Swift 3

Swift 3 представляет Dataтип с семантикой значения. Чтобы преобразовать deviceTokenстроку в строку, вы можете сделать следующее:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
    print(token)
}
Sascha
источник
118
Почему это должно быть так сложно, что не так с ОС, которая дает нам строку, поскольку это то, что нужно всем? Спасибо за это решение.
Piwaf
3
@ Саша Я надеюсь, что вы одобряете мою правку на ваш очень полезный ответ :)
jrturton
16
Я рефакторинг: let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() qiita.com/mono0926/items/3cf0dca3029f32f54a09
моно
2
Я не рекомендую использовать .description, поскольку это не гарантирует стабильности. Проверьте мой ответ здесь: stackoverflow.com/questions/9372815/…
Свифт Тейлор
7
Можете ли вы объяснить, что делает "%02.2hhx?
Мед
155

Кто-то помог мне с этим. Я просто передаю

- (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
источник
5
Это лучшее решение, поскольку байты encondig в шестнадцатеричном формате подразумевают, что вы можете считать их;)
loretoparisi
4
В XCode 5 мне пришлось привести deviceToken для его компиляции: const unsigned * tokenBytes = (const unsigned *) [deviceToken bytes];
Ponytech
3
Скоро токены будут длиннее 32 байтов, так что это должен быть цикл над каждым байтом вместо восьми жестко запрограммированных целых чисел.
Том Даллинг
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
Important: APNs device tokens are of variable length. Do not hard-code their size.Apple говорит.
erkanyildiz
141

Вы могли бы использовать это

- (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, который будет обрабатывать предстоящее увеличение размера токена.
Том Даллинг
Договорились, что это, вероятно, самый безопасный способ, поскольку он не предполагает какого-либо конкретного размера / длины токена.
Райан Х.
Работает в iOS 10.
Tjalsma
2
Я использовал, [token appendFormat:@"%02.2hhx", data[i]];поскольку Amazon SNS требует строчных букв.
Мануэль Шмитцбергер
43

Для тех, кто хочет в Swift 3 и самый простой способ

func extractTokenFromData(deviceToken:Data) -> String {
    let token = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
    return token.uppercased();
}
Ананд
источник
1
Я написал тот же код :) Это самая быстрая версия, и только это работает
Quver 10.10.16
1
@ Ананд, можешь ли ты объяснить, что происходит в этом кодеdeviceToken.reduce("", {$0 + String(format: "%02X", $1)})
Рамакришна,
1
Он использует функцию уменьшения функции swift, которая сериализует данные в шестнадцатеричную строку, а затем в строку. Чтобы узнать больше о функции сокращения, прочтите useyourloaf.com/blog/swift-guide-to-map-filter-reduce
Anand
15

Объяснение %02.2hhxв высоком голосе ответа :

  • %: Вводит xспецификатор конверсии.
  • 02: Минимальная ширина преобразованного значения равна 2. Если преобразованное значение имеет меньше байтов, чем ширина поля, оно должно быть дополнено 0слева.
  • .2: Предоставляет минимальное количество цифр, отображаемых для xспецификатора преобразования.
  • hh: Указывает, что xспецификатор преобразования применяется к аргументу со знаком char или unsigned char (аргумент будет повышен в соответствии с целочисленными предложениями, но его значение должно быть преобразовано в char со знаком или unsigned char перед печатью).
  • x: Аргумент без знака должен быть преобразован в шестнадцатеричный формат без знака в стиле "dddd"; буквы "abcdef" используются. Точность определяет минимальное количество отображаемых цифр; если конвертируемое значение может быть представлено меньшим количеством цифр, оно должно быть расширено начальными нулями. Точность по умолчанию равна 1. Результатом преобразования нуля с явной точностью нуля не должно быть символов.

Для получения дополнительной информации см. Спецификацию IEEE printf .


Исходя из приведенного выше объяснения, я думаю, что лучше изменить %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
jqgsninimo
источник
Спасибо за этот ответ. Это работает также с iOS 12? Или это зависит только от версии Swift?
Маркус
1
@Markus Это работает в iOS 12, зависит только от версии Swift.
jqgsninimo
14

Это мое решение, и оно хорошо работает в моем приложении:

    NSString* newToken = [[[NSString stringWithFormat:@"%@",deviceToken] 
stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""];
  • преобразовать NSDataв NSStringсstringWithFormat
  • обрезать "<>"
  • удалить пробелы
Зеб
источник
10
Это просто неявно вызывает -description, так что это не безопаснее, чем принятый ответ.
jszumski
Можете ли вы связать свой источник? Я не могу найти информацию об этом нигде. Спасибо.
Зеб
Нашел это! Я думаю, что это немного по-другому. Использовать атрибут description напрямую небезопасно, поскольку он может измениться в будущих версиях, но если вы используете его через метод NSString, у вас вряд ли возникнут проблемы.
Зеб
5
Нет, это действительно требует descriptiondeviceToken, как говорит jszumski.
Джонни
1
@Zeb Ненадежно полагаться на descriptionто, вызываете ли вы его напрямую или используете через другой метод, потому что формат возвращаемой строки может быть изменен в любое время. Правильное решение здесь: stackoverflow.com/a/16411517/108105
Том Даллинг
10

Я думаю, что преобразование deviceToken в шестнадцатеричную байтовую строку не имеет смысла. Зачем? Вы отправите его на свой сервер, где он будет преобразован обратно в байты для отправки в APNS. Итак, используйте метод NSData,base64EncodedStringWithOptions отправьте его на сервер, а затем используйте обратные данные, декодированные base64 :) Это намного проще :)

NSString *tokenString = [tokenData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
Олег Шанюк
источник
@ jeet.chanchawat, пожалуйста, не добавляйте код в ответы других пользователей. Мы не хотим помещать слова в рот, особенно при добавлении Swift к ответу Objective-C. Вместо этого добавьте свой собственный ответ.
JAL
2
Я просто не хотел заниматься плагиатом ответа @Oleg Shanyuk. Поскольку это просто перевод на другой язык, основанный на его ответе, он заслуживает будущих голосов. Если я добавлю еще один ответ, я получу положительные отзывы за ответ, который является исследованием кого-то другого. Надеюсь, это оправдывает редактирование.
jeet.chanchawat
10

В iOS 13 descriptionсломается, так что используйте это

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

Для ясности давайте разберем это и объясним каждую часть:

Метод map работает с каждым элементом последовательности. Поскольку Data - это последовательность байтов в Swift, переданное закрытие оценивается для каждого байта в deviceToken. Инициализатор String (format :) оценивает каждый байт данных (представленный анонимным параметром $ 0), используя спецификатор формата% 02x, чтобы получить заполненное нулями 2-значное шестнадцатеричное представление байта / 8-разрядного целого числа. После сбора каждого байтового представления, созданного методом map, join () объединяет каждый элемент в одну строку.

PS не использовать описание дает разные строки в 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 }"

Для получения дополнительной информации прочитайте это .

SuryaKantSharma
источник
10

В 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. Спасибо Вишну
Маниш
1
В настоящее время он не компилируется - lengthв цикле for его следует изменить на len. По-видимому, слишком маленькое изменение для меня, чтобы внести изменения .. Но в остальном работает отлично!
Андерс Фрис
ты спасатель жизни
Моез Акрам
3

Это немного более короткое решение:

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
источник
3

Функциональная версия Swift

Один лайнер:

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

Вот в форме многоразового и самодокументируемого расширения:

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

В качестве альтернативы используйте reduce("", combine: +)вместо того, joinWithSeparator("")чтобы ваши коллеги воспринимали вас как функционального мастера.


Изменить: я изменил String ($ 0, основание: 16) на String (формат: "% 02x", $ 0), потому что однозначные числа должны иметь нулевой отступ

(Я пока не знаю, как пометить вопрос как дубликат другого , поэтому я просто опубликовал свой ответ снова)

NiñoScript
источник
Работает на меня, спасибо.
Хася
3

2020

токен как текст ...

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

или если вы предпочитаете

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

(результат тот же)

Fattie
источник
2

Кидаю мой ответ в кучу. Избегайте использования разбора строк; Документами не гарантируется, что NSData.description всегда будет работать таким образом.

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

Я пытался проверить два разных метода с форматом "%02.2hhx"и"%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)

и результат в том, что самый быстрый - "%02x"в среднем 2,0 против 2,6 для уменьшенной версии:

deviceToken.reduce("", {$0 + String(format: "%02x", $1)})
Николас Манзини
источник
1

Использование updateAccumulationResult более эффективно, чем различные другие подходы, найденные здесь, так что вот самый быстрый способ упорядочить ваши Dataбайты:

func application(_ application: UIApplication,
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.reduce(into: "") { $0 += String(format: "%.2x", $1) }
    print(token)
}
Алекс Курило
источник
Алекс, не будет ли это% 02.2hhx
Толстяк
0

Для Свифта:

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

println( deviceTokenString )
Адарш Г.Дж.
источник
0

Как насчет однострочного решения?

Цель С

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

стриж

let token = data.description.componentsSeparatedByCharactersInSet(NSCharacterSet.alphanumericCharacterSet().invertedSet).joinWithSeparator("")
Николай Шубенков
источник
2
Это простое и лучшее решение. Спасибо
Эмми
0

Вот как вы это делаете в 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])];
Геня Гришин
источник
отличное решение На сегодняшний день его можно отнести к credentials.token.description.replacingOccurferences (из: "[<>]", с: "", опции: .regularExpression, range: nil)
Франк
-1

Swift:

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

стриж

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

Используйте отличную категорию!

// .h файл

@interface NSData (DeviceToken)

- (NSString *)stringDeviceToken;

@end    

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

@конец

// AppDelegate.m

#import "NSData+DeviceToken.h"

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

Работает отлично!

LLIAJLbHOu
источник
Не полагайтесь на использование «описания», его формат может измениться в будущем. Это только для демонстрации.
Майкл Петерсон
-3

Свифт 3:

Если кто-то ищет способ получить токен устройства в Swift 3. Используйте приведенный ниже измененный фрагмент.

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

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

    print(deviceTokenString)
Лакш Гандикота
источник
2
Я не рекомендую использовать .description, поскольку это не гарантирует, что он останется прежним. Смотрите мой ответ здесь: stackoverflow.com/questions/9372815/…
Свифт Тейлор
-4
var token: String = ""
for i in 0..<deviceToken.count {
    token += String(format: "%02.2hhx", deviceToken[i] as CVarArg)
}

print(token)
Абдул Ясин
источник
1
Использование описания небезопасно, так как оно не гарантирует того же результата в будущем.
Сахил Капур
-4

Размещенное здесь решение @kulss, хотя и не обладающее элегантностью, но обладающее простотой, больше не работает в iOS 13, поскольку descriptionдля NSData будет работать по-другому. Вы все еще можете использовать, debugDescriptionхотя.

NSString * deviceTokenString = [[[[deviceToken debugDescription]
                     stringByReplacingOccurrencesOfString: @"<" withString: @""] 
                    stringByReplacingOccurrencesOfString: @">" withString: @""] 
                   stringByReplacingOccurrencesOfString: @" " withString: @""];
johnyu
источник
-7

Попробуйте это, если данные не заканчиваются нулем.

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

Навид Ахмад
источник
Я попробовал это, это не работает. Я закомментировал это в моем фрагменте кода.
Шихан Алам
@SheehanAlam Этот парень прошел через это. Посмотрите, как он конвертируется в строку. stackoverflow.com/questions/4994302/…
Навид Ахмад
-9
NSString *tokenstring = [[NSString alloc] initWithData:token encoding:NSUTF8StringEncoding];
Ravikant
источник
Это работает, когда данные являются строкой, однако deviceToken не является строкой.
Саймон Эспкамп,