制御フロー
分岐、ループ、早期終了を使用してコードを構造化します。
Swiftは様々な制御フロー文を提供します。これには、タスクを複数回実行するためのwhile
ループ、特定の条件に基づいて異なるコードの分岐を実行するためのif
、guard
、switch
文、そしてコードの実行フローを別のポイントに転送するためのbreak
やcontinue
などの文が含まれます。Swiftは配列、辞書、範囲、文字列、その他のシーケンスを簡単に反復処理できるfor-in
ループを提供します。また、現在のスコープを離れるときに実行されるコードをラップするdefer
文も提供します。
Swiftのswitch
文は、多くのC言語系の言語の対応するものよりもはるかに強力です。ケースは、区間の一致、タプル、特定の型へのキャストなど、多くの異なるパターンに一致させることができます。switch
ケースで一致した値は、ケースの本体内で使用するために一時的な定数や変数にバインドでき、各ケースのwhere
句を使用して複雑な一致条件を表現できます。
For-Inループ
for-in
ループを使用して、配列のアイテム、数値の範囲、文字列の文字などのシーケンスを反復処理します。
この例では、for-in
ループを使用して配列のアイテムを反復処理します。
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!
辞書を反復処理して、そのキーと値のペアにアクセスすることもできます。辞書内の各アイテムは、辞書が反復処理されるときに(key, value)
タプルとして返され、for-in
ループの本体内で使用するために(key, value)
タプルのメンバーを明示的に名前付き定数として分解できます。以下のコード例では、辞書のキーはanimalName
という定数に分解され、辞書の値はlegCount
という定数に分解されます。
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
print("\(animalName)s have \(legCount) legs")
}
// cats have 4 legs
// ants have 6 legs
// spiders have 8 legs
辞書の内容は本質的に順序付けされておらず、それらを反復処理する際に取得される順序は保証されません。特に、辞書にアイテムを挿入する順序は、それらが反復処理される順序を定義しません。配列と辞書の詳細については、コレクションタイプを参照してください。
数値範囲を使用してfor-in
ループを使用することもできます。この例では、5の倍数の最初のいくつかのエントリを出力します。
for index in 1...5 {
print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
反復処理されるシーケンスは、閉じた範囲演算子(...
)を使用して示されるように、1から5までの数値の範囲です。index
の値は範囲内の最初の数値(1)に設定され、ループ内のステートメントが実行されます。この場合、ループには1つのステートメントしか含まれておらず、現在のindex
の値に対して5の倍数のエントリを出力します。ステートメントが実行された後、index
の値は範囲内の2番目の値(2)に更新され、再びprint(_:separator:terminator:)
関数が呼び出されます。このプロセスは、範囲の終わりに達するまで続きます。
上記の例では、index
はループの各反復の開始時に自動的に設定される定数です。そのため、index
は使用される前に宣言する必要はありません。ループ宣言に含まれるだけで暗黙的に宣言され、let
宣言キーワードは必要ありません。
シーケンスからの各値が必要ない場合は、変数名の代わりにアンダースコアを使用して値を無視できます。
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// Prints "3 to the power of 10 is 59049"
上記の例では、ある数値を別の数値の累乗に計算します(この場合、3の10乗)。1(つまり、3の0乗)の開始値を3で10回掛け合わせ、1から10までの閉じた範囲を使用して計算します。この計算では、ループを通過するたびに個々のカウンター値は不要です。コードは単にループを正しい回数実行します。ループ変数の代わりに使用されるアンダースコア文字(_
)は、個々の値を無視し、ループの各反復中の現在の値にアクセスできません。
いくつかの状況では、両端点を含む閉じた範囲を使用したくない場合があります。時計の文字盤に毎分の目盛りを描くことを考えてみてください。0分から始めて60個の目盛りを描きたい場合、下限を含み上限を含まない半開範囲演算子(..<
)を使用します。範囲の詳細については、範囲演算子を参照してください。
let minutes = 60
for tickMark in 0..<minutes {
// 毎分の目盛りを描画する(60回)
}
一部のユーザーは、UIに少ない目盛りを希望するかもしれません。彼らは5分ごとに1つの目盛りを好むかもしれません。stride(from:to:by:)
関数を使用して不要な目盛りをスキップします。
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
// 5分ごとに目盛りを描画する(0、5、10、15 ... 45、50、55)
}
閉じた範囲も使用できます。stride(from:through:by:)
を使用します。
let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
// 3時間ごとに目盛りを描画する(3、6、9、12)
}
上記の例では、for-in
ループを使用して範囲、配列、辞書、文字列を反復処理します。ただし、Sequence
プロトコルに準拠している限り、独自のクラスやコレクションタイプを含む任意のコレクションを反復処理するためにこの構文を使用できます。
Whileループ
while
ループは、条件がfalseになるまで一連のステートメントを実行します。これらの種類のループは、最初の反復が始まる前に反復回数が不明な場合に最適です。Swiftは2種類のwhile
ループを提供します。
while
は、ループの各パスの開始時に条件を評価します。repeat-while
は、ループの各パスの終了時に条件を評価します。
While
while
ループは、単一の条件を評価することから始まります。条件がtrueの場合、一連のステートメントが条件がfalseになるまで繰り返されます。
以下は、while
ループの一般的な形式です。
while <#condition#> {
<#statements#>
}
この例では、シンプルなスゴロクゲーム(Chutes and Laddersとも呼ばれる)をプレイします。
ゲームのルールは次のとおりです。
- ボードには25のマスがあり、目標は25番目のマスに到達することです。
- プレイヤーの開始マスは「マス0」で、ボードの左下隅のすぐ外にあります。
- 各ターンで6面のサイコロを振り、その数だけマスを進みます。
- ターンがはしごの下で終わると、はしごを上ります。
- ターンがヘビの頭で終わると、ヘビを下ります。
ゲームボードはInt
値の配列で表されます。そのサイズはfinalSquare
という定数に基づいており、配列を初期化するために使用され、後の例で勝利条件を確認するためにも使用されます。プレイヤーはボードの外、「マス0」から始まるため、ボードは25ではなく26のゼロのInt
値で初期化されます。
let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
次に、いくつかのマスに特定の値を設定して、ヘビやはしごを表します。はしごの下にあるマスにはボードを上るための正の数値があり、ヘビの頭にあるマスにはボードを下るための負の数値があります。
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
マス3には、マス11に移動するはしごの下があります。これを表すために、board[03]
は+08に等しく、これは3と11の差に相当します。値とステートメントを整列させるために、単項プラス演算子(+i
)が単項マイナス演算子(-i
)とともに明示的に使用され、10未満の数値はゼロでパディングされます。(どちらのスタイリスティックな技法も厳密には必要ありませんが、コードをより整然とします。)
var square = 0
var diceRoll = 0
while square < finalSquare {
// サイコロを振る
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
// 振った数だけ進む
square += diceRoll
if square < board.count {
// まだボード上にいる場合、ヘビやはしごのために上下に移動する
square += board[square]
}
}
print("ゲームオーバー!")
上記の例では、非常にシンプルな方法でサイコロを振ります。ランダムな数値を生成する代わりに、diceRoll
の値を0から始めます。while
ループを通過するたびに、diceRoll
は1ずつ増加し、その後、値が大きすぎるかどうかを確認します。この戻り値が7に等しい場合、サイコロの目は大きすぎるため、値が1にリセットされます。結果は、常に1、2、3、4、5、6、1、2という一連のdiceRoll
値です。
サイコロを振った後、プレイヤーはdiceRoll
マス進みます。サイコロの目がプレイヤーを25番目のマスを超えて移動させる可能性があります。このシナリオに対処するために、コードはsquare
がボード配列のcount
プロパティより小さいかどうかを確認します。square
が有効な場合、現在のsquare
値にboard[square]
に格納されている値が追加され、プレイヤーははしごやヘビの上下に移動します。
注:このチェックが行われない場合、
board[square]
はボード配列の範囲外の値にアクセスしようとし、ランタイムエラーが発生します。
現在のwhile
ループの実行が終了し、ループの条件が再度実行されるかどうかを確認します。プレイヤーが25番目のマスに到達または超えた場合、ループの条件はfalseと評価され、ゲームは終了します。
この場合、while
ループが適切です。ゲームの長さはwhile
ループの開始時には明確ではありません。代わりに、特定の条件が満たされるまでループが実行されます。
Repeat-While
while
ループのもう一つのバリエーションであるrepeat-while
ループは、ループブロックを最初に1回実行し、その後、ループの条件を考慮します。その後、条件がfalseになるまでループを繰り返します。
注:Swiftの
repeat-while
ループは、他の言語のdo-while
ループに相当します。
以下は、repeat-while
ループの一般的な形式です。
repeat {
<#statements#>
} while <#condition#>
以下は、while
ループではなくrepeat-while
ループとして書かれたスゴロクゲームの例です。finalSquare
、board
、square
、およびdiceRoll
の値は、while
ループと同じ方法で初期化されます。
let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
var square = 0
var diceRoll = 0
このバージョンのゲームでは、ループの最初のアクションとしてはしごやヘビを確認します。ボード上のはしごはプレイヤーを直接25番目のマスに移動させることはないため、はしごを上ることでゲームに勝つことはできません。したがって、ループの最初のアクションとしてヘビやはしごを確認することは安全です。
ゲームの開始時に、プレイヤーは「マス0」にいます。board[0]
は常に0であり、影響はありません。
repeat {
// ヘビやはしごのために上下に移動する
square += board[square]
// サイコロを振る
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
// 振った数だけ進む
square += diceRoll
} while square < finalSquare
print("ゲームオーバー!")
コードがヘビやはしごを確認した後、サイコロが振られ、プレイヤーはdiceRoll
マス進みます。現在のループの実行が終了します。
ループの条件(while square < finalSquare
)は以前と同じですが、今回はループの最初の実行が終了するまで評価されません。上記のrepeat-while
ループの構造は、前の例のwhile
ループよりもこのゲームに適しています。上記のrepeat-while
ループでは、square += board[square]
は常にループのwhile
条件がsquare
がまだボード上にあることを確認した直後に実行されます。この動作により、前述のゲームのwhile
ループバージョンで見られた配列境界チェックの必要性がなくなります。
条件文
特定の条件に基づいて異なるコードの部分を実行することはしばしば有用です。エラーが発生したときに追加のコードを実行したり、値が高すぎたり低すぎたりしたときにメッセージを表示したりすることができます。これを行うには、コードの一部を条件付きにします。
Swiftは、コードに条件付き分岐を追加するための2つの方法を提供します:if
文とswitch
文です。通常、if
文を使用して、少数の可能な結果を持つ単純な条件を評価します。switch
文は、複数の可能な組み合わせを持つより複雑な条件に適しており、パターンマッチングが適切なコード分岐を選択するのに役立つ状況で役立ちます。
If
最も単純な形式では、if
文には単一のif
条件があります。その条件がtrueの場合にのみ、一連のステートメントを実行します。
var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
print("とても寒いです。スカーフを着用することを検討してください。")
}
// "とても寒いです。スカーフを着用することを検討してください。"と出力されます。
上記の例では、温度が32度以下(氷点)かどうかを確認します。そうであれば、メッセージが表示されます。そうでない場合、メッセージは表示されず、if
文の閉じ括弧の後にコードの実行が続行されます。
if
文は、if
条件がfalseの場合の代替の一連のステートメントを提供できます。これらのステートメントはelse
キーワードで示されます。
temperatureInFahrenheit = 40
if temperatureInFahrenheit <= 32 {
print("とても寒いです。スカーフを着用することを検討してください。")
} else {
print("それほど寒くありません。Tシャツを着てください。")
}
// "それほど寒くありません。Tシャツを着てください。"と出力されます。
これらの2つの分岐のいずれかが常に実行されます。温度が40度に上昇したため、スカーフを着用するほど寒くなくなり、代わりにelse
分岐がトリガーされます。
追加の条件を考慮するために、複数のif
文を連鎖させることができます。
temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 {
print("とても寒いです。スカーフを着用することを検討してください。")
} else if temperatureInFahrenheit >= 86 {
print("本当に暖かいです。日焼け止めを忘れないでください。")
} else {
print("それほど寒くありません。Tシャツを着てください。")
}
// "本当に暖かいです。日焼け止めを忘れないでください。"と出力されます。
ここでは、特に暖かい温度に対応するために追加のif
文が追加されました。最終的なelse
句は残り、あまり暖かくも寒くもない温度に対する応答を出力します。
最終的なelse
句はオプションであり、条件のセットが完全である必要がない場合は省略できます。
temperatureInFahrenheit = 72
if temperatureInFahrenheit <= 32 {
print("とても寒いです。スカーフを着用することを検討してください。")
} else if temperatureInFahrenheit >= 86 {
print("本当に暖かいです。日焼け止めを忘れないでください。")
}
温度がif
条件をトリガーするほど寒くなく、else if
条件をトリガーするほど暖かくないため、メッセージは表示されません。
Swiftは、値を設定する際に使用できるif
の短縮表記を提供します。たとえば、次のコードを考えてみてください。
let temperatureInCelsius = 25
let weatherAdvice: String
if temperatureInCelsius <= 0 {
weatherAdvice = "とても寒いです。スカーフを着用することを検討してください。"
} else if temperatureInCelsius >= 30 {
weatherAdvice = "本当に暖かいです。日焼け止めを忘れないでください。"
} else {
weatherAdvice = "それほど寒くありません。Tシャツを着てください。"
}
print(weatherAdvice)
// "それほど寒くありません。Tシャツを着てください。"と出力されます。
ここでは、各分岐がweatherAdvice
定数の値を設定し、if
文の後に出力されます。
代替の構文を使用すると、if
式としてこのコードをより簡潔に書くことができます。
let weatherAdvice = if temperatureInCelsius <= 0 {
"とても寒いです。スカーフを着用することを検討してください。"
} else if temperatureInCelsius >= 30 {
"本当に暖かいです。日焼け止めを忘れないでください。"
} else {
"それほど寒くありません。Tシャツを着てください。"
}
print(weatherAdvice)
// "それほど寒くありません。Tシャツを着てください。"と出力されます。
このif
式バージョンでは、各分岐に単一の値が含まれています。分岐の条件がtrueの場合、その分岐の値がif
式全体の値として使用され、weatherAdvice
の代入に使用されます。すべてのif
分岐には対応するelse if
分岐またはelse
分岐があり、いずれかの分岐が常に一致し、if
式が常に値を生成することが保証されます。
代入の構文がif
式の外側で始まるため、各分岐内でweatherAdvice =
を繰り返す必要はありません。代わりに、if
式の各分岐はweatherAdvice
の3つの可能な値のいずれかを生成し、代入はその値を使用します。
すべてのif
式の分岐には同じ型の値が含まれている必要があります。Swiftは各分岐の型を個別にチェックするため、nil
のような値は複数の型で使用でき、Swiftがif
式の型を自動的に決定するのを妨げます。代わりに、型を明示的に指定する必要があります。たとえば:
let freezeWarning: String? = if temperatureInCelsius <= 0 {
"氷点下です。氷に注意してください!"
} else {
nil
}
上記のコードでは、if
式の1つの分岐には文字列値があり、もう1つの分岐にはnil
値があります。nil
値は任意のオプション型の値として使用できるため、freezeWarning
がオプションの文字列であることを明示的に記述する必要があります。
この型情報を提供する別の方法は、freezeWarning
の明示的な型を提供する代わりに、nil
の明示的な型を提供することです。
let freezeWarning = if temperatureInCelsius <= 0 {
"氷点下です。氷に注意してください!"
} else {
nil as String?
}
if
式は、エラーをスローしたり、fatalError(_:file:line:)
のような関数を呼び出したりして、予期しない失敗に対応できます。たとえば:
let weatherAdvice = if temperatureInCelsius > 100 {
throw TemperatureError.boiling
} else {
"適切な温度です。"
}
この例では、if
式は予測温度が100°Cを超えているかどうかを確認します。これほど高温の場合、if
式はテキストの要約を返す代わりに.boiling
エラーをスローします。このif
式はエラーをスローする可能性がありますが、その前にtry
を書く必要はありません。エラー処理の詳細については、エラー処理を参照してください。
代入の右側にif
式を使用するだけでなく、関数やクロージャが返す値としても使用できます。
Switch
switch
文は、値を考慮し、それをいくつかの可能な一致パターンと比較します。その後、最初に一致したパターンに基づいて適切なコードブロックを実行します。switch
文は、複数の潜在的な状態に対応するためのif
文の代替手段を提供します。
最も単純な形式では、switch
文は値を同じ型の1つ以上の値と比較します。
switch <#some value to consider#> {
case <#value 1#>:
<#respond to value 1#>
case <#value 2#>,
<#value 3#>:
<#respond to value 2 or 3#>
default:
<#otherwise, do something else#>
}
すべてのswitch
文は、case
キーワードで始まる複数の可能なケースで構成されます。特定の値と比較するだけでなく、Swiftは各ケースがより複雑な一致パターンを指定するためのいくつかの方法を提供します。これらのオプションは、この章の後半で説明します。
if
文の本体と同様に、各ケースは個別のコード実行の分岐です。switch
文は、どの分岐を選択するかを決定します。この手順は、考慮されている値に基づいて切り替えることとして知られています。
すべてのswitch
文は網羅的でなければなりません。つまり、考慮されている型のすべての可能な値がswitch
ケースのいずれかによって一致する必要があります。すべての可能な値に対してケースを提供することが適切でない場合、明示的に対処されていない値をカバーするためにdefault
ケースを定義できます。このdefault
ケースはdefault
キーワードで示され、常に最後に表示されなければなりません。
この例では、単一の小文字の文字someCharacter
を考慮するためにswitch
文を使用します。
let someCharacter: Character = "z"
switch someCharacter {
case "a":
print("ラテンアルファベットの最初の文字")
case "z":
print("ラテンアルファベットの最後の文字")
default:
print("その他の文字")
}
// "ラテンアルファベットの最後の文字"と出力されます。
switch
文の最初のケースは、英語アルファベットの最初の文字a
に一致し、2番目のケースは最後の文字z
に一致します。switch
はすべての可能な文字に対してケースを持つ必要があるため、このswitch
文はa
とz
以外のすべての文字に一致するdefault
ケースを使用します。この規定により、switch
文が網羅的であることが保証されます。
if
文と同様に、switch
文にも式形式があります。
let anotherCharacter: Character = "a"
let message = switch anotherCharacter {
case "a":
"ラテンアルファベットの最初の文字"
case "z":
"ラテンアルファベットの最後の文字"
default:
"その他の文字"
}
print(message)
// "ラテンアルファベットの最初の文字"と出力されます。
この例では、switch
式の各ケースにanotherCharacter
が一致した場合に使用されるmessage
の値が含まれています。switch
は常に網羅的であるため、代入する値が常に存在します。
if
式と同様に、エラーをスローしたり、fatalError(_:file:line:)
のような関数を呼び出したりして、値を提供する代わりに特定のケースに対応できます。switch
式を代入の右側に使用するだけでなく、関数やクロージャが返す値としても使用できます。
暗黙的なフォールスルーなし
C言語やObjective-Cのswitch
文とは対照的に、Swiftのswitch
文はデフォルトで各ケースの下部をフォールスルーして次のケースに入ることはありません。代わりに、最初に一致したswitch
ケースが完了すると、switch
文全体の実行が終了し、明示的なbreak
文を必要とせずに次のケースに進むことはありません。これにより、switch
文はC言語のものよりも安全で使いやすくなり、誤って複数のswitch
ケースを実行することを避けることができます。
注:Swiftでは
break
は必須ではありませんが、特定のケースを一致させて無視するためにbreak
文を使用したり、一致したケースが実行を完了する前にそのケースを終了するためにbreak
文を使用することができます。詳細については、switch
文でのbreak
を参照してください。
各ケースの本体には少なくとも1つの実行可能なステートメントが含まれている必要があります。最初のケースが空であるため、次のコードは有効ではありません。
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // 無効、ケースの本体が空です
case "A":
print("文字A")
default:
print("文字Aではありません")
}
// これはコンパイル時エラーを報告します。
C言語のswitch
文とは異なり、このswitch
文は"a"
と"A"
の両方に一致しません。むしろ、case "a":
が実行可能なステートメントを含まないため、コンパイル時エラーが報告されます。このアプローチにより、1つのケースから別のケースへの偶発的なフォールスルーが防止され、意図が明確な安全なコードが作成されます。
"a"
と"A"
の両方に一致する単一のケースを作成するには、カンマで区切って2つの値を複合ケースに結合します。
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
print("文字A")
default:
print("文字Aではありません")
}
// "文字A"と出力されます。
読みやすさのために、複合ケースは複数行にわたって記述することもできます。複合ケースの詳細については、複合ケースを参照してください。
注:特定の
switch
ケースの最後に明示的にフォールスルーするには、fallthrough
キーワードを使用します。詳細については、フォールスルーを参照してください。
区間マッチング
switch
ケースの値は、その値が区間に含まれているかどうかをチェックできます。この例では、任意のサイズの数値の自然言語カウントを提供するために数値区間を使用します。
let approximateCount = 62
let countedThings = "土星の衛星"
let naturalCount: String
switch approximateCount {
case 0:
naturalCount = "なし"
case 1..<5:
naturalCount = "少数の"
case 5..<12:
naturalCount = "いくつかの"
case 12..<100:
naturalCount = "数十の"
case 100..<1000:
naturalCount = "数百の"
default:
naturalCount = "多数の"
}
print("土星の衛星は\(naturalCount)あります。")
// "土星の衛星は数十あります。"と出力されます。
上記の例では、approximateCount
がswitch
文で評価されます。各ケースはその値を数値または区間と比較します。approximateCount
の値が12から100の間にあるため、naturalCount
には"数十の"という値が割り当てられ、実行はswitch
文から転送されます。
タプル
タプルを使用して、同じswitch
文で複数の値をテストできます。タプルの各要素は、異なる値または値の区間と比較できます。代わりに、アンダースコア文字(_
)を使用して、任意の値に一致させることもできます。
以下の例では、(x, y)点を単純なタプル型(Int, Int)として表現し、次のグラフ上で分類します。
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("\(somePoint)は原点にあります")
case (_, 0):
print("\(somePoint)はx軸上にあります")
case (0, _):
print("\(somePoint)はy軸上にあります")
case (-2...2, -2...2):
print("\(somePoint)はボックス内にあります")
default:
print("\(somePoint)はボックス外にあります")
}
// "\(somePoint)はボックス内にあります"と出力されます。
switch
文は、点が原点(0, 0)にあるか、赤いx軸上にあるか、緑のy軸上にあるか、原点を中心とした青い4x4ボックス内にあるか、ボックス外にあるかを判断します。
C言語とは異なり、Swiftは同じ値または値に対して複数のswitch
ケースを考慮することができます。実際、この例では点(0, 0)がすべての4つのケースに一致する可能性があります。ただし、複数の一致が可能な場合、最初に一致したケースが常に使用されます。点(0, 0)は最初にcase (0, 0)
に一致し、他のすべての一致するケースは無視されます。
値バインディング
switch
ケースは、一時的な定数や変数に一致する値を名前付けし、そのケースの本体内で使用することができます。この動作は値バインディングとして知られており、値がケースの本体内で一時的な定数や変数にバインドされます。
以下の例では、(x, y)点をタプル型(Int, Int)として表現し、次のグラフ上で分類します。
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
print("x軸上にあり、xの値は\(x)です")
case (0, let y):
print("y軸上にあり、yの値は\(y)です")
case let (x, y):
print("他の場所にあり、(\(x), \(y))にあります")
}
// "x軸上にあり、xの値は\(x)です"と出力されます。
switch
文は、点が赤いx軸上にあるか、緑のy軸上にあるか、他の場所にあるかを判断します。
3つのswitch
ケースは、anotherPoint
からの1つまたは両方のタプル値を一時的な定数x
とy
にバインドします。最初のケースcase (let x, 0)
は、y値が0の任意の点に一致し、点のx値を一時的な定数x
に割り当てます。同様に、2番目のケースcase (0, let y)
は、x値が0の任意の点に一致し、点のy値を一時的な定数y
に割り当てます。
一時的な定数が宣言された後、それらはケースのコードブロック内で使用できます。ここでは、点の分類を出力するために使用されます。
このswitch
文にはdefault
ケースがありません。最終的なケースcase let (x, y)
は、任意の値に一致する2つの一時的な定数のタプルを宣言します。anotherPoint
は常に2つの値のタプルであるため、このケースはすべての残りの値に一致し、switch
文を網羅的にするためにdefault
ケースは必要ありません。
Where
switch
ケースは、追加の条件をチェックするためにwhere
句を使用できます。
以下の例では、(x, y)点を次のグラフ上で分類します。
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
print("(\(x), \(y))はx == yの線上にあります")
case let (x, y) where x == -y:
print("(\(x), \(y))はx == -yの線上にあります")
case let (x, y):
print("(\(x), \(y))は任意の点です")
}
// "(\(x), \(y))はx == -yの線上にあります"と出力されます。
switch
文は、点が緑の対角線上にあるか(x == y)、紫の対角線上にあるか(x == -y)、またはどちらでもないかを判断します。
3つのswitch
ケースは、yetAnotherPoint
からの2つのタプル値を一時的な定数x
とy
にバインドします。これらの定数はwhere
句の一部として使用され、動的なフィルタを作成します。switch
ケースは、where
句の条件がその値に対してtrueと評価される場合にのみ、現在の点の値に一致します。
前の例と同様に、最終的なケースはすべての残りの値に一致し、switch
文を網羅的にするためにdefault
ケースは必要ありません。
複合ケース
同じ本体を共有する複数のswitch
ケースは、case
の後にいくつかのパターンをカンマで区切って記述することで結合できます。パターンのいずれかが一致する場合、そのケースは一致したと見なされます。リストが長い場合、パターンは複数行にわたって記述できます。たとえば:
let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
print("\(someCharacter)は母音です")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
print("\(someCharacter)は子音です")
default:
print("\(someCharacter)は母音でも子音でもありません")
}
// "\(someCharacter)は母音です"と出力されます。
switch
文の最初のケースは、英語の小文字の母音すべてに一致します。同様に、2番目のケースは英語の小文字の子音すべてに一致します。最後に、default
ケースは他のすべての文字に一致します。
複合ケースには値バインディングも含めることができます。複合ケースのすべてのパターンには同じセットの値バインディングが含まれている必要があり、各バインディングは複合ケースのすべてのパターンから同じ型の値を取得する必要があります。これにより、複合ケースのどの部分が一致しても、ケースの本体のコードは常にバインディングの値にアクセスでき、その値は常に同じ型を持つことが保証されます。
let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
print("軸上にあり、原点から\(distance)離れています")
default:
print("軸上にありません")
}
// "軸上にあり、原点から\(distance)離れています"と出力されます。
上記のケースには2つのパターンがあります。(let distance, 0)
はx軸上の点に一致し、(0, let distance)
はy軸上の点に一致します。両方のパターンにはdistance
のバインディングが含まれており、distance
は両方のパターンで整数です。これにより、ケースの本体のコードは常にdistance
の値にアクセスできます。
制御転送文
制御転送文は、コードの実行順序を変更し、コードの一部から別の部分に制御を転送します。Swiftには5つの制御転送文があります。
continue
break
fallthrough
return
throw
continue
、break
、およびfallthrough
文については以下で説明します。return
文については関数を参照し、throw
文についてはエラーの伝播を参照してください。
Continue
continue
文は、ループに現在の反復を停止し、ループの次の反復の最初から再開するように指示します。ループ全体を終了せずに「現在のループ反復が終了しました」と言います。
次の例では、小文字の文字列からすべての母音とスペースを削除して、暗号化されたパズルフレーズを作成します。
let puzzleInput = "great minds think alike"
var puzzleOutput = ""
let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "]
for character in puzzleInput {
if charactersToRemove.contains(character) {
continue
}
puzzleOutput.append(character)
}
print(puzzleOutput)
// "grtmndsthnklk"と出力されます。
上記のコードは、母音またはスペースに一致するたびにcontinue
キーワードを呼び出し、ループの現在の反復を直ちに終了し、次の反復の最初にジャンプします。
Break
break
文は、制御フロー文全体の実行を直ちに終了します。break
文は、switch
文またはループ文の内部で使用して、switch
文またはループ文の実行を通常よりも早く終了したい場合に使用できます。
ループ文でのBreak
ループ文の内部で使用される場合、break
はループの実行を直ちに終了し、ループの閉じ括弧(}
)の後のコードに制御を転送します。現在のループ反復のコードは実行されず、ループの次の反復は開始されません。
Switch文でのBreak
switch
文の内部で使用される場合、break
はswitch
文の実行を直ちに終了し、switch
文の閉じ括弧(}
)の後のコードに制御を転送します。
この動作は、switch
文で1つ以上のケースを一致させて無視するために使用できます。Swiftのswitch
文は網羅的であり、空のケースを許可しないため、意図を明確にするためにケースを一致させて無視する必要がある場合があります。この場合、無視したいケースの本体全体としてbreak
文を記述します。そのケースがswitch
文によって一致すると、ケース内のbreak
文がswitch
文の実行を直ちに終了します。
注:コメントのみを含む
switch
ケースはコンパイル時エラーとして報告されます。コメントはステートメントではなく、switch
ケースを無視する原因にはなりません。switch
ケースを無視するためには常にbreak
文を使用してください。
次の例では、Character
値を切り替えて、それが4つの言語のいずれかで数字のシンボルを表しているかどうかを判断します。簡潔にするために、複数の値が単一のswitch
ケースでカバーされています。
let numberSymbol: Character = "三" // 数字3の中国語のシンボル
var possibleIntegerValue: Int?
switch numberSymbol {
case "1", "١", "一", "๑":
possibleIntegerValue = 1
case "2", "٢", "二", "๒":
possibleIntegerValue = 2
case "3", "٣", "三", "๓":
possibleIntegerValue = 3
case "4", "٤", "四", "๔":
possibleIntegerValue = 4
default:
break
}
if let integerValue = possibleIntegerValue {
print("\(numberSymbol)の整数値は\(integerValue)です。")
} else {
print("\(numberSymbol)の整数値が見つかりませんでした。")
}
// "\(numberSymbol)の整数値は3です。"と出力されます。
この例では、numberSymbol
がラテン、アラビア、中国、またはタイの数字のシンボルであるかどうかを確認します。一致が見つかった場合、switch
文のケースの1つがオプションのInt?
変数possibleIntegerValue
に適切な整数値を設定します。
switch
文の実行が完了した後、例はオプションバインディングを使用して値が見つかったかどうかを判断します。possibleIntegerValue
変数はオプション型であるため、暗黙的に初期値としてnil
を持ち、オプションバインディングはpossibleIntegerValue
がswitch
文の最初の4つのケースのいずれかによって実際の値に設定された場合にのみ成功します。
上記の例では、すべての可能なCharacter
値をリストすることは実用的ではないため、一致しない文字を処理するためにdefault
ケースが使用されます。このdefault
ケースはアクションを実行する必要はなく、その本体として単一のbreak
文が記述されています。default
ケースが一致すると、break
文がswitch
文の実行を直ちに終了し、コードの実行はif let
文から続行されます。
Fallthrough
Swiftでは、switch
文は各ケースの下部をフォールスルーして次のケースに入ることはありません。つまり、最初に一致したケースが完了すると、switch
文全体の実行が終了します。対照的に、C言語では各switch
ケースの最後に明示的なbreak
文を挿入してフォールスルーを防ぐ必要があります。デフォルトのフォールスルーを避けることで、Swiftのswitch
文はC言語のものよりもはるかに簡潔で予測可能になり、誤って複数のswitch
ケースを実行することを避けることができます。
C言語スタイルのフォールスルー動作が必要な場合、fallthrough
キーワードを使用してケースごとにこの動作をオプトインできます。以下の例では、fallthrough
を使用して数値のテキスト説明を作成します。
let integerToDescribe = 5
var description = "数値\(integerToDescribe)は"
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
description += "素数であり、さらに"
fallthrough
default:
description += "整数です。"
}
print(description)
// "数値5は素数であり、さらに整数です。"と出力されます。
この例では、新しいString
変数description
を宣言し、初期値を割り当てます。関数は次にintegerToDescribe
の値をswitch
文を使用して考慮します。integerToDescribe
の値がリスト内の素数の1つである場合、関数はdescription
の末尾にテキストを追加して、その数が素数であることを示します。その後、fallthrough
キーワードを使用してdefault
ケースにも「フォールイン」します。default
ケースは説明の末尾に追加のテキストを追加し、switch
文が完了します。
integerToDescribe
の値が既知の素数のリストにない場合、最初のswitch
ケースには一致しません。特定のケースが他にないため、integerToDescribe
はdefault
ケースに一致します。
switch
文の実行が完了した後、数値の説明がprint(_:separator:terminator:)
関数を使用して出力されます。この例では、数値5が正しく素数として識別されます。
注:
fallthrough
キーワードは、実行を次のケース(またはdefault
ケース)ブロック内のステートメントに直接移動させるだけで、switch
ケースの条件をチェックしません。これはC言語の標準的なswitch
文の動作と同様です。
ラベル付きステートメント
Swiftでは、ループや条件文を他のループや条件文の内部にネストして、複雑な制御フロー構造を作成できます。ただし、ループや条件文の両方でbreak
文を使用して実行を早期に終了することができます。したがって、どのループや条件文をbreak
文で終了するかを明示することが役立つ場合があります。同様に、複数のネストされたループがある場合、どのループにcontinue
文が影響するかを明示することが役立つ場合があります。
これらの目的を達成するために、ステートメントラベルを使用してループ文や条件文にラベルを付けることができます。条件文では、break
文とともにステートメントラベルを使用して、ラベル付きステートメントの実行を終了できます。ループ文では、break
またはcontinue
文とともにステートメントラベルを使用して、ラベル付きステートメントの実行を終了または続行できます。
ラベル付きステートメントは、ステートメントの導入キーワードと同じ行にラベルを配置し、その後にコロンを付けることで示されます。以下は、この構文のwhile
ループの例ですが、原則はすべてのループやswitch
文に同じです。
<#label name#>: while <#condition#> {
<#statements#>
}
次の例では、break
とcontinue
文をラベル付きwhile
ループとともに使用して、この章の前半で見たスゴロクゲームの適応バージョンを示します。今回は、ゲームに追加のルールがあります。
- 勝つためには、正確に25番目のマスに到達する必要があります。
- 特定のサイコロの目が25番目のマスを超える場合、25番目のマスに到達するために必要な正確な数が出るまで再度サイコロを振る必要があります。
ゲームボードは以前と同じです。
finalSquare
、board
、square
、およびdiceRoll
の値は以前と同じ方法で初期化されます。
let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
var square = 0
var diceRoll = 0
このバージョンのゲームでは、while
ループとswitch
文を使用してゲームのロジックを実装します。while
ループには、スゴロクゲームのメインゲームループを示すためにgameLoop
というステートメントラベルがあります。
while
ループの条件はwhile square != finalSquare
であり、正確に25番目のマスに到達する必要があることを反映しています。
gameLoop: while square != finalSquare {
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
switch square + diceRoll {
case finalSquare:
// diceRollは私たちを最終マスに移動させるので、ゲームは終了です
break gameLoop
case let newSquare where newSquare > finalSquare:
// diceRollは私たちを最終マスを超えて移動させるので、再度サイコロを振ります
continue gameLoop
default:
// これは有効な移動なので、その効果を確認します
square += diceRoll
square += board[square]
}
}
print("ゲームオーバー!")
サイコロは各ループの開始時に振られます。プレイヤーを直ちに移動させるのではなく、ループはswitch
文を使用して移動の結果を考慮し、その移動が許可されるかどうかを判断します。
- サイコロの目がプレイヤーを最終マスに移動させる場合、ゲームは終了です。
break gameLoop
文は、while
ループの外側の最初の行に制御を転送し、ゲームを終了します。 - サイコロの目がプレイヤーを最終マスを超えて移動させる場合、その移動は無効であり、プレイヤーは再度サイコロを振る必要があります。
continue gameLoop
文は現在のwhile
ループの反復を終了し、ループの次の反復を開始します。 - その他のすべての場合、サイコロの目は有効な移動です。プレイヤーは
diceRoll
マス進み、ゲームロジックはヘビやはしごを確認します。ループは終了し、制御は次のターンが必要かどうかを決定するためにwhile
条件に戻ります。
注:上記の
break
文がgameLoop
ラベルを使用していなかった場合、switch
文を終了し、while
文を終了しません。gameLoop
ラベルを使用することで、どの制御文を終了するかが明確になります。
continue gameLoop
を呼び出す際にgameLoop
ラベルを使用する必要はありません。ゲームには1つのループしかなく、continue
文がどのループに影響するかに曖昧さはありません。ただし、continue
文とともにgameLoop
ラベルを使用しても問題ありません。これにより、break
文と一貫性が保たれ、ゲームのロジックが読みやすく理解しやすくなります。
早期終了
guard
文は、if
文のように、式のブール値に基づいてステートメントを実行します。guard
文を使用して、条件がtrueであることを要求し、guard
文の後のコードが実行されるようにします。if
文とは異なり、guard
文には常にelse
句があります。条件がtrueでない場合、else
句内のコードが実行されます。
func greet(person: [String: String]) {
guard let name = person["name"] else {
return
}
print("こんにちは、\(name)さん!")
guard let location = person["location"] else {
print("あなたの近くの天気が良いことを願っています。")
return
}
print("あなたの近くの天気が良いことを願っています。")
}
greet(person: ["name": "John"])
// "こんにちは、Johnさん!"と出力されます。
// "あなたの近くの天気が良いことを願っています。"と出力されます。
greet(person: ["name": "Jane", "location": "Cupertino"])
// "こんにちは、Janeさん!"と出力されます。
// "Cupertinoの天気が良いことを願っています。"と出力されます。
guard
文の条件が満たされると、コードの実行はguard
文の閉じ括弧の後に続きます。オプションバインディングを使用して条件の一部として値が割り当てられた変数や定数は、guard
文が表示されるコードブロックの残りの部分で使用できます。
その条件が満たされない場合、else
ブランチ内のコードが実行されます。そのブランチは、guard
文が表示されるコードブロックを終了するために制御を転送する必要があります。これを行うには、return
、break
、continue
、またはthrow
などの制御転送文を使用するか、fatalError(_:file:line:)
のような戻らない関数やメソッドを呼び出すことができます。
guard
文を使用して要件を満たすことで、同じチェックをif
文で行う場合と比較してコードの可読性が向上します。通常実行されるコードをelse
ブロックにラップすることなく記述でき、要件が満たされなかった場合のコードを要件の近くに配置できます。
遅延アクション
if
やwhile
のような制御フロー構造とは異なり、コードが実行されるかどうかや何回実行されるかを制御するのではなく、defer
はコードが実行されるタイミングを制御します。defer
ブロックを使用して、プログラムが現在のスコープの終わりに達したときに実行されるコードを記述します。たとえば:
var score = 1
if score < 10 {
defer {
print(score)
}
score += 5
}
// "6"と出力されます。
上記の例では、defer
ブロック内のコードはif
文の本体を終了する前に実行されます。最初にif
文のコードが実行され、score
が5増加します。その後、if
文のスコープを終了する前に、遅延されたコードが実行され、score
が出力されます。
defer
内のコードは、プログラムがそのスコープを終了する方法に関係なく常に実行されます。これには、関数からの早期終了、for
ループの中断、エラーのスローなどのコードが含まれます。この動作により、defer
はメモリの手動割り当てと解放、低レベルのファイルディスクリプタのオープンとクローズ、データベースのトランザクションの開始と終了など、ペアのアクションが発生する必要がある操作に役立ちます。たとえば、次のコードは、コードのチャンク内で100を追加および減算することで、一時的なボーナスをスコアに与えます。
var score = 3
if score < 100 {
score += 100
defer {
score -= 100
}
// ボーナスを使用する他のコードがここにあります。
print(score)
}
// "103"と出力されます。
同じスコープで複数のdefer
ブロックを記述する場合、最初に指定したものが最後に実行されます。
if score < 10 {
defer {
print(score)
}
defer {
print("スコアは:")
}
score += 5
}
// "スコアは:"と出力されます。
// "6"と出力されます。
プログラムが停止した場合(たとえば、ランタイムエラーやクラッシュが発生した場合)、遅延されたコードは実行されません。ただし、エラーがスローされた後に遅延されたコードは実行されます。エラー処理でdefer
を使用する方法については、クリーンアップアクションの指定を参照してください。
APIの可用性の確認
SwiftにはAPIの可用性を確認するための組み込みサポートがあり、指定されたデプロイメントターゲットで利用できないAPIを誤って使用しないようにします。
コンパイラはSDKの可用性情報を使用して、コードで使用されているすべてのAPIがプロジェクトで指定されたデプロイメントターゲットで利用可能であることを確認します。利用できないAPIを使用しようとすると、Swiftはコンパイル時にエラーを報告します。
if
またはguard
文で可用性条件を使用して、使用したいAPIが実行時に利用可能かどうかに応じてコードブロックを条件付きで実行します。コンパイラは、可用性条件からの情報を使用して、そのコードブロック内のAPIが利用可能であることを確認します。
if #available(iOS 10, macOS 10.12, *) {
// iOSではiOS 10のAPIを使用し、macOSではmacOS 10.12のAPIを使用します
} else {
// 以前のiOSおよびmacOSのAPIにフォールバックします
}
上記の可用性条件は、iOSではif
文の本体がiOS 10以降でのみ実行され、macOSではmacOS 10.12以降でのみ実行されることを指定します。最後の引数*
は必須であり、他のプラットフォームではif
文の本体がターゲットで指定された最小デプロイメントターゲットで実行されることを指定します。
一般的な形式では、可用性条件はプラットフォーム名とバージョンのリストを取ります。iOS、macOS、watchOS、tvOS、visionOSなどのプラットフォーム名を使用します。完全なリストについては、宣言属性を参照してください。iOS 8やmacOS 10.10のようなメジャーバージョン番号を指定するだけでなく、iOS 11.2.6やmacOS 10.13.3のようなマイナーバージョン番号も指定できます。
if #available(<#platform name#> <#version#>, <#...#>, *) {
<#statements to execute if the APIs are available#>
} else {
<#fallback statements to execute if the APIs are unavailable#>
}
guard
文で可用性条件を使用する場合、それはそのコードブロックの残りの部分で使用される可用性情報を洗練します。
@available(macOS 10.12, *)
struct ColorPreference {
var bestColor = "blue"
}
func chooseBestColor() -> String {
guard #available(macOS 10.12, *) else {
return "gray"
}
let colors = ColorPreference()
return colors.bestColor
}
上記の例では、ColorPreference
構造体はmacOS 10.12以降が必要です。chooseBestColor()
関数は可用性ガードで始まります。プラットフォームのバージョンがColorPreference
を使用するには古すぎる場合、常に利用可能な動作にフォールバックします。guard
文の後、macOS 10.12以降が必要なAPIを使用できます。
#available
に加えて、Swiftは逆のチェックもサポートしています。たとえば、次の2つのチェックは同じことを行います。
if #available(iOS 10, *) {
} else {
// フォールバックコード
}
if #unavailable(iOS 10) {
// フォールバックコード
}
#unavailable
形式を使用すると、チェックにフォールバックコードのみが含まれている場合にコードの可読性が向上します。