Swift 言語ガイド – 5. 制御フロー

Swift

はじめに

公式ページのSwift 言語ガイド「The Swift Programming Language Swift 5.3」に基づき記載いたします。本ページでは、「制御フロー」について記載いたします。
記載内容に誤り等ございましたら、ご連絡をいただければ幸いです。

5. 制御フロー

Swiftは、さまざまな制御フローステートメントを提供します。これには、タスクを複数回実行するためのwhileループが含まれます。if、guard、およびswitchステートメントを使用して、特定の条件に基づいてコードのさまざまなブランチを実行します。また、breakやcontinueなどのステートメントは、実行フローをコード内の別のポイントに転送し続けます。

Swiftは、配列、辞書、範囲、文字列、およびその他のシーケンスを簡単に反復できるfor-inループも提供します。

Swiftのswitchステートメントは、多くのCのような言語の対応するステートメントよりもかなり強力です。caseは、間隔の一致、タプル、特定の型へのキャストなど、さまざまなパターンに一致する可能性があります。switch caseで一致した値は、caseの本体内で使用するために一時的な定数または変数にバインドでき、複雑な一致条件は、各caseのwhere句で表すことができます。

5.1. 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つだけ含まれ、インデックスの現在の値の5回の表からエントリを出力します。ステートメントが実行された後、インデックスの値が更新されて範囲(2)の2番目の値が含まれ、print(_:separator:terminator:)関数が再度呼び出されます。このプロセスは、範囲の終わりに達するまで続きます。

上記の例では、indexは定数であり、その値はループの各反復の開始時に自動的に設定されます。そのため、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)")
// "3 to the power of 10 is 59049"を出力します

上記の例では、ある数値の値を別の数値の累乗(この場合、3の10の累乗)で計算します。1で始まり10で終わる閉範囲を使用して、開始値1(つまり、3の0乗)に3を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)
}

5.2. Whileループ

whileループは、条件がfalseになるまで一連のステートメントを実行します。これらの種類のループは、最初の反復が始まる前に反復回数がわからない場合に最適に使用されます。Swiftは、次の2種類のwhileループを提供します。

  • whileは、ループを通過する各パスの開始時にその状態を評価します。
  • repeat-whileは、ループを通過する各パスの終了時にその状態を評価します。

5.2.1. While

whileループは、単一の条件を評価することから始まります。条件が真の場合、条件が偽になるまで一連のステートメントが繰り返されます。

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

while condition {
    statements
}

この例では、蛇と梯子(シュートと梯子とも呼ばれます)の簡単なゲームをプレイします。

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

  • ボードには25の正方形があり、目的は正方形25以上に着陸することです。
  • プレーヤーの開始正方形は「正方形ゼロ」で、ボードの左下隅のすぐそばにあります。
  • 毎ターン、6面のサイコロを振って、上の点線の矢印で示された水平パスに従って、そのマスの数だけ移動します。
  • あなたのターンが梯子の一番下で終わる場合、あなたはその梯子を上に移動します。
  • あなたのターンがヘビの頭で終わる場合、あなたはそのヘビを下に移動します。

ゲームボードは、Int値の配列で表されます。そのサイズは、finalSquareと呼ばれる定数に基づいています。この定数は、配列を初期化し、例の後半で勝ち条件をチェックするためにも使用されます。プレーヤーはボードから開始するため、「スクエアゼロ」では、ボードは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に等しく、整数値8(3と11の差)に相当します。値とステートメントを揃えるには、単項プラス演算子(+i)を単項マイナス演算子(-i)とともに明示的に使用し、10未満の数値にはゼロを埋め込みます。(どちらのスタイル手法も厳密には必要ありませんが、コードがすっきりします。)

var square = 0
var diceRoll = 0
while square < finalSquare {
    // roll the dice
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    // サイコロを振った量だけ移動します
    square += diceRoll
    if square < board.count {
        // まだボードにいる場合は、ヘビや梯子のために上下に移動します
        square += board[square]
    }
}
print("Game over!")

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

サイコロを振った後、プレイヤーはサイコロを振って前進します。サイコロの目がプレーヤーをマス25を超えて動かした可能性があります。その場合、ゲームは終了します。このシナリオに対処するために、コードは、squareがボード配列のcountプロパティよりも小さいことを確認します。マスが有効な場合、board[square]に保存されている値が現在の正方形の値に追加され、梯子やヘビを上下に移動します。

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

その後、現在のwhileループの実行が終了し、ループの状態がチェックされて、ループを再度実行する必要があるかどうかが確認されます。プレーヤーがマス25以上に移動した場合、ループの状態はfalseと評価され、ゲームは終了します。

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

5.2.2. 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のマスにまっすぐ進むことができないため、梯子を上に移動してゲームに勝つことはできません。したがって、ループの最初のアクションとしてヘビや梯子をチェックするのが安全です。

ゲームの開始時、プレイヤーは「マスゼロ」になっています。 board[0]は常に0に等しく、効果はありません。

repeat {
    // ヘビや梯子のために上下に移動します
    square += board[square]
    // サイコロを転がす
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    // 転がした量だけ移動します
    square += diceRoll
} while square < finalSquare
print("Game over!")

コードが蛇と梯子をチェックした後、サイコロが振られ、プレーヤーはdiceRollのマスによって前方に移動します。その後、現在のループの実行は終了します。

ループの条件(square < finalSquareの間)は以前と同じですが、今回はループの最初の実行が終了するまで評価されません。このゲームには、前の例のwhileループよりもrepeat-whileループの構造の方が適しています。上記のrepeat-whileループでは、square += board[square]は、常にループのwhile条件の直後に実行され、squareがまだボード上にあることを確認します。この動作により、前述のゲームのwhileループバージョンで見られる配列境界チェックが不要になります。

5.3. 条件文

特定の条件に基づいてさまざまなコードを実行すると便利なことがよくあります。エラーが発生したときに余分なコードを実行したり、値が高すぎたり低すぎたりしたときにメッセージを表示したりすることができます。これを行うには、コードの一部を条件付きにします。

Swiftには、コードに条件分岐を追加する2つの方法があります。ifステートメントとswitchステートメントです。通常、ifステートメントを使用して、考えられる結果がわずかしかない単純な条件を評価します。switchステートメントは、複数の可能な順列を持つより複雑な条件に適していて、パターンマッチングが実行する適切なコードブランチの選択に役立つ場合に役立ちます。

5.3.1. If

最も単純な形式では、ifステートメントには単一のif条件があります。 その条件が真の場合にのみ、一連のステートメントを実行します。

var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
}
// "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.")
}
// "It's not that cold. Wear a t-shirt."を出力します

これら2つのブランチのいずれかが常に実行されます。気温が華氏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.")
}
// "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またはelseif条件をトリガーできないため、メッセージは出力されません。

5.3.2. 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つと一致する必要があります。考えられるすべての値にcaseを区別することが適切でない場合は、明示的に対処されていない値をカバーするデフォルトのケースを定義できます。このデフォルトのケースはdefaultキーワードで示され、常に最後に表示される必要があります。

この例では、switchステートメントを使用して、someCharacterと呼ばれる単一の小文字を考慮します。

let someCharacter: Character = "z"
switch someCharacter {
case "a":
    print("The first letter of the alphabet")
case "z":
    print("The last letter of the alphabet")
default:
    print("Some other character")
}
// "The last letter of the alphabet"を出力します

switchステートメントの最初のケースは英語のアルファベットの最初の文字aと一致し、2番目のケースは最後の文字zと一致します。switchには、すべてのアルファベット文字だけでなく、すべての可能な文字のケースを区別する必要があるため、このswitchステートメントでは、defaultのケースを使用して、aとz以外のすべての文字を照合します。この規定により、switchステートメントが網羅的になります。

5.3.2.1. 無暗黙フォールスルー

CおよびObjective-Cのswitchステートメントとは対照的に、Swiftのswitchステートメントは、デフォルトでは、各ケースの下部から次のケースにフォールスルーされません。 代わりに、明示的なbreakステートメントを必要とせずに、最初に一致するswitchケースが完了するとすぐに、switchステートメント全体が実行を終了します。これにより、switchステートメントはCのステートメントよりも安全で使いやすくなり、誤って複数のswitchケースを実行することを回避できます。

注意
Swiftではbreakは必要ありませんが、breakステートメントを使用して、特定のケースを照合して無視したり、そのケースの実行が完了する前に一致したcaseからブレークアウトしたりできます。詳細については、Break in a Switchステートメントを参照してください。

各ケースの本文には、少なくとも1つの実行可能ステートメントが含まれている必要があります。最初のケースは空であるため、次のコードを記述することは無効です。

let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // 無効です。caseの本文が空です
case "A":
    print("The letter A")
default:
    print("Not the letter A")
}
// これにより、コンパイル時エラーが報告されます。

Cのswitchステートメントとは異なり、このswitchステートメントは”a”と”A”の両方に一致しません。むしろ、case “a”:に実行可能ステートメントが含まれていないというコンパイル時エラーが報告されます。このアプローチは、あるcaseから別のcaseへの偶発的なフォールスルーを回避し、その意図がより明確であるより安全なコードを作成します。

“a”と”A”の両方に一致する単一のcaseでswitchを作成するには、2つの値を組み合わせて複合caseにし、値をコンマで区切ります。

let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
    print("The letter A")
default:
    print("Not the letter A")
}
// "The letter A"を出力します

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

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

5.3.2.2. インターバルマッチング

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).")
// "There are dozens of moons orbiting Saturn."を出力します

上記の例では、approxCountはswitchステートメントで評価されます。いずれのケースも、その値を数値または数値の間隔と比較します。approxCountの値は12から100の間にあるため、naturalCountには”dozens of”の値が割り当てられ、実行はswitchステートメントから遷移されます。

5.3.2.3. タプル

タプルを使用して、同じ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")
}
// "(1, 1) is inside the box"を出力します

switchステートメントは、ポイントが原点(0, 0)にあるか、赤いx軸にあるか、オレンジ色のy軸にあるか、原点を中心とする青い4行4列のボックスの内側にあるか、ボックスの外側にあるかを判別します。

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

5.3.2.4. 値バインディング

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

以下の例では、型(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))")
}
// "on the x-axis with an x value of 2"を出力します

switchステートメントは、ポイントが赤いx軸上にあるか、オレンジ色のy軸上にあるか、または他の場所(どちらの軸上にもない)にあるかを判別します。

3つのswitch caseは、プレースホルダー定数xおよびyを宣言します。これらは、anotherPointから一方または両方のタプル値を一時的に取得します。最初のcaseであるcase (let x, 0)は、y値が0の任意のポイントに一致し、ポイントのx値を一時定数xに割り当てます。同様に、2番目のcaseであるcase (0, let y)は、x値が0の任意のポイントに一致し、ポイントのy値を一時定数yに割り当てます。

一時定数が宣言された後、それらはcaseのコードブロック内で使用できます。ここでは、ポイントの分類を出力するために使用されます。

このswitchステートメントにはdefaultのケースはありません。 最後のケースであるcase let(x, y)は、任意の値に一致する2つのプレースホルダー定数のタプルを宣言します。anotherPointは常に2つの値のタプルであるため、このケースは残りのすべての可能な値と一致し、switchステートメントを網羅するためにdefaultのケースは必要ありません。

5.3.2.5. 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")
}
// "(1, -1) is on the line x == -y"を出力します

switchステートメントは、ポイントがx == yの場合の緑色の対角線上にあるか、x == -yの場合の紫色の対角線上にあるか、またはどちらでもないかを判別します。

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

前の例のように、最後のcaseは残りのすべての可能な値と一致するため、switchステートメントを網羅するためにdefaultのケースは必要ありません。

5.3.2.6. 複合ケース

同じ本体を共有する複数のswitchケースは、各パターンの間にコンマを入れて、caseの後に複数のパターンを書き込むことで組み合わせることができます。パターンのいずれかが一致する場合、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) is not a vowel or a consonant")
}
// "e is a vowel"を出力します

switchステートメントの最初のケースは、英語の5つの小文字の母音すべてに一致します。同様に、2番目のcaseは、すべての小文字の英語の子音に一致します。最後に、defaultのケースは他の文字と一致します。

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

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")
}
// "On an axis, 9 from the origin"を出力します

上記のcaseには2つのパターンがあります。(let distance, 0)はx軸上の点に一致し、(0, let distance)はy軸上の点に一致します。両方のパターンにはdistanceのバインディングが含まれ、distanceは両方のパターンの整数です。つまり、ケースの本体のコードは常にdistanceの値にアクセスできます。

5.4. 制御遷移ステートメント

制御遷移ステートメントは、あるコードから別のコードに制御を遷移することにより、コードが実行される順序を変更します。Swiftには、5つの制御遷移ステートメントがあります。

  • continue
  • break
  • fallthrough
  • return
  • throw

continue、break、およびfallthroughステートメントについて以下に説明します。returnステートメントは関数で説明されており、throwステートメントはスロー関数を使用したエラーの伝播で説明されています。

5.4.1. 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キーワードを呼び出し、ループの現在の反復をすぐに終了し、次の反復の開始に直接ジャンプします。

5.4.2. Break

breakステートメントは、制御フローステートメント全体の実行をただちに終了します。breakステートメントは、switchまたはloopステートメントの実行を、そうでない場合よりも早く終了する場合に、switchまたはloopステートメント内で使用できます。

5.4.2.1. loopステートメントのbreak

breakをloopステートメント内で使用すると、loopの実行がすぐに終了し、loopの閉じ中括弧(})の後にコードの制御を移します。loopの現在の反復からそれ以上のコードは実行されず、loopのそれ以上の反復は開始されません。

5.4.2.2. Switchステートメントのbreak

breakをswitchステートメント内で使用すると、switchステートメントはすぐに実行を終了し、switchステートメントの閉じ中括弧(})の後にコードの制御を移します。

この動作は、switchステートメントの1つ以上のcaseを照合して無視するために使用できます。Swiftのswitchステートメントは網羅的であり、空のcaseを許可しないため、意図を明確にするために、caseを意図的に一致させて無視する必要がある場合があります。これを行うには、無視するcaseの本体全体としてbreakステートメントを記述します。そのcaseがswitchステートメントと一致すると、case内のbreakステートメントはswitchステートメントの実行をただちに終了します。

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

次の例では、文字値をオンにして、4つの言語のいずれかで番号記号を表すかどうかを判別します。簡潔にするために、複数の値が1つのswitch caseでカバーされています。

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("The integer value of \(numberSymbol) is \(integerValue).")
} else {
    print("An integer value could not be found for \(numberSymbol).")
}
// "The integer value of 三 is 3."を出力します

この例では、numberSymbolをチェックして、1〜4の数字がラテン語、アラビア語、中国語、またはタイ語の記号であるかどうかを判断します。一致するものが見つかった場合、switchステートメントのcaseの1つでオプショナルのInt?のpossibleIntegerValueという変数を適切な整数値に変換します。

switchステートメントが実行を完了した後、この例ではオプショナルバインディングを使用して、値が見つかったかどうかを判別します。possibleIntegerValue変数は、オプショナル型であるため、暗黙の初期値nilを持っています。したがって、オプショナルバインディングは、switchステートメントの最初の4つのcaseのいずれかによってpossibleIntegerValueが実際の値に設定された場合にのみ成功します。

上記の例で考えられるすべての文字値をリストすることは実用的ではないため、defaultのcaseでは、一致しない文字がすべて処理されます。このdefaultのcaseはアクションを実行する必要がないため、本体として単一のbreakステートメントを使用して記述されています。defaultのcaseが一致するとすぐに、breakステートメントはswitchステートメントの実行を終了し、コードの実行はif letステートメントから続行されます。

5.4.2.3. Fallthrough

Swiftでは、switchステートメントは各caseの下部から次のcaseにフォールスルーされません。つまり、最初に一致するcaseが完了するとすぐに、switchステートメント全体が実行を完了します。対照的に、Cでは、フォールスルーを防ぐために、すべてのswitch caseの最後に明示的な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)
// "The number 5 is a prime number, and also an integer."を出力します

この例では、descriptionという新しいString変数を宣言し、それに初期値を割り当てます。次に、関数はswitchステートメントを使用してintegerToDescribeの値を検討します。integerToDescribeの値がリスト内の素数の1つである場合、関数は説明の最後にテキストを追加して、その数が素数であることを示します。次に、fallthroughキーワードを使用して、defaultのcaseにも落ちます。 defaultケースは、説明の最後にテキストが追加され、switchステートメントが完了します。

integerToDescribeの値が既知の素数のリストに含まれていない限り、最初のswitchケースとはまったく一致しません。他に特定のケースがないため、integerToDescribeはdefaultのcaseと一致します。

switchステートメントの実行が終了すると、print(_:separator:terminator:)関数を使用して番号の説明が出力されます。この例では、番号5が素数として正しく識別されています。

注意
fallthroughキーワードは、実行に該当するswitch caseのcase条件をチェックしません。fallthroughキーワードを使用すると、Cの標準のswitchステートメントの動作と同様に、コードの実行が次のcase(またはdefaultのcase)ブロック内のステートメントに直接移動します。

5.5. ラベル付きステートメント

Swiftでは、loopとifステートメントを他のloopとifステートメントの中にネストして、複雑な制御フロー構造を作成できます。ただし、loopとifステートメントはどちらも、breakステートメントを使用して実行を途中で終了する可能性があります。したがって、breakステートメントを終了するloopまたはifステートメントを明示することが役立つ場合があります。同様に、ネストされたloopが複数ある場合は、continueステートメントがどのloopに影響を与えるかを明示すると便利です。

これらの目的を達成するために、loopステートメントまたはifステートメントにステートメントラベルを付けることができます。条件付きステートメントでは、breakステートメントでステートメントラベルを使用して、ラベル付きステートメントの実行を終了できます。loopステートメントでは、breakステートメントまたはcontinueステートメントでステートメントラベルを使用して、ラベル付きステートメントの実行を終了または続行できます。

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

label name: while condition {
    statements
}

次の例では、この章の前半で見た蛇と梯子ゲームのラベル付きwhileループを含むbreakステートメントとcontinueステートメントを使用した適応バージョンです。

今回は、ゲームに追加のルールがあります。

  • 勝つためには、正方形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ループの条件はwhilesquare != 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ループの反復を終了し、ループの次の反復を開始します。
  • 他のすべての場合、振られたサイコロは有効な移動です。プレイヤーはサイコロが振られたマスまで前進し、ゲームロジックは蛇と梯子をチェックします。その後、ループは終了し、制御はwhile条件に戻って、次のターンが必要かどうかを判断します。

注意
上記のbreakステートメントがgameLoopラベルを使用していなかった場合、whileステートメントではなくswitchステートメントからブレークアウトします。gameLoopラベルを使用すると、どの制御ステートメントを終了する必要があるかが明確になります。

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

5.6. 早期終了

guardステートメントは、ifステートメントと同様に、式のブール値に応じてステートメントを実行します。guardステートメントを使用して、guardステートメントの後のコードを実行するために条件が真でなければならないことを要求します。ifステートメントとは異なり、guardステートメントには常にelse句があります。条件が真でない場合、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"])
// "Hello John!"を出力します
// "I hope the weather is nice near you."を出力します
greet(person: ["name": "Jane", "location": "Cupertino"])
// "Hello Jane!"を出力します
// "I hope the weather is nice in Cupertino."を出力します

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

その条件が満たされない場合、elseブランチ内のコードが実行されます。そのブランチは、guardステートメントが表示されるコードブロックを終了するために制御を遷移する必要があります。これは、return、break、continue、throwなどの制御遷移ステートメントを使用して行うことも、fatalError(_:file:line:)などのの戻り値のない関数またはメソッドを呼び出すことができます。

要件にguardステートメントを使用すると、ifステートメントで同じチェックを行う場合と比較して、コードの可読性が向上します。これにより、通常はelseブロックでラップせずに実行されるコードを記述でき、違反した要件を処理するコードを要件の横に保持できます。

5.7. APIの可用性の確認

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

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

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

if #available(iOS 10, macOS 10.12, *) {
    // iOSでiOS 10 APIsを使用し、macOSでmacOS 10.12 APIsを使用する
} else {
    // 以前のiOSおよびmacOS APIにフォールバックします
}

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

一般的な形式では、可用性条件はプラットフォーム名とバージョンのリストを取ります。 iOS、macOS、watchOS、tvOSなどのプラットフォーム名を使用します。完全なリストについては、宣言属性を参照してください。iOS8やmacOS10.10などのメジャーバージョン番号を指定することに加えて、iOS11.2.6やmacOS10.13.3などのマイナーバージョン番号を指定することもできます。

if #available(platform name version, ..., *) {
    APIが利用可能な場合に実行するステートメント
} else {
    APIが利用できない場合に実行するフォールバックステートメント
}
タイトルとURLをコピーしました