Swiftの文字列型 | Unicodeと文字列のUnicode表現

Swiftの文字列型 | Unicodeと文字列のUnicode表現

ユニコード(Unicode)です。ユニコードは文字コードの規格の1つで、使うOSなどに依存しない文字を提供出来るのが特長です。Swiftの文字列型で使用されているユニコード方式は、UTF-8、UTF-16、それとユニコードスカラー(UTF-32)です。このページで詳しく見ていきますが、実はCharacter型はユニコードスカラーが1つまたは複数集まった表現です。

ここではユニコードとは何か?から始めて、ユニコードスカラーと文字列リテラル中の特殊文字について簡単に説明し、拡張書記素クラスタ(extended grapheme cluster)を紹介します。また、簡単な文字列を使って、異なるエンコード方式で文字列がどのように表現されるか?ということを見ていきます。

Unicode(ユニコード)

Unicodeって何?

Wikipediaを見ると、

Unicode(ユニコード)とは、符号化文字集合や文字符号化方式などを定めた、文字コードの業界規格である。文字集合(文字セット)が単一の大規模文字セットであること(「Uni」という名はそれに由来する)などが特徴である。

抜粋: Unicode, Wikipedia

と書いてあるように、Unicodeというのは文字コードの業界規格です。使う機種(例えばWindowsとかMacとか)に依存しない文字を提供することが出来るので便利です。文書で言う所のPDFのような共通規格のようなものでしょうか?

ユニコードを使うことで、ほぼ全ての言語の全ての文字を表現出来ます。また、外部媒体(テキストファイルとかwebページとか)への書き込みや、それら外部媒体からの読み出しが出来るようです。

SwiftのStringCharacterはユニコードに準拠しています。これから見ていきますが、ユニコードに準拠していることによって、

  • Unicode scalar(ユニコードスカラー)と呼ばれる表現等を使って、ユニコードで定義されている文字にアクセス
  • 異なる文字符号化スキーム(例えばUTF8とUTF16)を使って文字を表現

出来ます。ユニコード的な表現から私達が通常使う文字(列)を取り出したり、逆に私達が使う文字列をユニコード表現に落としたりすることが出来ます。

ユニコードスカラー | Unicode Scalars

Swiftの文字列型は、ユニコードスカラー(Unicode Scalar)と呼ばれる値から構成されているようです。ユニコードスカラーは21ビットの数字を使って文字や修飾語句を表現しています。

例)公式マニュアルに載っているユニコードスカラー

U+0061 LATIN SMALL LETTER "a"
U+1F425 FRONT-FACING BABY CHICK "🐥"
Wikipediaの別ページにユニコード一覧のテーブルも用意されています。

参考: Unicode一覧 0000-0FFF

一覧で確認してみると、確かに"a"はユニコードの0061に対応していることが分かります。通常ユニコードスカラーには名前も同時に割り当てられていて、上記の例だと「LATIN SMALL LETTER」などが名前になります。また、使われていないスカラー値もあって、それらは将来文字が増えた時のために取ってあるみたいです。

公式マニュアルに注意書きがありますが、ここで「ユニコードスカラー」と呼んでいるものは、拡張領域に割り当てられたスカラー値の範囲(U+D800からU+DFFF)を含まないようです。

“NOTE

A Unicode scalar is any Unicode code point in the range U+0000 to U+D7FF inclusive or U+E000 to U+10FFFF inclusive. Unicode scalars do not include the Unicode surrogate pair code points, which are the code points in the range U+D800 to U+DFFF inclusive.”

抜粋:: Apple Inc. “The Swift Programming Language (Swift 3)”。 iBooks https://itun.es/jp/jEUH0.l

文字列リテラル中の特殊文字 | Special Characters in String Literals

他のプログラミング言語と同様に、Swiftでも文字列リテラル内に特殊文字を挿入することが出来ます。特殊文字はバックスラッシュ"\"から始まる、特定の組み合わせの文字のことを指します。

// 特殊文字
\0 // null character
\\ // バックスラッシュ
\t // タブ
\n // 改行(line feed)
\r // 行頭復帰(carriage return) <- 改行
\" // ダブルクォーテーション
\' // シングルクォーテーション

// ユニコードスカラー
\u{n} // e.g. \u{0061} --> "a"

Swiftで使える特殊文字は上記のようなものがあります。ユニコードスカラーのカッコの中に入る数字nは、8桁までの数字(16進数)が入るようです。

ただし、8桁未満で完結している数字に関しては上の桁に0を付けても付けなくても関係ないようです。

// 0のありなしは関係ない
"\u{72}" // r
"\u{0072}" // r
"\u{00000072}" // r

拡張書記素クラスタ | Extended Grapheme Clusters

Character型インスタンスは拡張書記素クラスタ

StringCharactersのページで、

一方、文字型であるCharacterは、1文字単位で文字を管理する型です。「1文字単位」という言葉が具体的に何を指すのかが肝なのですが、…

と書きました。「1文字単位」と表現しましたが、この1文字に該当するのがここで紹介するExtended Grapheme Clustersです。和訳を当てると、拡張書記素クラスタでしょうか?

つまり、

Character型のインスタンスは1つの拡張書記素クラスタで表現されている、

ということになります。

拡張書記素クラスタ = (複数の)ユニコードスカラー

では拡張書記素クラスタとは何か?というと、

拡張書記素クラスタ = (1つまたは複数の)ユニコードスカラー

です。端的に言えばユニコードスカラーです。このユニコードスカラー(の組み合わせ)によって、結果として人間の読める文字(絵文字も含む)を出力するような構成物を「拡張書記素クラスタ」と呼んでいるようです。

拡張書記素クラスタの具体例

// 平仮名の「が」
let ga: Character = "\u{304c}"
let ka: Character = "\u{304b}"
let dakuten: Character = "\u{309b}"
let gaCombined: Character = "\u{304b}\u{3099}"

if ga == gaCombined {
    print("同じ文字")
}

ユニコード一覧表を見ると平仮名の「か」は「U+304C」、「が」(濁点付き)は「U+304B」です。一方で、濁点単体(U+309B)でもユニコードとして登録されていますので、これを平仮名の「か」と組み合わせると

// 濁点と平仮名を組み合わせて「が」を作る
let gaCombined: Character = "\u{304b}\u{3099}"

濁点付きの「が」を作ることが出来ます。拡張書記素クラスタは複数のユニコードスカラーの組み合わせから成るというのは、この例を見ると良く分かると思います。

また、結果として出来たCharacter型のインスタンスgagaCombinedは、値としては同じものになります。

if ga == gaCombined {
    print("同じ文字")
}
//"同じ文字"と出力

公式マニュアルの例も非常に分かりやすいですので、色々試してみると面白いと思います。

文字列のユニコード表現 | Unicode Representation of Strings

コードユニット | Code Units

ユニコードで作られた文字列がテキストファイル等に書き出された時、(文字列を構成している)ユニコードスカラーはコードユニット(code units)で符号化されます。これをエンコードと呼びますが、Swiftで採用しているエンコード方式は3つで、UTF-8、UTF-16、UTF-32です。数字は文字列を何ビットのコードユニットでエンコードするかを表しており、例えばUTF-16なら16ビット、という具合です。

この後見ていきますが、コードユニットは整数値です。ユニコードスカラー(UTF-32)の場合、U+XXXXの「XXXX」で表されている数字(16進数)を10進数表示したものがコードユニットになります。
エンコード方式 コードユニットのbit数 プロパティ
UTF-8 8 bit utf8
UTF-16 16 bit utf16
UTF-32 32 bit(実際に使っているのは21 bit) unicodeScalars

表にまとめると上記のようになります。プロパティと書いたのは、Stringが持つプロパティのことです。後で具体例を使って見てみますが、例えばutf8というプロパティを使うと、Stringで作った文字列に対して、8ビットで符号化した各文字のコードユニット(整数値)にアクセス出来ます。

プロパティ(properties)は、型の内部で定義されている変数や定数(のようなもの)です

Stringはコードユニットの配列としても取り出せる

Stringの文字列はCharacter配列で格納されています。Characterは拡張書記素クラスタと等価と言えるので、「拡張書記素クラスタの配列」と呼んでも良いと思います。

この後具体例を使って見ていきますが、エンコードされたコードユニットも配列として保存されています。したがって、文字列を1文字ずつCharacterで分解するのではなく、特定のエンコード方式(例えばUTF-8)に対応したコードユニットで分解することも可能です。

公式マニュアルではlet dogString = "Dog‼🐶"を使っていましたが、ここでは

let catString = "Cat‼😺"

を分解してみます。

ユニコード表現 | Unicode Representation

UTF-8、UTF-16、ユニコードスカラー(UTF-32)によるエンコード結果をまとめると以下のようになります。

Character C a t 😺
ユニコードスカラー U+0043 U+0061 U+0074 U+203C U+1F63A
UTF-8コードユニット 67 97 116 226 128 188 240 159 152 186
UTF-16コードユニット 67 97 116 8252 55357 56890
ユニコードスカラーコードユニット 67 97 116 8252 128570

表を見ると分かりますが、よく使う英数字などには小さい整数値が割り当てられているので、どのエンコード方式を取っても結果は同じです。一方で、特殊な文字(上記例だとダブルエクスクラメーションマーク‼とネコの絵文字😺)には大きい整数値が割り当てられているので、UTF-8やUTF-16ではマルチバイトでコードユニットを割り当てています。

UTF-8表現

UTF-8のコードユニットを取り出したい場合は、utf8プロパティを使います。

// UTF-8
print("UTF-8: ", terminator:"")
for codeUnit in catString.utf8 {
    print("\(codeUnit) ", terminator:"")
}
print("")
//UTF-8: 67 97 116 226 128 188 240 159 152 186

コードユニットは8ビット単位なので、文字の種類によって2バイトや3バイトを割り当てて文字を表現しています。例えば、ネコの絵文字マーク「😺」には、4バイトのUTF-8のコードユニット(240 159 152 186)が割り当てられています。

変換規則には決まりがあるので、UTF-8のコードユニットを一度2進数に落として、それをユニコードスカラーに変換することが可能です。また、その逆変換(ユニコードスカラーからUTF-8)も当然可能です。

参考: UTF-8, Wikipedia

UTF-16表現

UTF-16のコードユニットを取り出したい場合はutf16プロパティです。

// UTF-16
print("UTF-16: ", terminator:"")
for codeUnit in catString.utf16 {
    print("\(codeUnit) ", terminator:"")
}
print("")
//UTF-16: 67 97 116 8252 55357 56890

UTF-8との違いはダブルエクスクラメーションマークです。16ビット単体で表現出来るようになったので、8252(16進数で203C)というコードユニットが割り当てられているのが分かります。

ユニコードスカラー(UTF-32)表現

ユニコードスカラーで取り出す場合unicodeScalarsプロパティを使います。

// ユニコードスカラー
print("Unicode Scalar: ", terminator:"")
for scalar in catString.unicodeScalars {
    let codeUnit = scalar.value
    print("\(codeUnit) ", terminator:"")
}
print("")
//Unicode Scalar: 67 97 116 8252 128570

ただし、unicodeScalarsの要素からコードユニットを取り出すには、さらにvalueというプロパティにアクセスしないといけません。

まとめ

  • Swiftの文字(列)型はユニコード(Unicode)に準拠している
  • 文字型の最小単位はユニコードスカラー(Unicode Scalar)
  • 文字列リテラル中の特殊文字にはバックスラッシュ。例)改行\n
  • 文字列リテラル中のユニコードスカラーは\u{n}nは整数)で表現
  • Character型を構成しているのが拡張書記素クラスタ(extended grapheme cluster)
  • 拡張書記素クラスタは1つまたは複数のユニコードスカラーから成る
  • 文字列をユニコード表現する時に使う整数値をコードユニット(code unit)と呼ぶ

Swiftの文字列型 | Stringに対する演算(アクセス、変更、比較)