Konwertuj NSData zakodowane w UTF-8 na NSString

567

Mam kodowanie UTF-8 NSDataz serwera Windows i chcę przekonwertować go NSStringna iPhone'a. Ponieważ dane zawierają znaki (takie jak symbol stopnia), które mają różne wartości na obu platformach, w jaki sposób przekonwertować dane na ciąg?

Ashwini Shahapurkar
źródło
16
UTF-8 jest wszędzie UTF-8. Gdy jest to UTF-8, nie ma różnych wartości dla różnych platform. Właśnie o to chodzi.
gnasher729

Odpowiedzi:

1155

Jeśli dane nie są zakończone zerem, należy użyć -initWithData:encoding:

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

Jeśli dane są zakończone zerem, powinieneś zamiast tego użyć, -stringWithUTF8String:aby uniknąć dodatkowych \0na końcu.

NSString* newStr = [NSString stringWithUTF8String:[theData bytes]];

(Zauważ, że jeśli wejście nie jest poprawnie zakodowane w UTF-8, otrzymasz nil.)


Szybki wariant:

let newStr = String(data: data, encoding: .utf8)
// note that `newStr` is a `String?`, not a `String`.

Jeśli dane są zakończone zerem, możesz przejść bezpieczną drogą, która polega na usunięciu znaku null lub niebezpieczną drogą podobną do powyższej wersji Objective-C.

// safe way, provided data is \0-terminated
let newStr1 = String(data: data.subdata(in: 0 ..< data.count - 1), encoding: .utf8)
// unsafe way, provided data is \0-terminated
let newStr2 = data.withUnsafeBytes(String.init(utf8String:))
kennytm
źródło
5
Uważaj!! jeśli używasz stringWithUTF8String, nie przekazuj mu argumentu NULL, w przeciwnym razie
wygeneruje
31
UWAŻAJ, ŻE: w przypadku użycia ciągu „stringWithUTF8String:” na łańcuchu, który nie jest zakończony zerem, wynik jest nieprzewidywalny!
Berik
2
Oba rozwiązania zwracają mi zero.
Husyn
1
Skąd wiesz, czy twoje NSData jest zakończone zerowo, czy nie? Patrz odpowiedź Tom Harrington w: stackoverflow.com/questions/27935054/... . Z mojego doświadczenia wynika, że ​​nigdy nie należy zakładać, że NSData ma albo zerowe zakończenie, albo nie: może różnić się w zależności od transmisji, nawet od znanego serwera.
Elise van Looij
1
@ElisevanLooij Dzięki za link. Twierdziłbym, że jeśli przesyłane dane mogłyby zostać losowo zakończone zerem lub nie, protokół jest źle zdefiniowany.
kennytm
28

Możesz wywołać tę metodę

+(id)stringWithUTF8String:(const char *)bytes.
Gouldsc
źródło
27
Tylko jeśli dane są zakończone zerem. Który może nie być (a tak naprawdę prawdopodobnie nie jest).
Ivan Vučica,
nie wiem, dlaczego, do licha, miałoby to załamać się na łańcuchach nie zakończonych zerem, widząc, skąd NSDatawie, ile ma bajtów ...
Claudiu
5
@ Claudiu, nie przekazujesz obiektu NSData, przekazujesz go (const char *) uzyskany z [bajtów danych], który jest tylko wskaźnikiem, bez informacji o rozmiarze. Dlatego wskazany blok danych musi być zakończony zerem. Sprawdź dokumentację, mówi tak wyraźnie.
jbat100
1
@ jbat100: Oczywiście. Nie byłem jasny. Miałem na myśli, biorąc pod uwagę, że możliwe jest przejście z opcji z zerowym zakończeniem NSDatana NSString(patrz odpowiedź Kenny'ego), jestem zaskoczony, że nie ma czegoś, +(id)stringWithUTF8Data:(NSData *)dataco po prostu działa.
Claudiu
stringWithUTF8Data, dlatego większość z nas tworzy kategorię NSString + Foo i tworzy metodę.
William Cerniuk
19

Pokornie przesyłam kategorię, aby było to mniej denerwujące:

@interface NSData (EasyUTF8)

// Safely decode the bytes into a UTF8 string
- (NSString *)asUTF8String;

@end

i

@implementation NSData (EasyUTF8)

- (NSString *)asUTF8String {
    return [[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding];    
}

@end

(Pamiętaj, że jeśli nie używasz ARC, potrzebujesz autorelease .)

Teraz zamiast przerażająco gadatliwego:

NSData *data = ...
[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

Możesz to zrobić:

NSData *data = ...
[data asUTF8String];
Claudiu
źródło
18

Wersja Swift od String do Data iz powrotem do String:

Xcode 10.1 • Swift 4.2.1

extension Data {
    var string: String? {
        return String(data: self, encoding: .utf8)
    }
}

extension StringProtocol {
    var data: Data {
        return Data(utf8)
    }
}

extension String {
    var base64Decoded: Data? {
        return Data(base64Encoded: self)
    }
}

Plac zabaw

let string = "Hello World"                                  // "Hello World"
let stringData = string.data                                // 11 bytes
let base64EncodedString = stringData.base64EncodedString()  // "SGVsbG8gV29ybGQ="
let stringFromData = stringData.string                      // "Hello World"

let base64String = "SGVsbG8gV29ybGQ="
if let data = base64String.base64Decoded {
    print(data)                                    //  11 bytes
    print(data.base64EncodedString())              // "SGVsbG8gV29ybGQ="
    print(data.string ?? "nil")                    // "Hello World"
}

let stringWithAccent = "Olá Mundo"                          // "Olá Mundo"
print(stringWithAccent.count)                               // "9"
let stringWithAccentData = stringWithAccent.data            // "10 bytes" note: an extra byte for the acute accent
let stringWithAccentFromData = stringWithAccentData.string  // "Olá Mundo\n"
Leo Dabus
źródło
16

Czasami metody z innych odpowiedzi nie działają. W moim przypadku generuję podpis za pomocą mojego prywatnego klucza RSA, a wynikiem jest NSData. Odkryłem, że to działa:

Cel C

NSData *signature;
NSString *signatureString = [signature base64EncodedStringWithOptions:0];

Szybki

let signatureString = signature.base64EncodedStringWithOptions(nil)
mikeho
źródło
jak zdobyć ten ciąg do nsdata?
Darshan Kunjadiya,
1
@DarshanKunjadiya: Cel C : [[NSData alloc] initWithBase64EncodedString:signatureString options:0]; Swift : NSData(base64EncodedString: str options: nil)
mikeho,
1

Podsumowując, oto pełna odpowiedź, która zadziałała dla mnie.

Mój problem polegał na tym, że kiedy używałem

[NSString stringWithUTF8String:(char *)data.bytes];

Ciąg, który otrzymałem, był nieprzewidywalny: około 70% zawierało oczekiwaną wartość, ale zbyt często to powodowało Null a nawet gorzej: garbowany na końcu łańcucha.

Po kopaniu przełączyłem się na

[[NSString alloc] initWithBytes:(char *)data.bytes length:data.length encoding:NSUTF8StringEncoding];

I za każdym razem uzyskałem oczekiwany wynik.

Gal
źródło
Ważne jest, aby zrozumieć <i> dlaczego </i> wyniki „śmieci”.
Edgar Aroutiounian
1

Swift z 5, można użyć String„s init(data:encoding:)inicjator w celu przekształcenia Datainstancji do Stringinstancji przy użyciu UTF-8. init(data:encoding:)ma następującą deklarację:

init?(data: Data, encoding: String.Encoding)

Zwraca Stringzainicjowany przez konwersję danych na znaki Unicode przy użyciu danego kodowania.

Poniższy kod placu zabaw pokazuje, jak go używać:

import Foundation

let json = """
{
"firstName" : "John",
"lastName" : "Doe"
}
"""

let data = json.data(using: String.Encoding.utf8)!

let optionalString = String(data: data, encoding: String.Encoding.utf8)
print(String(describing: optionalString))

/*
 prints:
 Optional("{\n\"firstName\" : \"John\",\n\"lastName\" : \"Doe\"\n}")
*/
Imanou Petit
źródło