Як я можу конвертувати маркер свого пристрою (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);
}

Перший рядок коду друкує null. другий друкує маркер. Як я можу отримати свій новийDeviceToken як NSString?


Який вихід другого NSLog, того, що друкує newDeviceToken?
пограбувати майора


НЕ використовуйте опис
Fattie

Відповіді:


40

використовуй це :

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

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

134
Використовувати опис здається поганою ідеєю: ніщо не гарантує, що пізніша версія iOS не змінить реалізацію та результат цього виклику.
madewulf

16
Дійсно, це дійсно погана ідея.
David Snabel-Caunt

21
@madewulf дуже приємно з вас, щоб зазначити, як це така жахлива ідея використовувати опис .. було б навіть приємніше, якби ви запропонували альтернативу
abbood

6
Розв’язання, розміщене тут, у розділі [deviceToken bytes] підходить до рахунку.
madewulf

37
Виходить, як на Swift 3 / iOS 10,. Опис на маркер пристрою повертає "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)
}

118
Чому це має бути настільки складним, що не так у тому, що ОС дає нам рядок, оскільки саме це потрібно всім? Дякую за таке рішення.
Піваф

3
@Sascha Я сподіваюся, що ти схвалюєш мою редакцію вашої дуже корисної відповіді :)
jrturton

16
Я let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() відремонтував
моно

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

5
Це найкраще рішення, оскільки енконіг байт як шістнадцятковий означає, що ви можете його порахувати;)
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])]; }
Харро

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

1
@Anand можете, будь ласка, пояснити, що відбувається в цьому кодіdeviceToken.reduce("", {$0 + String(format: "%02X", $1)})
Рамакришна

1
Він використовує функцію зменшення швидкості, яка серіалізує Дані у шістнадцятковий рядок, а потім у String. Щоб дізнатися більше про функцію зменшення, прочитайте useyourloaf.com/blog/swift-guide-to-map-filter-reduce
Ананд,

15

Пояснення %02.2hhxв високому голосі відповіді :

  • %: Вводить xспецифікатор перетворення.
  • 02: Мінімальна ширина перетвореного значення - 2. Якщо перетворене значення має менше байтів, ніж ширина поля, воно повинно бути зафіксовано 0зліва.
  • .2: Дає мінімальну кількість цифр, що відображаються для xспецифікатора перетворення.
  • hh: Вказує, що xспецифікатор перетворення застосовується до підписаного аргументу char або неподписаного char (аргумент буде просунутий відповідно до цілих акцій, але його значення перед друком перетвориться на підписане char або неподписане 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

Дякую за цю відповідь. Чи працює це також з 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

Чи можете ви зв'язати своє джерело? Я не можу ніде знайти інформацію про неї. Дякую.
Зеб

Знайшов це! Я думаю, що це трохи інакше. Використовувати атрибут опису безпосередньо не є безпечним, оскільки він може змінитися в майбутніх версіях, але якщо ви будете використовувати його через метод NSString, у вас навряд чи виникнуть проблеми.
Зеб

5
Ні, це дійсно не вимагає descriptionпристрою. Так, як каже jszumski.
Джоні

1
@Zeb Не можна покладатися на descriptionте, чи будете ви викликати його безпосередньо, чи використовувати його іншим методом, тому що формат повернутого рядка можна було змінити в будь-який час. Правильне рішення тут: stackoverflow.com/a/16411517/108105
Том Dalling

10

Я думаю, що перетворювати deviceToken в шістнадцяткову байтову рядок не має сенсу. Чому? Ви надішлете його у свій бекенд, де він буде перетворений назад у байти, які будуть перенесені на APNS. Таким чином, використовувати NSData метод «s base64EncodedStringWithOptions, штовхати його на сервер, а потім використовувати зворотний base64decoded дані :) Це набагато простіше :)

NSString *tokenString = [tokenData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];

@ jeet.chanchawat, будь ласка, не додайте код до відповідей інших користувачів. Ми не хочемо вставляти слова в рот, особливо, додаючи Swift до відповіді Objective-C. Натомість додайте власну відповідь.
JAL

2
Я просто не хотів плагіатувати відповідь @ Олега Шанюка. Оскільки це лише переклад на іншій мові, побудований на його відповіді, тож він заслуговує на голосування в майбутньому. Якщо я додам ще одну відповідь, це дасть мені нагороди за відповідь, яка є дослідженням когось іншого. Сподіваюсь, це виправдає EDIT.
jeet.chanchawat

10

В iOS 13 descriptionбуде зламатися, тому використовуйте це

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

Для наочності давайте розбимо це і пояснимо кожну частину:

Метод карти працює на кожному елементі послідовності. Оскільки Дані - це послідовність байтів у Swift, пройдене закриття оцінюється для кожного байту в пристроїToken. Ініціалізатор String (формат :) оцінює кожен байт даних (представлений анонімним параметром $ 0), використовуючи специфікатор формату% 02x, для створення нульованого, двозначного дванадцятизначного подання шестидесяткового подання цілого числа байт / 8 біт. Після збору кожного байтового представлення, створеного методом map, joined () об'єднує кожен елемент в один рядок.

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

Для отримання додаткової інформації читайте це .


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

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), тому що для нумерації прокладки потрібен один цифр

(Я ще не знаю, як позначити питання як дублікат цього іншого , тому я просто опублікував свою відповідь ще раз)


Для мене працює, дякую.
Хася

3

2020 рік

маркер як текст ...

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

або якщо ви віддаєте перевагу

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

(результат той самий)


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

Використання updateAccumulatingResult є більш ефективним, ніж різні інші підходи, знайдені тут, тому ось найшвидший спосіб поширити свої Dataбайти:

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

Алекс, не було б% 02.2hhx
Fattie

0

Для Swift:

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

println( deviceTokenString )

0

А як щодо одного рядкового рішення?

Мета C

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

чудове рішення На сьогоднішній день це може бути застосовано до облікових даних.ken.description.replacingOccurrences (of: "[<>]", with: "", options: .regularExpression, range: nil)
Frank

-1

Швидкий:

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

@end

// AppDelegate.m

#import "NSData+DeviceToken.h"

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

Добре працює!


Не покладайтеся на використання "опису", його формат може змінитися в майбутньому. Це лише для відображення.
Майкл Петерсон

-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: @""];

-7

Спробуйте це, якщо дані не скасовуються.

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


Я спробував це, він не працює. Я це прокоментував у своєму фрагменті коду.
Шихан Алам

@SheehanAlam Цей хлопець пробив через. Погляньте, як це перетворюється на рядок. stackoverflow.com/questions/4994302/…
Naveed Ahmad

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.