Swift2での文字列型

LINEで送る
Pocket

Swift2では新機能としてプロトコル拡張があります。
よく使うString型もこのプロトコル拡張を利用して、根本的な変更がされています。
なかなか興味深い変更です。

Swift provides a performant, Unicode-compliant string implementation as part of its Standard Library. In Swift 2, the String type no longer conforms to the CollectionType protocol, where String was previously a collection of Character values, similar to an array. Now, String provides a characters property that exposes a character collection view.Swift blog

Swift2での文字列型

Swfitは標準ライブラリで、高性能でUnicode対応の文字列実装を提供しています。今までのString型は配列に似ていてCharacter値の集合(collection)でしたが、Swift2のString型はCollectionTypeには準拠していません。Stringは文字集合のように見せるcharactersを持つようになりました。

なぜこのような変更があったのでしょうか? 文字列を文字の集合としてモデル化するのが自然に思えますが、String型はArraySetDictionaryのような集合(collection)の型とは違った振る舞いをします。今までもそうだったのですが、Swift2にプロトコル拡張を追加して、この違いに対して根本的な変更をすることにしました。

文字列は部分の総和ではない

集合に要素を加えると、集合にはその要素が含まれることになります。つまり、配列に値を追加すると配列はその値を持つようになります。dictionaryやsetでも同じです。しかし文字列に結合文字を追加すると、文字列自身が変化します。

cafeという文字列を考えてみてください。それにはcafeの4文字があります。

var letters: [Character] = ["c", "a", "f", "e"]
var string: String = String(letters)

print(letters.count) // 4
print(string) // cafe
print(string.characters.count) // 4

結合文字のアクセント符号U+0301 ´を追加したら、文字列は4文字のままですが最後の文字はéになります。

let acuteAccent: Character = "\u{0301}" // ´ COMBINING ACUTE ACCENT' (U+0301)

string.append(acuteAccent)
print(string.characters.count) // 4
print(string.characters.last!) // é

文字列のcharactersプロパティには元の小文字eも追加した結合文字のアクセント符号´もありません。代わりに小文字の「e」にアクセント符号のついたéが含まれています。

string.characters.contains("e") // false
string.characters.contains("´") // false
string.characters.contains("é") // true

文字列を他の集合と同じように扱っていたら、この事実にはsetにUIColor.redColor()UIColor.greenColor()とを追加したらそのsetにUIColor.yellowColor()が含まれているくらいの驚きです。

文字の内容によって判定する

文字列と集合のもう一つの違いは、等価であることを判定するやり方です。

  • 2つの配列は、同じ要素数を持ち対応するインデックスにある要素が等しければ同等といえます
  • 2つのsetは、同じ要素数を持ち1つのsetに含まれる各要素がもう1つのsetにも含まれていれば同等といえます
  • 2つのdictionaryは、同じペアのキーと値を持っていれば同等といえます

しかしStringは「規準的な等価(canonically equivalent)」に基づいて同等と判定されます。文字は言語上の意味と表記が同じであれば基準的に等価です。それらを表現しているUnicodeスカラの組み合わせ違っていたとしてもです。

韓国語の表記は個々の母音と子音をあらわす24文字や「字母」から構成されることを考えてみましょう。これらの文字を書くときは音節ごとに文字を組み立てます。例えば、「가」([ga])という文字は「ᄀ」([g])という文字と「ᅡ」([a])という文字からできています。Swiftでは文字列は分解されているか合成されているかに関係なく等価と判定します。

let decomposed = "\u{1100}\u{1161}" // ᄀ + ᅡ
let precomposed = "\u{AC00}" // 가

decomposed == precomposed // true

繰り返しになりますが、この振る舞いはSwiftの集合型とは大きく異なります。🐟🍚という値からなる配列は🍣と同じであると言っているようなものです。

さまざまなビュー

文字列は集合ではありませんが、CollectionTypeに準拠する「ビュー」を提供しています。

先に例であげた「café」という単語は分解された文字[ c, a, f, e ]と[ ´ ]からできていますが、さまざまな文字列ビューを持っています。

  • charactersプロパティは「拡張書記素クラスタ」にテキストを分解します。それはユーザが認知する文字(この場合c、a、f、é)に近いものです。文字列は全体の文字列内の各位置(コードポイントと呼ばれます)を反復処理して文字の境界を見つけます。このプロパティにアクセスすると文字数に比例した時間O(n)がかかります。可読テキストを処理するには、localizedStandardCompare(_:)メソッドやlocalizedLowercaseStringメソッドのようなハイレベルでロケール依存のUnicodeアルゴリズムが一文字ごとの処理よりも優先されるべきです。
  • unicodeScalarsプロパティは文字列に格納されている基礎となるスカラー値を出力します。元の文字列が分解されたe´でなく、結合文字のéであった場合、Unicodeスカラー値ビューには分解された文字が反映されます。このAPIは文字データのローレベルな操作を実行するときに使用します。
  • utf8プロパティとutf16プロパティはそれぞれUTF-8とUTF-8を表現するコードポイントを出力します。これらの値は特定のエンコーディング間の変換時に実際にファイルに書き込まれるバイトに対応します。UTF-8文字コードは多くのPOSIX標準の文字列処理APIに使用されており、UTF-16文字コードは文字列の長さとオフセットを表現するためにCocoaとCocoa Touchで使われています。

Swiftにおける文字列と文字の詳細についてはSwift言語仕様Swift標準ライブラリリファレンスを参照してください。

LINEで送る
Pocket

コメントを残す