Swiftでの文字列操作の基礎(その1)

2019年9月27日(金)
大津 真
連載9回目となる今回は、Swfitによる文字列操作の基本を学びます。あわせてSwift 5で導入された新機能にもついても紹介します。

はじめに

今回から数回に分けて、Swiftにおける文字列の基本操作について解説していきましょう。またSwift 5での変更点についても説明します。

Swift 5登場

Swiftの最新メジャーリリースであるSwift 5.0が、2019年3月25日にリリースされました。Swift 5はXcode 10.2から利用可能となっています。

主な新機能

  • ABIの安定化
  • String型の内部形式がUTF-8になり高速化
  • Result型の追加
  • 辞書(dictionary)とセット(Set)のパフォーマンス強化

新機能の詳細や変更点は「Swift 5 Release Notes for Xcode 10.2」で確認することができます。

Swift 5 Release Notes for Xcode 10.2

ABIの安定化

Swift 5におけるもっとも重要な変更点が、アプリケーションとシステムのバイナリレベルでのインターフェースであるABI(Application Binary Interface)の安定化です。これにより、今後のリリースではバイナリレベルでアプリケーションやライブラリの互換性が保証されるようになりました。また、OSに付属する標準のライブラリを利用できることから、アプリケーション自体のサイズも小さくなります。

文字列の内部形式がUTF-8に

Swift 4.2までは、文字列の内部形式として、ASCII文字のみを含む文字列はASCII形式が、それ以外はユニコードのUTF-16が使用されていました。それに対して、Swift 5では内部形式がUTF-8に刷新されました。昨今、クライアントサイド/サーバサイドを問わずプログラミング環境や開発ツールなどはUTF-8によるエンコーディングが基本です。Swiftもそれに合わせることにより、効率的に文字列を処理できるようになったわけです。また、保存形式がASCIIとUTF-16の2種類からUTF-8に統一されたことにより、コードサイズおよびコンパイル時間、実行速度が最適化されました。

文字列リテラル

それでは、Swiftにおける文字列操作の基本について解説していきましょう。Swiftでは、文字列はString型のインスタンスです。String型はクラス(class)ではなく構造体(struct)として定義されています。Objective-Cと同じく、Swiftの文字列リテラルは、文字列をダブルクォーテーション「"」で囲みます。

リスト1:Swiftの文字列リテラル

var myName = "大津真"

上記の例では、次のように型アノテーションとして「:String」が指定されたものとみなされます。

リスト2:形アノテーションをつけた文字列リテラルの指定

var myName:String = "大津真"

なおJavaScriptなどとは異なり、シングルクォーテーション「'」は使用できないので注意してください。

リスト3:シングルクォートは使用できない

var myName = '大津真' // <- エラー

文字列リテラル内にユニコードのスカラ値を指定して埋め込むには、「\u{スカラ値の16進数形式}」の形式のエスケープシーケンスを記述します。

リスト4:顔文字も表示できる

print("顔文字: \u{1F601}") // -> 顔文字: 😁

文字列リテラル内には、エスケープシーケンスを使用して次のような特殊文字を含められます。

文字列リテラル内で使用できる特殊文字

特殊文字説明
\”ダブルクォート
\’シングルクォート
\rキャリッジリターン
\nラインフィード
\0null文字
\\バックスラッシュ自身

文字列補完

文字列リテラル内に、「\(変数名)」の書式で記述することで、変数の値を埋め込むことができます。これを「文字列補間(String Interpolation)」と言います。たとえば、変数myNameを文字列リテラル内に埋め込むには次のようにします。

リスト5:文字列補完の例

var myName = "大津真"
print("私の名前は\(myName)です。") // -> 私の名前は大津真です。

文字列補完内では、変数だけでなく式も記述できます。

リスト6:文字列補完内で式を記述する例

var dollar = 5
var rate = 100
print("\(dollar)ドルは\(dollar * rate)円です") // -> 5ドルは500円です。

複数行の文字列リテラル

文字列リテラルの途中に改行「\n」を入れることにより、複数行の文字列リテラルを記述できます。

リスト7:複数行の文字列リテラルも記述できる

var msg = "こんにちは\nさようなら\nまた会う日まで"
print(msg)

上記のリストの実行結果は、以下のようになります。

cmd01

こんにちは
さようなら
また会う日まで

複数行の文字列リテラルを表現する別の方法として、「"""」(ダブルクォーテーション「"」を3つ繋げる)で括る方法が挙げられます。「"""」の次の行から文字列を記述し、最後の行には「"""」のみを記述します。

リスト8:「"""」で複数行の文字列リテラルを表す

var msg = """
こんにちは
さようなら
また会う日まで
"""

この時、最後の「"""」で設定したインデントが、各行のインデントから削除されます

リスト9:最後の行でインデントを調整する

var msg = """
        こんにちは
                さようなら
                        また会う日まで
        """
print(msg)

上記のリストの実行結果は、以下のようになります。

cmd02

こんにちは
        さようなら
                また会う日まで

また以下の例のように、最後の「"""」のインデントよりも前方に行を記述した場合にはエラーとなります。

リスト10:エラーになる例

var msg = """
        こんにちは
                さようなら
また会う日まで
        """

Raw Stringsリテラル

Swift 4.2以前は、文字列内に「\」を文字そのものとして埋め込むには、その直前に「\」を記述して「\\」のように記述する必要がありました。

リスト11:Swift 4.2以前の例

print("\\Hello \\World") // -> \Hello \World

Swift 5では、文字列リテラルを「#」で囲むことにより、「\」を特殊文字ではなく文字そのものとして扱えるようになりました。

リスト12:Swift 5ではこのように記述できるように

print(#"\Hello \World"#) // -> \Hello \World

これをRaw Stringsリテラルといいます。Raw Stringsリテラル内で文字列補完を使用するには「\#(変数)」の形式で指定します。

リスト13:Raw Stringsリテラル内での文字列補完

var myName = "大津真"
print(#"私の名前は\#(myName)です。"#) // -> 私の名前は大津真です。

文字列の長さはcountプロパティで

Swift 4以降では、文字列の長さはString型のcountプロパティで取得できます。

リスト14:文字列の長さはcountプロパティで取得

var myName = "大津真"
var len = myName.count  // -> 3

Swiftは、ユニコードの扱いが他の言語と比べて厳格で、見た目の1文字を正しく1文字とカウントしてくれます。たとえば、"🇯🇵"(日本)や"🇺🇸"(アメリカ)といった国旗(環境によってはJPやUSと表示される)は、国コードリスト(ISO 3166)を元に1つの文字を組み合わせて表現します。"🇯🇵"は、"🇯"と"🇵"の組みわせです。

🇯(\u{1f1ef}) + 🇵(\u{1f1f5}) => 🇯🇵

Pythonとの比較で説明しましょう。Pythonでは文字列の長さを取得するのにlen()関数を使用します。len()関数では"🇯🇵"を2文字と認識します。

リスト15:Pythonで文字列の長さを取得する

>>> jp = "🇯🇵"
>>> len(jp)
2

それに対して、Swiftのcountプロパティでは、見た目通り1文字と認識します。

リスト16:Swiftで文字列の長さを取得する

var jp = "🇯🇵"
print(jp.count)  // -> 1

別の例を挙げましょう。ユニコードでは、「ポ」のような濁点付きの文字は、「ホ」+濁点(u{309a})といった結合文字列(Combining Character Sequence)で表現することもできます。Pythonのlen()関数では、結合文字列は2文字と認識されます。

リスト17:Pythonでの結合文字列の扱い

>>> str1 = "ホ" + '\u309a' + "ハ" + '\u309a' + "イ"
>>> str1
'ポパイ'
>>> len(str1)
5

一方Swiftでは、結合文字列も1文字と認識されます。

リスト18:Swiftでの結合文字列の扱い

var str1 = "ホ\u{309a}ハ\u{309a}イ"
print(str1) // -> "ポパイ"
print(str.count) // -> 3

String型には、文字列をユニコードのスカラ値の並びとして扱うunicodeScalarsプロパティがあります。unicodeScalarsプロパティのcountプロパティでは、それぞれのコードポイントの値を1文字と認識します。したがってcountプロパティの値は、Pythonのlen()関数の値と同じになります。

リスト19:unicodeScalarsプロパティのcountプロパティ

var jp = "🇯🇵"
print(jp.unicodeScalars.count) // -> 2
var str1 = "ホ\u{309a}ハ\u{309a}イ"
print(str1.unicodeScalars.count) // -> 5

文字列と数値の相互変換

「+」演算子を使用することにより、文字列同士を連結できます。

var hw = "Hello" + " " + "World" // -> "Hello World"

ただし、Swiftは型(Type)に厳格な言語です。そのため、次のように文字列と数値を「+」演算子で直接連結することはできません。

リスト20:文字列と数値を直接連結することはできない

var age = 53
var msg = age + "歳" // -> 型が異なるためエラー

あらかじめString()イニシャライザを使用して、数値を文字列に変換してから連結する必要があります。

リスト21:String()イニシャライザを利用する

var msg = String(age) + "歳" // -> 53歳

あるいは、オブジェクトの文字列表現を取得するdescriptionプロパティを使用する方法もあります。

リスト22:descriptionプロパティを利用する

var msg = age.description + "歳" // -> 53歳

また、文字列補完で変数を埋め込むことでも連結できます。

リスト23:文字列補完を利用する

var msg = "\(age)歳" // -> 53歳

文字列を数値に変換する

逆に、"123"のような整数を表す文字列を数値に変換するには、Int()イニシャライザを使用します。戻り値はオプショナル型なので、安全に変換するにはオプショナルバインディングを使用するとよいでしょう。

リスト24:文字列を数値に変換

var str = "123"
if let num = Int(str) {
        print(num)  // -> 123
} else {
        print("変換できません")
}

Int()イニシャライザのradix引数では、基数を指定できます。たとえば文字列を16進数とみなして変換するには、radixに「16」を指定して次のようにします。

リスト25:文字列を16進数とみなして数値に変換

var str = "ff"
if let num = Int(str, radix:16) {
        print(num)  // -> 255
} else {
        print("変換できません")
}

「3.14」のような小数を表す文字列を、Double型の数値に変換するにはDouble()イニシャライザを使用します。

リスト26:小数を表す文字列を数値に変換

var str = "3.14"
if let num = Double(str) {
        print(num)  // -> 3.14
} else {
        print("変換できません")
}

同様にFloat型に変換するにはFloat()イニシャライザを使用します。

for~in文で文字列内の文字に順にアクセスする

配列や要素にアクセスする時などに使う添字式のことを「サブスクリプト」といいますが、Swiftでは、サブスクリプトを使用して文字列内の指定した文字にアクセスすることはできません。

リスト27:サブスクリプトによる文字列内の文字指定はできない

let str2 = "東京都世田谷区"
var c = str2[2] // <- エラー

これは文字によって必要なバイト数が異なるため、先頭から順にアクセスする必要があるためです。for~in文を使用すると、文字列内の文字を先頭から順に取り出すことができます。

リスト28:for~in文で文字列内の文字を指定

let str2 = "東京都世田谷区"
for c in str2 {
        print(c)
}

上記のリストの実行結果は、以下のようになります。

cmd03

東
京
都
世
田
谷
区

大文字、小文字の変換

英大文字を英小文字に変換するにはlowercased()メソッドを使用します。半角文字だけでなく、全角の英大文字も全角の英小文字に変換されます。

リスト29:lowercased()メソッドは全角も変換の対象に

let l = "HELLO HELLO".lowercased() // -> "hello hello"

逆に、英小文字を英大文字に変換するにはuppercased()メソッドを使用します。

リスト30:uppercased()メソッドも同様

let u = "world world".uppercased() // -> "WORLD WORLD"

文字列の比較

「==」(等しい)「!=」(等しくない)といった比較演算子を使用して、2つの文字列を比較できます。

このとき、「ホ」+濁点(u{309a}」のような濁点を加えた結合文字列も、「ポ」のような通常の文字と等しいと判断されます。

リスト31:結合文字列を含む比較

// 結合文字列(ポパイ)
var str1 = "ホ\u{309a}ハ\u{309a}イ"
// 通常の文字列(ポパイ)
var str2 = "ポパイ"
if str1 == str2 {
        print("同じです") // -> 同じです。
}

また「<」「<=」「>」「>=」といった比較演算子を用いると、文字列同士を辞書式順序の大小で比較できます。

リスト32:辞書順で比較すると95は12345より大きい

var sm1 = "95"
var sm2 = "12345"

if sm1 > sm2 {
        print("\(sm1)は\(sm2)より大きい") // -> 95は12345より大きい
}

東京都生まれ。早稲田大学理工学部卒業後、外資系コンピューターメーカーにSEとして8年間勤務。現在はフリーランスのテクニカルライター、プログラマー。

<主な著書>
きちんとわかる! JavaScript とことん入門(技術評論社)
基礎Python(インプレス)
他多数

連載バックナンバー

開発言語技術解説
第9回

Swiftでの文字列操作の基礎(その1)

2019/9/27
連載9回目となる今回は、Swfitによる文字列操作の基本を学びます。あわせてSwift 5で導入された新機能にもついても紹介します。
開発言語
第8回

Swiftでクロージャを理解する

2018/12/21
連載8回目となる今回は、モダンな言語に欠かせないクロージャについて解説します。
開発言語技術解説
第7回

Swiftのユーザ定義関数の活用

2018/9/13
連載7回目となる今回は、Swiftのユーザ定義関数のさまざまな活用方法を紹介していく。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています