参考資料

制御フロー

Swiftは様々な制御フロー文を提供しています。これらには、タスクを複数回実行するためのwhileループ、特定の条件に基づいてコードの異なる分岐を実行するためのif、guard、switch文、そしてbreakやcontinueなどの実行フローをコード内の別の場所に移すための文が含まれます。Swiftは、配列、辞書、範囲、文字列、およびその他のシーケンスを簡単に反復処理できるfor-inループを提供します。また、Swiftは現在のスコープを離れる際に実行されるコードをラップするdefer文も提供します。

制御フロー

Swiftは様々な制御フロー文を提供しています。これらには、タスクを複数回実行するためのwhileループ、特定の条件に基づいてコードの異なる分岐を実行するためのifguardswitch文、そしてbreakcontinueなどの実行フローをコード内の別の場所に移すための文が含まれます。Swiftは、配列、辞書、範囲、文字列、およびその他のシーケンスを簡単に反復処理できるfor-inループを提供します。また、Swiftは現在のスコープを離れる際に実行されるコードをラップするdefer文も提供します。

Swiftのswitch文は、多くのC言語系の言語における対応するものよりもかなり強力です。caseは、区間マッチング、タプル、特定の型へのキャストなど、多くの異なるパターンにマッチできます。switchのcaseでマッチした値は、そのcaseの本体内で使用するために一時的な定数または変数にバインドでき、複雑なマッチング条件は各caseの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)タプルとして返され、(key, value)タプルのメンバーをfor-inループの本体内で使用するために明示的に名前付き定数として分解できます。以下のコード例では、辞書のキーは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

Dictionaryの内容は本質的に順序付けされておらず、反復処理しても取得される順序は保証されません。特に、Dictionaryに項目を挿入する順序は、反復処理される順序を定義しません。配列と辞書についての詳細は、コレクション型を参照してください。

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".

上記の例では、ある数値の別の数値に対する累乗の値を計算しています(この場合、310乗)。開始値1(つまり、30乗)に3を10回掛けます。1で始まり10で終わる閉区間を使用しています。この計算では、ループを通過するたびに個々のカウンター値は不要です — コードは単純に正しい回数だけループを実行します。ループ変数の代わりに使用されるアンダースコア文字(_)により、個々の値は無視され、ループの各反復中に現在の値へのアクセスは提供されません。

状況によっては、両端点を含む閉区間を使用したくない場合があります。時計の文字盤に毎分の目盛りを描画することを考えてください。0分から始めて60個の目盛りを描画したいとします。半開区間演算子(..<)を使用して下限を含み、上限を含まないようにします。範囲についての詳細は、範囲演算子を参照してください。

let minutes = 60
for tickMark in 0..<minutes {
    // render the tick mark each minute (60 times)
}

一部のユーザーは、UIでより少ない目盛りを望むかもしれません。5分ごとに1つの目盛りを好むかもしれません。stride(from:to:by:)関数を使用して、不要な目盛りをスキップします。

let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
    // render the tick mark every 5 minutes (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) {
    // render the tick mark every 3 hours (3, 6, 9, 12)
}

上記の例では、for-inループを使用して範囲、配列、辞書、文字列を反復処理しています。ただし、この構文を使用して、独自のクラスやコレクション型を含む任意のコレクションを反復処理できます。それらの型がSequenceプロトコルに準拠している限りです。

Whileループ

whileループは、条件がfalseになるまで一連の文を実行します。この種のループは、最初の反復が始まる前に反復回数がわからない場合に最適です。Swiftは2種類のwhileループを提供しています:

  • whileは、ループの各パスの開始時に条件を評価します。
  • repeat-whileは、ループの各パスの終了時に条件を評価します。

While

whileループは、単一の条件を評価することから始まります。条件がtrueの場合、条件がfalseになるまで一連の文が繰り返されます。

whileループの一般的な形式は次のとおりです:

while <#condition#> {
   <#statements#>
}

この例では、シンプルなSnakes and Laddersゲーム(Chutes and Laddersとも呼ばれる)をプレイしています:

snakesAndLadders

ゲームのルールは次のとおりです:

  • ボードには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に等しく、これは整数値8311の差)に相当します。値と文を揃えるために、単項プラス演算子(+i)が単項マイナス演算子(-i)と明示的に一緒に使用され、10未満の数はゼロでパディングされています。(どちらのスタイリングテクニックも厳密には必要ありませんが、より整ったコードになります。)

var square = 0
var diceRoll = 0
while square < finalSquare {
    // roll the dice
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    // move by the rolled amount
    square += diceRoll
    if square < board.count {
        // if we're still on the board, move up or down for a snake or a ladder
        square += board[square]
    }
}
print("Game over!")

上記の例では、サイコロを振るための非常にシンプルなアプローチを使用しています。乱数を生成する代わりに、diceRollの値0から始めます。whileループを通過するたびに、diceRollは1ずつ増加し、大きくなりすぎていないかチェックされます。この戻り値が7に等しくなるたびに、サイコロの目は大きくなりすぎており、1の値にリセットされます。結果は、常に12345612などとなるdiceRoll値のシーケンスです。

サイコロを振った後、プレイヤーはdiceRollマス前進します。サイコロの目によってプレイヤーがマス25を超える可能性があり、その場合ゲームは終了します。このシナリオに対処するため、コードはsquareboard配列のcountプロパティより小さいことをチェックします。squareが有効な場合、board[square]に格納されている値が現在のsquare値に加算され、プレイヤーははしごやヘビに沿って上下に移動します。

注意 このチェックが実行されない場合、board[square]board配列の範囲外の値にアクセスしようとする可能性があり、ランタイムエラーが発生します。

現在のwhileループの実行が終了し、ループの条件がチェックされて、ループを再度実行するかどうかが判断されます。プレイヤーがマス番号25以上に移動した場合、ループの条件はfalseと評価され、ゲームは終了します。

この場合、whileループが適切です。なぜなら、whileループの開始時にゲームの長さが明確ではないからです。代わりに、特定の条件が満たされるまでループが実行されます。

Repeat-While

whileループのもう1つのバリエーションは、repeat-whileループと呼ばれ、ループの条件を考慮する前にまずループブロックを1回パスします。その後、条件がfalseになるまでループを繰り返し続けます。

注意 Swiftのrepeat-whileループは、他の言語のdo-whileループに類似しています。

repeat-whileループの一般的な形式は次のとおりです:

repeat {
   <#statements#>
} while <#condition#>

再びSnakes and Laddersの例を示しますが、今回はwhileループではなくrepeat-whileループとして書かれています。finalSquareboardsquarediceRollの値は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 {
    // move up or down for a snake or ladder
    square += board[square]
    // roll the dice
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    // move by the rolled amount
    square += diceRoll
} while square < finalSquare
print("Game over!")

コードがヘビとはしごをチェックした後、サイコロが振られ、プレイヤーは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("It's very cold. Consider wearing a scarf.")
}
// Prints "It's very cold. Consider wearing a scarf."

上記の例では、気温が華氏32度以下(水の氷点)かどうかをチェックしています。そうであれば、メッセージが出力されます。そうでなければ、メッセージは出力されず、コードの実行はif文の閉じ波括弧の後から続行されます。

if文は、if条件がfalseの場合のために、else句として知られる代替の文セットを提供できます。これらの文はelseキーワードによって示されます。

temperatureInFahrenheit = 40
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
} else {
    print("It's not that cold. Wear a T-shirt.")
}
// Prints "It's not that cold. Wear a T-shirt."

これら2つの分岐のうち1つが常に実行されます。気温が華氏40度に上昇したため、マフラーを着用するようにアドバイスするほど寒くなくなり、代わりにelse分岐がトリガーされます。

複数のif文を連鎖させて、追加の句を考慮できます。

temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
    print("It's really warm. Don't forget to wear sunscreen.")
} else {
    print("It's not that cold. Wear a T-shirt.")
}
// Prints "It's really warm. Don't forget to wear sunscreen."

ここでは、特に暖かい気温に対応するために追加のif文が追加されました。最後のelse句は残っており、暖かすぎも寒すぎもしない気温に対する応答を出力します。

ただし、最後のelse句はオプションであり、条件のセットが完全である必要がない場合は除外できます。

temperatureInFahrenheit = 72
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
    print("It's really warm. Don't forget to wear sunscreen.")
}

気温がif条件をトリガーするほど寒くなく、else if条件をトリガーするほど暖かくないため、メッセージは出力されません。

Swiftは、値を設定する際に使用できるifの省略形を提供しています。例えば、次のコードを考えてみましょう:

let temperatureInCelsius = 25
let weatherAdvice: String

if temperatureInCelsius <= 0 {
    weatherAdvice = "It's very cold. Consider wearing a scarf."
} else if temperatureInCelsius >= 30 {
    weatherAdvice = "It's really warm. Don't forget to wear sunscreen."
} else {
    weatherAdvice = "It's not that cold. Wear a T-shirt."
}

print(weatherAdvice)
// Prints "It's not that cold. Wear a T-shirt."

ここでは、各分岐がweatherAdvice定数に値を設定し、それがif文の後に出力されます。

if式として知られる代替構文を使用すると、このコードをより簡潔に書くことができます:

let weatherAdvice = if temperatureInCelsius <= 0 {
    "It's very cold. Consider wearing a scarf."
} else if temperatureInCelsius >= 30 {
    "It's really warm. Don't forget to wear sunscreen."
} else {
    "It's not that cold. Wear a T-shirt."
}

print(weatherAdvice)
// Prints "It's not that cold. Wear a T-shirt."

このif式バージョンでは、各分岐には単一の値が含まれています。分岐の条件がtrueの場合、その分岐の値がweatherAdviceの代入におけるif式全体の値として使用されます。すべてのif分岐には対応するelse if分岐またはelse分岐があり、分岐の1つが常にマッチし、どの条件がtrueであってもif式が常に値を生成することが保証されます。

代入の構文はif式の外側から始まるため、各分岐内でweatherAdvice =を繰り返す必要はありません。代わりに、if式の各分岐がweatherAdviceの3つの可能な値のうちの1つを生成し、代入がその値を使用します。

if式のすべての分岐は、同じ型の値を含む必要があります。Swiftは各分岐の型を個別にチェックするため、複数の型で使用できるnilのような値は、Swiftがif式の型を自動的に判定することを妨げます。代わりに、型を明示的に指定する必要があります — 例えば:

let freezeWarning: String? = if temperatureInCelsius <= 0 {
    "It's below freezing. Watch for ice!"
} else {
    nil
}

上記のコードでは、if式の1つの分岐は文字列値を持ち、もう1つの分岐はnil値を持っています。nil値は任意のオプショナル型の値として使用できるため、型アノテーションで説明されているように、freezeWarningがオプショナル文字列であることを明示的に記述する必要があります。

この型情報を提供する別の方法は、freezeWarningに明示的な型を提供する代わりに、nilに明示的な型を提供することです:

let freezeWarning = if temperatureInCelsius <= 0 {
    "It's below freezing. Watch for ice!"
} else {
    nil as String?
}

if式は、エラーをスローしたり、決して戻らないfatalError(_:file:line:)のような関数を呼び出したりすることで、予期しない失敗に対応できます。例えば:

let weatherAdvice = if temperatureInCelsius > 100 {
    throw TemperatureError.boiling
} else {
    "It's a reasonable temperature."
}

この例では、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文の本体と同様に、各caseはコード実行の別々の分岐です。switch文は、どの分岐を選択するかを決定します。この手順は、考慮されている値に対するスイッチングと呼ばれます。

すべてのswitch文は網羅的でなければなりません。つまり、考慮されている型のすべての可能な値が、switchケースの1つによってマッチされなければなりません。すべての可能な値に対してケースを提供することが適切でない場合は、明示的に対処されていない値をカバーするデフォルトケースを定義できます。このデフォルトケースはdefaultキーワードによって示され、常に最後に表示される必要があります。

この例では、switch文を使用してsomeCharacterという単一の小文字を考慮しています:

let someCharacter: Character = "z"
switch someCharacter {
case "a":
    print("The first letter of the Latin alphabet")
case "z":
    print("The last letter of the Latin alphabet")
default:
    print("Some other character")
}
// Prints "The last letter of the Latin alphabet".

switch文の最初のケースは英語アルファベットの最初の文字aにマッチし、2番目のケースは最後の文字zにマッチします。switchはすべてのアルファベット文字だけでなく、すべての可能な文字に対してケースを持つ必要があるため、このswitch文はaz以外のすべての文字にマッチするためにdefaultケースを使用します。この規定により、switch文が網羅的であることが保証されます。

if文と同様に、switch文にも式形式があります:

let anotherCharacter: Character = "a"
let message = switch anotherCharacter {
case "a":
    "The first letter of the Latin alphabet"
case "z":
    "The last letter of the Latin alphabet"
default:
    "Some other character"
}

print(message)
// Prints "The first letter of the Latin alphabet".

この例では、switch式の各ケースには、そのケースがanotherCharacterにマッチしたときにmessageに使用される値が含まれています。switchは常に網羅的であるため、常に代入する値があります。

if式と同様に、特定のケースに値を提供する代わりに、エラーをスローしたり、決して戻らないfatalError(_:file:line:)のような関数を呼び出したりできます。上記の例で示したように、代入の右辺でswitch式を使用でき、関数やクロージャが返す値としても使用できます。

暗黙のフォールスルーなし

CおよびObjective-Cのswitch文とは対照的に、Swiftのswitch文は各ケースの下部を通過して次のケースにフォールスルーしません。代わりに、明示的なbreak文を必要とせず、最初のマッチングswitchケースが完了するとすぐにswitch文全体の実行が終了します。これにより、switch文はCのものよりも安全で使いやすくなり、誤って複数のswitchケースを実行することを防ぎます。

注意 Swiftではbreakは必須ではありませんが、特定のケースにマッチして無視したり、マッチしたケースの実行が完了する前に抜け出したりするためにbreak文を使用できます。詳細については、Switch文内のBreakを参照してください。

各ケースの本体は少なくとも1つの実行可能な文を含む必要があります。最初のケースが空であるため、次のコードを書くことは無効です:

let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // Invalid, the case has an empty body
case "A":
    print("The letter A")
default:
    print("Not the letter A")
}
// This will report a compile-time error.

Cのswitch文とは異なり、このswitch文は"a""A"の両方にマッチしません。むしろ、case "a":が実行可能な文を含んでいないというコンパイル時エラーを報告します。このアプローチは、あるケースから別のケースへの偶発的なフォールスルーを回避し、意図がより明確なより安全なコードを生み出します。

"a""A"の両方にマッチする単一のケースを持つswitchを作成するには、2つの値を複合ケースにまとめ、値をカンマで区切ります。

let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
    print("The letter A")
default:
    print("Not the letter A")
}
// Prints "The letter A".

読みやすさのために、複合ケースは複数行にわたって書くこともできます。複合ケースについての詳細は、複合ケースを参照してください。

注意 特定のswitchケースの終わりで明示的にフォールスルーするには、Fallthroughで説明されているようにfallthroughキーワードを使用します。

区間マッチング

switchケースの値は、区間への包含をチェックできます。この例では、任意のサイズの数値に対する自然言語のカウントを提供するために数値区間を使用しています:

let approximateCount = 62
let countedThings = "moons orbiting Saturn"
let naturalCount: String
switch approximateCount {
case 0:
    naturalCount = "no"
case 1..<5:
    naturalCount = "a few"
case 5..<12:
    naturalCount = "several"
case 12..<100:
    naturalCount = "dozens of"
case 100..<1000:
    naturalCount = "hundreds of"
default:
    naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")
// Prints "There are dozens of moons orbiting Saturn."

上記の例では、approximateCountswitch文で評価されています。各caseはその値を数値または区間と比較します。approximateCountの値が12から100の間にあるため、naturalCountには値"dozens of"が割り当てられ、実行はswitch文から移されます。

タプル

同じswitch文で複数の値をテストするためにタプルを使用できます。タプルの各要素は、異なる値または値の区間に対してテストできます。または、ワイルドカードパターンとしても知られるアンダースコア文字(_)を使用して、任意の可能な値にマッチさせることもできます。

以下の例では、(Int, Int)型の単純なタプルとして表される(x, y)点を取り、例に続くグラフ上で分類しています。

let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("\(somePoint) is at the origin")
case (_, 0):
    print("\(somePoint) is on the x-axis")
case (0, _):
    print("\(somePoint) is on the y-axis")
case (-2...2, -2...2):
    print("\(somePoint) is inside the box")
default:
    print("\(somePoint) is outside of the box")
}
// Prints "(1, 1) is inside the box".

coordinateGraphSimple

switch文は、点が原点(0, 0)にあるか、赤いx軸上にあるか、緑のy軸上にあるか、原点を中心とした青い4×4のボックス内にあるか、ボックスの外にあるかを判定します。

Cとは異なり、Swiftでは複数のswitchケースが同じ値または複数の値を考慮できます。実際、点(0, 0)はこの例の4つすべてのケースにマッチする可能性があります。ただし、複数のマッチが可能な場合、最初のマッチングケースが常に使用されます。点(0, 0)は最初にcase (0, 0)にマッチするため、他のすべてのマッチングケースは無視されます。

値バインディング

switchケースは、ケースの本体内で使用するために、マッチする値または複数の値を一時的な定数または変数に名前付けできます。この動作は値バインディングと呼ばれ、値がケースの本体内の一時的な定数または変数にバインドされるためです。

以下の例では、(Int, Int)型のタプルとして表される(x, y)点を取り、続くグラフ上で分類しています:

let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
    print("on the x-axis with an x value of \(x)")
case (0, let y):
    print("on the y-axis with a y value of \(y)")
case let (x, y):
    print("somewhere else at (\(x), \(y))")
}
// Prints "on the x-axis with an x value of 2".

coordinateGraphMedium

switch文は、点が赤いx軸上にあるか、緑のy軸上にあるか、それ以外の場所(どちらの軸上にもない)にあるかを判定します。

3つのswitchケースは、プレースホルダー定数xyを宣言し、anotherPointから1つまたは両方のタプル値を一時的に取得します。最初のケース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)) is on the line x == y")
case let (x, y) where x == -y:
    print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
    print("(\(x), \(y)) is just some arbitrary point")
}
// Prints "(1, -1) is on the line x == -y".

coordinateGraphComplex

switch文は、点がx == yの緑の対角線上にあるか、x == -yの紫の対角線上にあるか、どちらでもないかを判定します。

3つのswitchケースは、yetAnotherPointから2つのタプル値を一時的に取得するプレースホルダー定数xyを宣言しています。これらの定数は、動的フィルターを作成するためにwhere句の一部として使用されます。switchケースは、where句の条件がその値に対してtrueと評価された場合にのみ、pointの現在の値にマッチします。

前の例と同様に、最後のケースはすべての可能な残りの値にマッチするため、switch文を網羅的にするためにdefaultケースは必要ありません。

複合ケース

同じ本体を共有する複数のswitchケースは、caseの後に複数のパターンを書き、各パターンの間にカンマを入れることで組み合わせることができます。パターンのいずれかがマッチした場合、そのケースはマッチしたと見なされます。リストが長い場合、パターンは複数行にわたって書くことができます。例えば:

let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
    print("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
    "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
    print("\(someCharacter) is a consonant")
default:
    print("\(someCharacter) isn't a vowel or a consonant")
}
// Prints "e is a vowel".

switch文の最初のケースは、英語の5つの小文字の母音すべてにマッチします。同様に、2番目のケースはすべての小文字の英語の子音にマッチします。最後に、defaultケースはその他の文字にマッチします。

複合ケースには値バインディングを含めることもできます。複合ケースのすべてのパターンは、同じ値バインディングのセットを含む必要があり、各バインディングは複合ケースのすべてのパターンから同じ型の値を取得する必要があります。これにより、複合ケースのどの部分がマッチしても、ケースの本体のコードは常にバインディングの値にアクセスでき、その値は常に同じ型であることが保証されます。

let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
    print("On an axis, \(distance) from the origin")
default:
    print("Not on an axis")
}
// Prints "On an axis, 9 from the origin".

上記のcaseには2つのパターンがあります:(let distance, 0)はx軸上の点にマッチし、(0, let distance)はy軸上の点にマッチします。両方のパターンはdistanceへのバインディングを含み、distanceは両方のパターンで整数です — これは、caseの本体のコードが常にdistanceの値にアクセスできることを意味します。

パターン

前の例では、各switchケースには、そのケースにマッチする値を示すパターンが含まれています。パターンはif文の条件としても使用できます。その様子を見てみましょう:

let somePoint = (12, 100)
if case (let x, 100) = somePoint {
    print("Found a point on the y=100 line, at \(x)")
}
// Prints "Found a point on the y=100 line, at 12".

このコードでは、if文の条件はcaseで始まり、条件がブール値ではなくパターンであることを示しています。パターンがマッチした場合、ifの条件はtrueと見なされ、if文の本体のコードが実行されます。if caseの後に書けるパターンは、switchケースで書けるパターンと同じです。

for-inループでは、コードにcaseを書かなくても、値バインディングパターンを使用して値の部分に名前を付けることができます:

let points = [(10, 0), (30, -30), (-20, 0)]

for (x, y) in points {
    if y == 0 {
        print("Found a point on the x-axis at \(x)")
    }
}
// Prints "Found a point on the x-axis at 10".
// Prints "Found a point on the x-axis at -20".

上記のfor-inループは、タプルの配列を反復処理し、タプルの1番目と2番目の要素をxy定数にバインドしています。ループ内の文はこれらの定数を使用でき、例えば点がx軸上にあるかどうかをチェックするif文などです。このコードをより簡潔に書く方法は、for-case-inループを使用して値バインディングと条件を組み合わせることです。以下のコードは上記のfor-inループと同じ動作をします:

for case (let x, 0) in points {
    print("Found a point on the x-axis at \(x)")
}
// Prints "Found a point on the x-axis at 10".
// Prints "Found a point on the x-axis at -20".

このコードでは、条件がパターンの一部としてfor-case-inループに統合されています。for-case-inループ内の文は、x軸上の点に対してのみ実行されます。このコードは上記のfor-inループと同じ結果を生成しますが、コレクション内の特定の要素のみを反復処理するよりコンパクトな方法です。

for-case-inループには、追加の条件をチェックするためのwhere句を含めることもできます。ループ内の文は、where句が現在の要素にマッチした場合にのみ実行されます。例えば:

for case let (x, y) in points where x == y || x == -y  {
    print("Found (\(x), \(y)) along a line through the origin")
}
// Prints "Found (30, -30) along a line through the origin".

このコードは、タプルの1番目と2番目の要素を定数としてxyにバインドし、次にwhere句でその値をチェックします。where句がtrueの場合、forループの本体のコードが実行されます。そうでなければ、次の要素で反復が続きます。

パターンは値をバインドできるため、if-case文とfor-case-inループは、関連値で説明されているように、関連値を持つ列挙型を扱う際に便利です。

制御転送文

制御転送文は、コードのある部分から別の部分に制御を転送することで、コードが実行される順序を変更します。Swiftには5つの制御転送文があります:

  • continue
  • break
  • fallthrough
  • return
  • throw

continuebreakfallthrough文については以下で説明します。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)
// Prints "grtmndsthnklk".

上記のコードは、母音またはスペースにマッチするたびにcontinueキーワードを呼び出し、ループの現在の反復を即座に終了し、次の反復の開始に直接ジャンプします。

Break

break文は、制御フロー文全体の実行を即座に終了します。break文は、switchまたはループ文の実行を、そうでなければそうなるよりも早く終了したい場合に使用できます。

ループ文内のBreak

ループ文内で使用される場合、breakはループの実行を即座に終了し、ループの閉じ波括弧(})の後のコードに制御を転送します。ループの現在の反復からのそれ以上のコードは実行されず、ループのそれ以上の反復は開始されません。

Switch文内のBreak

switch文内で使用される場合、breakswitch文の実行を即座に終了させ、switch文の閉じ波括弧(})の後のコードに制御を転送します。

この動作は、switch文で1つ以上のケースをマッチさせて無視するために使用できます。Swiftのswitch文は網羅的であり、空のケースを許可しないため、意図を明確にするためにケースを意図的にマッチさせて無視することが必要な場合があります。これを行うには、無視したいケースの本体全体としてbreak文を書きます。そのケースがswitch文によってマッチされると、ケース内のbreak文がswitch文の実行を即座に終了します。

注意 コメントのみを含むswitchケースは、コンパイル時エラーとして報告されます。コメントは文ではなく、switchケースを無視させることはありません。switchケースを無視するには常にbreak文を使用してください。

次の例では、Character値に対してスイッチし、4つの言語のいずれかで数字記号を表しているかどうかを判定しています。簡潔さのために、複数の値が1つのswitchケースでカバーされています。

let numberSymbol: Character = "三"  // Chinese symbol for the number 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("The integer value of \(numberSymbol) is \(integerValue).")
} else {
    print("An integer value couldn't be found for \(numberSymbol).")
}
// Prints "The integer value of 三 is 3."

この例では、numberSymbol1から4の数字のラテン、アラビア語、中国語、またはタイ語の記号であるかどうかをチェックしています。マッチが見つかった場合、switch文のケースの1つがpossibleIntegerValueというオプショナルのInt?変数に適切な整数値を設定します。

switch文の実行が完了した後、この例ではオプショナルバインディングを使用して値が見つかったかどうかを判定します。possibleIntegerValue変数はオプショナル型であるため、暗黙的にnilの初期値を持ち、オプショナルバインディングはpossibleIntegerValueswitch文の最初の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 = "The number \(integerToDescribe) is"
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
    description += " a prime number, and also"
    fallthrough
default:
    description += " an integer."
}
print(description)
// Prints "The number 5 is a prime number, and also an integer."

この例では、descriptionという新しいString変数を宣言し、初期値を割り当てています。次に関数はswitch文を使用してintegerToDescribeの値を考慮します。integerToDescribeの値がリスト内の素数のいずれかである場合、関数はdescriptionの末尾にテキストを追加し、その数が素数であることを示します。次にfallthroughキーワードを使用してdefaultケースにも「フォールイン」します。defaultケースは説明の末尾にいくつかの追加テキストを追加し、switch文は完了です。

integerToDescribeの値が既知の素数のリストにない限り、最初のswitchケースによってはまったくマッチしません。他に特定のケースがないため、integerToDescribedefaultケースによってマッチされます。

switch文の実行が終了した後、print(_:separator:terminator:)関数を使用して数値の説明が出力されます。この例では、数値5は正しく素数として識別されています。

注意 fallthroughキーワードは、実行がフォールインするswitchケースのケース条件をチェックしません。fallthroughキーワードは、Cの標準的なswitch文の動作のように、コード実行を直接次のケース(またはdefaultケース)ブロック内の文に移動させるだけです。

ラベル付き文

Swiftでは、ループと条件文を他のループと条件文の内部にネストして、複雑な制御フロー構造を作成できます。ただし、ループと条件文は両方とも、実行を早期に終了するためにbreak文を使用できます。したがって、break文がどのループまたは条件文を終了させたいかを明示することが役立つ場合があります。同様に、複数のネストされたループがある場合、continue文がどのループに影響するかを明示することが役立つ場合があります。

これらの目的を達成するために、ループ文または条件文に文ラベルをマークできます。条件文では、ラベル付きの文の実行を終了するために文ラベルとbreak文を使用できます。ループ文では、ラベル付きの文の実行を終了または続行するために文ラベルとbreakまたはcontinue文を使用できます。

ラベル付き文は、文のイントロデューサーキーワードと同じ行にラベルを配置し、その後にコロンを続けることで示されます。whileループのこの構文の例を示しますが、原則はすべてのループとswitch文で同じです:

<#label name#>: while <#condition#> {
   <#statements#>
}

次の例では、この章で前に見たSnakes and Laddersゲームの適応版に対して、ラベル付きのwhileループでbreak文とcontinue文を使用しています。今回は、ゲームに追加のルールがあります:

  • 勝つには、マス25に正確に着地する必要があります。

特定のサイコロの目がマス25を超える場合は、マス25に正確に着地するために必要な数を出すまで再度振る必要があります。

ゲームボードは以前と同じです。

snakesAndLadders

finalSquareboardsquarediceRollの値は以前と同じ方法で初期化されます:

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という文ラベルがあり、Snakes and Laddersゲームのメインゲームループであることを示しています。

whileループの条件はwhile square != finalSquareであり、マス25に正確に着地する必要があることを反映しています。

gameLoop: while square != finalSquare {
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    switch square + diceRoll {
    case finalSquare:
        // diceRoll will move us to the final square, so the game is over
        break gameLoop
    case let newSquare where newSquare > finalSquare:
        // diceRoll will move us beyond the final square, so roll again
        continue gameLoop
    default:
        // this is a valid move, so find out its effect
        square += diceRoll
        square += board[square]
    }
}
print("Game over!")

サイコロは各ループの開始時に振られます。プレイヤーをすぐに移動させる代わりに、ループはswitch文を使用して移動の結果を考慮し、移動が許可されているかどうかを判断します:

  • サイコロの目がプレイヤーを最終マスに移動させる場合、ゲームは終了です。break gameLoop文は、whileループの外側のコードの最初の行に制御を転送し、ゲームを終了します。
  • サイコロの目がプレイヤーを最終マスを超えて移動させる場合、その移動は無効であり、プレイヤーは再度振る必要があります。continue gameLoop文は現在のwhileループの反復を終了し、ループの次の反復を開始します。
  • その他のすべての場合、サイコロの目は有効な移動です。プレイヤーはdiceRollマス前進し、ゲームロジックはヘビとはしごをチェックします。ループが終了し、制御はwhile条件に戻り、別のターンが必要かどうかを決定します。

注意 上記のbreak文がgameLoopラベルを使用しなかった場合、while文ではなくswitch文から抜け出していたでしょう。gameLoopラベルを使用することで、どの制御文を終了すべきかが明確になります。

ループの次の反復にジャンプするためにcontinue gameLoopを呼び出すときにgameLoopラベルを使用することは厳密には必要ありません。ゲームにはループが1つしかなく、したがってcontinue文がどのループに影響するかについてあいまいさはありません。ただし、continue文と一緒にgameLoopラベルを使用しても害はありません。そうすることで、break文と一緒にラベルを使用することと一貫しており、ゲームのロジックをより明確に読んで理解するのに役立ちます。

早期脱出

guard文は、if文と同様に、式のブール値に応じて文を実行します。guard文の後のコードが実行されるためには、条件がtrueである必要があります。if文とは異なり、guard文には常にelse句があります — 条件がtrueでない場合、else句内のコードが実行されます。

func greet(person: [String: String]) {
    guard let name = person["name"] else {
        return
    }

    print("Hello \(name)!")

    guard let location = person["location"] else {
        print("I hope the weather is nice near you.")
        return
    }

    print("I hope the weather is nice in \(location).")
}

greet(person: ["name": "John"])
// Prints "Hello John!"
// Prints "I hope the weather is nice near you."
greet(person: ["name": "Jane", "location": "Cupertino"])
// Prints "Hello Jane!"
// Prints "I hope the weather is nice in Cupertino."

guard文の条件が満たされた場合、コードの実行はguard文の閉じ波括弧の後から続行されます。条件の一部としてオプショナルバインディングを使用して値が割り当てられた変数または定数は、guard文が表示されるコードブロックの残りの部分で使用できます。

その条件が満たされない場合、else分岐内のコードが実行されます。その分岐は、guard文が表示されるコードブロックを終了するために制御を転送する必要があります。これは、returnbreakcontinuethrowなどの制御転送文で行うことができます。または、fatalError(_:file:line:)のような戻らない関数やメソッドを呼び出すこともできます。

要件にguard文を使用すると、同じチェックをif文で行う場合と比較して、コードの読みやすさが向上します。これにより、通常実行されるコードをelseブロックでラップせずに書くことができ、違反した要件を処理するコードを要件の隣に保持できます。

遅延アクション

ifwhileのような制御フロー構造がコードの一部を実行するかどうかや何回実行するかを制御するのとは異なり、deferはコードの一部がいつ実行されるかを制御します。deferブロックを使用して、プログラムが現在のスコープの終わりに達したときに後で実行されるコードを書きます。例えば:

var score = 1
if score < 10 {
    defer {
        print(score)
    }
    score += 5
}
// Prints "6".

上記の例では、deferブロック内のコードはif文の本体を終了する前に実行されます。まず、if文のコードが実行され、scoreが5増加します。次に、if文のスコープを終了する前に、遅延コードが実行され、scoreが出力されます。

defer内のコードは、プログラムがそのスコープをどのように終了しても常に実行されます。これには、関数からの早期脱出、forループからの脱出、エラーのスローなどのコードが含まれます。この動作により、deferは、手動でメモリを割り当てて解放する、低レベルのファイルディスクリプタを開いて閉じる、データベースでトランザクションを開始して終了するなど、一対のアクションが発生することを保証する必要がある操作に役立ちます。なぜなら、両方のアクションをコード内で隣接して書くことができるからです。例えば、次のコードは、コードのチャンク内で100を加算および減算することにより、スコアに一時的なボーナスを与えます:

var score = 3
if score < 100 {
    score += 100
    defer {
        score -= 100
    }
    // Other code that uses the score with its bonus goes here.
    print(score)
}
// Prints "103".

同じスコープ内で複数のdeferブロックを書く場合、最初に指定したものが最後に実行されます。

if score < 10 {
    defer {
        print(score)
    }
    defer {
        print("The score is:")
    }
    score += 5
}
// Prints "The score is:".
// Prints "6".

プログラムの実行が停止した場合 — 例えば、ランタイムエラーやクラッシュにより — 遅延コードは実行されません。ただし、遅延コードはエラーがスローされた後に実行されます。エラー処理でdeferを使用する方法については、クリーンアップアクションの指定を参照してください。

API可用性のチェック

Swiftには、API可用性をチェックするための組み込みサポートがあり、特定のデプロイメントターゲットで使用できないAPIを誤って使用しないようにします。

コンパイラはSDKの可用性情報を使用して、コードで使用されるすべてのAPIがプロジェクトで指定されたデプロイメントターゲットで使用可能であることを確認します。Swiftは、使用できないAPIを使用しようとすると、コンパイル時にエラーを報告します。

ifまたはguard文で可用性条件を使用して、使用したいAPIが実行時に使用可能かどうかに応じて、コードブロックを条件付きで実行します。コンパイラは、そのコードブロック内のAPIが使用可能であることを確認するときに、可用性条件からの情報を使用します。

if #available(iOS 10, macOS 10.12, *) {
    // Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS
} else {
    // Fall back to earlier iOS and macOS APIs
}

上記の可用性条件は、iOSではif文の本体がiOS 10以降でのみ実行されることを指定しています。macOSではmacOS 10.12以降でのみ実行されます。最後の引数*は必須であり、その他のプラットフォームでは、ifの本体がターゲットで指定された最小デプロイメントターゲットで実行されることを指定します。

一般的な形式では、可用性条件はプラットフォーム名とバージョンのリストを取ります。iOSmacOSwatchOStvOSvisionOSなどのプラットフォーム名を使用します — 完全なリストについては、宣言属性を参照してください。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 {
    // Fallback code
}

if #unavailable(iOS 10) {
    // Fallback code
}

#unavailable形式を使用すると、チェックにフォールバックコードのみが含まれている場合に、コードをより読みやすくすることができます。