search
LoginSignup
12

More than 1 year has passed since last update.

posted at

updated at

[Swift] string.data(using: .utf8)ってnilになるの?

結論

絶対になりません。
※ただし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)のことです。

ちなみにDataFoundationで定義されている型なので、実際のコードには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。当然NSStringSwift.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にできないか?

できます。
UTF8ViewCollectionプロトコルに準拠しておりElementUInt8なので、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を引用してみます:

NSStringAPI.swift
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にならないということも起き得ます。


  1. https://developer.apple.com/documentation/swift/stringprotocol/3126754-data 

  2. 実際にはNSStringは抽象クラスとして定義され、NSConcreteStringなどのサブクラスが文字列操作を担当するという、いわゆる「クラスクラスタ」を形成しています。 

  3. 2020年10月現在: https://w3techs.com/technologies/overview/character_encoding 

  4. https://github.com/apple/swift/blob/a353176e1eb570a56809cf4202f5f30aa8905840/stdlib/public/Darwin/Foundation/NSStringAPI.swift#L791-L811 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
12