結論
絶対になりません。
※ただしSwift 5以降に限る。
説明
data(using:)
ってどんなメソッド?
シグネチャ
StringProtocol
で規定されているdata(using:allowLossyConversion:)
というメソッドに由来します1。実際には、
func data(using encoding: String.Encoding, allowLossyConversion: Bool = false) -> Data?
というようにallowLossyConversion
にデフォルト引数が設定されているため、そちらは省略できます。即ち、string.data(using: .utf8)
はstring.data(using: .utf8, allowLossyConversion: false)
のことです。
ちなみにData
はFoundation
で定義されている型なので、実際のコードにはimport Foundation
が必要です。
返り値
上記で見た通り返り値の型はData?
とオプショナル型になっています。メソッドは、指定したエンコーディングでエンコードされたデータを返すことになりますが、変換できない文字が含まれているとnil
となります。
たとえば、最近流行の(?)絵文字"💩"をUS-ASCIIにしようとするとnil
が返ってきます:
import Foundation
let string: String = "💩"
let data: Data? = string.data(using: .ascii)
print(data.debugDescription) // -> nil
String
は内部でどのように文字列データを保持しているか?
実はここが大事なところです。
Swift 4までは基本的にUTF-16でデータが保持されていました。これは、Swiftが当初からObjective-CのFoundationをそのまま活用しようとしていたことから来ています。歴史の長いNSString
は原則UTF-16で文字列データを保持していました2。当然NSString
とSwift.String
を"toll-free"で相互変換することを考えるとUTF-16を内部データ型に選択するのは当然だったでしょう。
しかし、世の中どの程度UTF-16が使われているでしょうか?ほとんどUTF-8が使われているのではないでしょうか。W3Techs - World Wide Web Technology Surveysによれば、今やWebサイトのうち95%以上がUTF-8だそうです3。この統計はWebサイトだけですが、それ以外の分野でも似たようなものではないでしょうか。
そうなると、内部データをUTF-16で保持していても、実際にデータを読み込んだりデータとして出力したりする場合、UTF-8⇄UTF-16の変換コストを伴うことがほとんどということになります。
であれば、最初から内部データはUTF-8として保持しておくほうがいい、となるのは自然なことでしょう。
というわけで、Swift 5からはString
の内部データがUTF-8になったのでした。詳細は https://swift.org/blog/utf8-string/ に書かれています。内部のUTF-8についてはvar utf8: Self.UTF8View { get }
を通してアクセス(read-only)できます。
UTF-8のデータをそのままData
にできないか?
できます。
UTF8View
はCollection
プロトコルに準拠しておりElement
はUInt8
なので、let data = Data(string.utf8)
とするだけで、UTF-8でエンコードされた文字列のデータを得られます。
ここで注目すべきはData(string.utf8)
は必ず成功するということです(∵failable initializerではない)。
つまり、次の例で言うと
let data_1: Data? = string.data(using: .utf8)
let data_2: Data = Data(string.utf8)
data_1
はオプショナル型ですが、data_2
は必ずData
型です。
では、常にData(string.utf8)
を使えば良いのではないか、と思いませんか。
正解です。
data(using:)
の実装を見てみよう
Swiftはオープンソースなので、どのように実装しているか見放題です。"NSStringAPI.swift"に実装を見ることができます。該当箇所4を引用してみます:
public func data(
using encoding: String.Encoding,
allowLossyConversion: Bool = false
) -> Data? {
switch encoding {
case .utf8:
return Data(self.utf8)
default:
return _ns.data(
using: encoding.rawValue,
allowLossyConversion: allowLossyConversion)
}
}
なんと!
encoding
が.utf8
のときはData(self.utf8)
をそのまま返しています(ちなみに、Gitで変更履歴を辿ってもらえるとわかるかと思いますが、Swift 4以前はどんなencoding
であろうとNSString
にデータへの変換をお願いする形になっていました)。
これで答えが出ましたね。
最後にもう一度結論
Q. string.data(using: .utf8)
ってnil
になるの?
A. 絶対になりません。 ※ただしSwift 5以降に限る。
おまけ(注意点)
var utf8: Self.UTF8View { get }
は内部にUTF-8として不正なバイト列を持ってしまっていても、そのままそのバイト列を返してきます。従って、Swift 4ではnil
になっていたstring.data(using: .utf8)
がSwift 5ではnil
にならないということも起き得ます。
-
https://developer.apple.com/documentation/swift/stringprotocol/3126754-data ↩
-
実際には
NSString
は抽象クラスとして定義され、NSConcreteString
などのサブクラスが文字列操作を担当するという、いわゆる「クラスクラスタ」を形成しています。 ↩ -
2020年10月現在: https://w3techs.com/technologies/overview/character_encoding ↩
-
https://github.com/apple/swift/blob/a353176e1eb570a56809cf4202f5f30aa8905840/stdlib/public/Darwin/Foundation/NSStringAPI.swift#L791-L811 ↩