Can the conversion of a String to Data with UTF-8 encoding ever fail? - swift

In order to convert a String instance to a Data instance in Swift you can use data(using:allowLossyConversion:), which returns an optional Data instance.
Can the return value of this function ever be nil if the encoding is UTF-8 (String.Encoding.utf8)?
If the return value cannot be nil it would be safe to always force-unwrap such a conversion.

UTF-8 can represent all valid Unicode code points, therefore a conversion
of a Swift string to UTF-8 data cannot fail.
The forced unwrap in
let string = "some string .."
let data = string.data(using: .utf8)!
is safe.
The same would be true for .utf16 or .utf32, but not for
encodings which represent only a restricted character set,
such as .ascii or .isoLatin1.
You can alternatively use the .utf8 view of a string to create UTF-8 data,
avoiding the forced unwrap:
let string = "some string .."
let data = Data(string.utf8)

Related

How do I deal with the String.Encoding initializer never failing?

if let encoding = String.Encoding(rawValue: 999) {
// ...
}
Produces a compiler error saying "Initializer for conditional binding must have Optional type, not 'String.Encoding'" because despite the docs saying the String.Encoding initializer is failable, it is not and will happily create non-existent encodings.
How do I check if the encoding returned by initializer is an actual encoding?
The two ideas I have are
Check the String.Encoding description is not empty. This assumes that supported encodings must have a description
Encode something - e.g. "abc".data(using: encoding) == nil - which assumes that the string "abc" can be encoded by all supported encodings
The documentation is misleading, String.Encoding has a non-failable
public init(rawValue: UInt)
initializer. A list of all valid string encodings is String.availableStringEncodings, so you can use that to check the validity:
let encoding = String.Encoding(rawValue: 999)
print(String.availableStringEncodings.contains(encoding)) // false
You can use CoreFoundation's CFStringIsEncodingAvailable() function to check if the encoding is valid, and also if it can be used on that particular device.
However, as MartiR pointed out, CFStringIsEncodingAvailable needs a CFStringEncoding to work with, so the String.Encoding needs to be converted first to a CF one.
let encoding = String.Encoding(rawValue: 999)
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(encoding.rawValue)
if CFStringIsEncodingAvailable(cfEncoding) {
// ...
}
, or, as MartinR nicely suggested, the result of CFStringConvertNSStringEncodingToEncoding can also be used:
if CFStringConvertNSStringEncodingToEncoding(encoding.rawValue) != kCFStringEncodingInvalidId {
// ...
}

How to get nil from string.data(using: .utf8)

Is there any string that fails to construct "".data(using: .utf8) and returns nil?
According to this discussion, string.data(using: .utf8) is the same as Data(string.utf8), which can't fail (for now, anyway. This is an implementation detail.).
Therefore, there is no string that you can use, to make data(from: .utf8) return nil.
data(from:) is declared to return an optional because you can pass in other encodings, which may not support the characters in the string. The method can't suddenly change its return type to non-optional just because you passed in .utf8, after all!

Is there a mapping in the standard library between Swift's String.Encoding and the IANA character set names?

Does a mapping exist in the standard library between the String.Encoding enumeration and the standard IANA character set names, e.g., "UTF-8" for String.Encoding.utf8? I was not able to find one.
I'm aware that Foundation's CFStringEncoding can be mapped to IANA character set names, but I could not find a way to go from String.Encoding to CFStringEncoding. CFStringEncoding is just a type alias for UInt32, and the String.Encoding enumeration is backed by a UInt, but unless I've made some simple error, they do not seem to correspond.
The raw value of a String.Encoding is an NSStringEncoding and that can be converted to a CFStringEncoding with CFStringConvertNSStringEncodingToEncoding. The IANA charset name is then determined with CFStringConvertEncodingToIANACharSetName. This function returns an optional CFString which can be toll-free bridged to an optional Swift String.
Example:
let enc = String.Encoding.isoLatin2
let cfEnc = CFStringConvertNSStringEncodingToEncoding(enc.rawValue)
if let ianaName = CFStringConvertEncodingToIANACharSetName(cfEnc) as String? {
print(ianaName) // iso-8859-2
}

Swift: '!=' cannot be applied to operands of type 'String' and 'Any'

I am trying to check if the string is empty of not, if it is not empty, i want to display the at the textField (outUrl.text)
let storage = UserDefaults.standard
var mainsite = storage.object(forKey:"sitestring") ?? ""
if (mainsite != "") {
outUrl.text = mainsite
}
But I have error:
Error: != cannot be applied to operands of type string and any
How do i check if the string is not empty?
The recommended solution is to use the appropriate API (string(forKey:) which returns String rather than Any and optional bindings.
The benefit is you get rid of any type cast and the nil coalescing operator
if let mainsite = UserDefaults.standard.string(forKey:"sitestring"), !mainsite.isEmpty {
outUrl.text = mainsite
}
The result returned by the Objective-C API -[UserDefaults obejectForKey:] is of type id, in order to accomodate various types of objects (NSString, NSNumber, etc, etc.). When accessed form Swift code, this becomes Any? (any type, perhaps nil).
If you know the stored value is indeed a string, you need to attempt cast it before comparing: != between values of types String and Any? isn't defined by default! What meaning would that have?):
var mainsite = storage.object(forKey:"sitestring") as? String ?? ""
This way, you either get a String value (if the cast succeeds) or the empty string (if it fails, because either there is no object for key "sitestring" or it isn't convertible to a String value).
The result from UserDefaults.standard is of type Any?, so before assigning the value to mainsite variable, typecast it to String. Here is an example,
let storage = UserDefaults.standard
var mainsite = storage.object(forKey:"sitestring") as? String

How to create a single character String

I've reproduced this problem in a Swift playground but haven't solved it yet...
I'd like to print one of a range of characters in a UILabel. If I explicitly declare the character, it works:
// This works.
let value: String = "\u{f096}"
label.text = value // Displays the referenced character.
However, I want to construct the String. The code below appears to produce the same result as the line above, except that it doesn't. It just produces the String \u{f096} and not the character it references.
// This doesn't work
let n: Int = 0x95 + 1
print(String(n, radix: 16)) // Prints "96".
let value: String = "\\u{f0\(String(n, radix: 16))}"
label.text = value // Displays the String "\u{f096}".
I'm probably missing something simple. Any ideas?
How about stop using string conversion voodoo and use standard library type UnicodeScalar?
You can also create Unicode scalar values directly from their numeric representation.
let airplane = UnicodeScalar(9992)
print(airplane)
// Prints "✈︎"
UnicodeScalar.init there is actually returning optional value, so you must unwrap it.
If you need String just convert it via Character type to String.
let airplaneString: String = String(Character(airplane)) // Assuming that airplane here is unwrapped