見出し画像

Swiftでプログラミング。 - 制御フロー 3

Control Transfer Statements

Control Transfer Statementsは、あるコードから別のコードに移し変えることでコードが実行される順序を変更します。 Swiftには、5つのControl Transfer Statementsがあります。

continuebreak
  .fallthroughreturnthrow

この章では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)
   // Prints "grtmndsthnklk"

上記のコードは、母音またはスペースに一致するたびにcontinueキーワードを呼び出します。これにより、現在のループを終了し、次のループに直接ジャンプします。

Break

breakは、制御フローステートメント全体の実行をただちに終了します。 breakは、switchまたはloopの実行を、すぐに終了する場合に、switchまたはloop内で使用します。

Break in a Loop Statement ループの中断

ループ内で使用すると、breakはループの実行をすぐに終了し、ループの閉じ中括弧(})の後に制御をコードに移します。ループの現在のコード以上のコードは実行されず、ループは中断します。

Break in a Switch Statement Switchの中断

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

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

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

次の例では、ある文字を定義して、switchで4つの文字の中にあるかを判別し数字を選びますます。簡潔にするために、複数の値が1つのcaseで定義されています。

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

この例では、numberSymbolで定義された数字に対して、ラテン語、アラビア語、中国語、またはタイ語で書かれた1〜4の記号についてどうの数字にあたるかを判断します。一致するものが見つかった場合、switchのcaseの1つでoptional Int? であるpossibleIntegerValue変数を適切な整数値に変換します。

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

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

Fallthrough

Swiftでは、switchは各caseの下部にある次の caseに移行するこはありません。 つまり、最初に一致するcaseが完了するとすぐに、switch全体が実行を完了します。 対照的に、"C言語"では、フォールスルーを防ぐために、すべてのswitch caseの最後に明示的なbreakステートメントを挿入する必要があります。 デフォルトを回避するということは、Swiftのswitchが"C言語"よりもよりもはるかに簡潔で予測可能であり誤って複数のswitch caseを実行することを回避します。

"C言語"スタイルのfallthrough動作が必要な場合は、fallthroughキーワードを使用して、ケースバイケースでこの動作を実行できます。 以下の例では、fallthroughを使用して、数値のテキストによる説明を作成します。(fallthroughを使うと最初のcaseで該当しても次のcaseのコードが実行されます。caseでの選択したもののみの実行を無視します)

    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の値がリスト内の素数の1つである場合、関数は説明の最後にテキストを追加して、その数が素数であることを示します。次に、fallthroughキーワードを使用して、defaultに移行します。defaultに移り、説明の最後にテキストが追加され、switchが完了します。

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

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

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

Labeled Statements

Swiftでは、ループと条件を他のループと条件の中に入れ込んで、複雑な制御フロー構造を作成できます。ただし、ループと条件はどちらも、breakステートメントを使用して実行を途中で終了することができますが、breakで終了するループまたは条件を明示する必要があります。同様に、入れ子されたループが複数ある場合は、continueがどのループに関連するかを明示することができます。

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

"label"キーワードと名前をつけ、その後にコロンを付けることが必要です。原理はすべてのループとswitchで同じですが、whileループのこの構文の例を次に示します

    label name: while condition {
       statements
   }

次の例では、この章の前半で見た蛇と梯子ゲームの適応バージョンのwhileループが"label付けされた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という"labelがあり、蛇と梯子ゲームのメインゲームループであることを示しています。

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ループの反復を終了し、ループの次の反復を開始します。
・その他の場合、サイコロの目通りに動きます。プレイヤーはサイコロの目通り前進し、ゲームロジックは蛇と梯子をチェックします。その後、ループは終了し、制御はwhile条件に戻って、次のターンが必要かどうかを判断します。

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

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

Early Exit

"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"])
   // 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の閉じ中括弧の後で続行されます。 条件の一部としてoptionalバインディングを使用して値が割り当てられた変数または定数は、guardが表示される残りのコードブロックで使用できます。

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

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

Checking API Availability

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

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

使用するAPIが実行時に使用可能かどうかに応じて、ifまたはguard使用可能条件を使用して、コードのブロックを条件付きで実行します。 コンパイラーは、そのコードロック内の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"の本体がiOS10以降でのみ実行されることを指定しています。 macOSでは、macOS10.12以降のみ。 最後の引数*は必須であり、他のプラットフォームでは、ifの本体がターゲットで指定された最小のターゲットで実行されるます。

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




この記事が気に入ったらサポートをしてみませんか?