制御構造編集

帰結節や繰返し文の { と } で囲まれた部分は、C言語のように複文(ブロック)ではなくGo言語のように構文要素です。そのため、1文だけであっても { } で囲む必要があります。条件式を囲む括弧は不要です。

分岐文編集

Swiftは、#if文#guard文#switch文 の3種類の分岐文( Branch Statements )を持ちます。

構文(EBNF)
branch-statement = if-statement
                 | guard-statement
                 | switch-statement
分岐文に関連する部分を抜粋[1]

if文編集

構文(EBNF)
if-statement = "if" condition-list code-block [ "else" ( if-statement | code-block ) ]
condition-list = condition [ "," condition-list ]
condition = expression | availability-condition | case-condition | optional-binding-condition
availability-condition = "#available" ( availability-arguments )
                       | "#unavailable" ( availability-arguments )
case-condition = "case" pattern initializer
optional-binding-condition = "let" pattern [ initializer ] | "var" pattern [ initializer ]
code-block = "{" [ statements ] "}"
if文に関連する部分を抜粋[2]
形式
 if 条件式1 {
     1
 } else if 条件式2 {
     2
 } else {
     3
 }
else節は省略可能です。
else節を省略しなかった場合、else に if文あるいは単純なコードブロックが後続します。
コード例
let a = 3

if a < 5 {
    print ("5未満です")
} else {
    print ("5以上です")
}
実行結果
5未満です

[TODO:条件が Optional束縛 な if]

[TODO:条件が #available/#unavailable な if]

guard文編集

条件を満たさない場合の処理を記述するための構文。else句内では必ずスコープを抜ける処理(returnbreakthrow等)を記述するか、Never return 型の関数を呼出す必要があります。

構文(EBNF)
guard-statement = "guard" condition-list "else" code-block
condition-list = condition [ "," condition-list ]
condition = expression | availability-condition | case-condition | optional-binding-condition
availability-condition = "#available" ( availability-arguments )
                       | "#unavailable" ( availability-arguments )
case-condition = "case" pattern initializer
optional-binding-condition = "let" pattern [ initializer ] | "var" pattern [ initializer ]
code-block = "{" [ statements ] "}"
if文に関連する部分を抜粋[3]
形式
guard 条件式 else {
   条件を満たさなかった場合の処理。
   このコードブロックではスコープを抜ける処理が必須
}
func intToUInt(_ n: Int) -> UInt {
  guard n >= 0 else {
    print("引数が0未満のため、正しく変換できませんでした。")
    return 0
  }
  return UInt(n)
}

for num in [1, 0, -3] {
  print(intToUInt(num))
}
実行結果
1
0
引数が0未満のため、正しく変換できませんでした。 
0
guard文の条件にはOptional bind condが使え、条件が偽(=nil)だと else に続くコードブロックが実行され(関数を終了し)ます。
条件が真(nil以外)だと、guard文で宣言された識別子はアンラップされguard文を抜けたあとも参照できます。

[TODO:条件が Optional束縛 な if]

[TODO:条件が #available/#unavailable な if]


switch文編集

構文(EBNF)
switch-statement = "switch" expression "{" [ switch-cases ] "}"
switch-cases = switch-case [ switch-cases ]
switch-case = case-label statements
            | default-label statements
            | conditional-switch-case
case-label = [ attributes ] "case" case-item-list ":"
case-item-list = pattern [ where-clause ] | pattern [ where-clause ] "," case-item-list
default-label = [ attributes ] "default" ":"
where-clause = "where" where-expression
where-expression = expression
conditional-switch-case = switch-if-directive-clause [ switch-elseif-directive-clauses ] [ switch-else-directive-clause ] endif-directive
switch-if-directive-clause = if-directive compilation-condition [ switch-cases ]
switch-elseif-directive-clauses = elseif-directive-clause [ switch-elseif-directive-clauses ]
switch-elseif-directive-clause = elseif-directive [ compilation-condition switch-cases ]
switch-else-directive-clause = else-directive [ switch-cases ]

if-directive = "#if"
elseif-directive = "#elseif"
else-directive = "#else"
endif-directive = "#endif"
switch文に関連する部分を抜粋[4]
形式
switch  {
case パターン1:
  println("case 1")
// 暗黙的にbreakされる
case パターン2, パターン3:  //複数条件
  print("case 2 or 3")
case パターン4 where 条件式:  // where 条件式でパターンに制限を加える事が出来る
  print("case 4")
  fallthrough  // フォールスルー
default:
  print("default")
}
複数のfallthroughを含むSwitch文
let a = 3

switch a {
case 0:
  print("0です")
case 1:
  print("1です")
case 2, 3:
  print("2または3です")
  fallthrough
case 3 ... 10:
  print("3 ... 10 にマッチしました")
  fallthrough
case 3:
  print("3です")
default:
  print("どれでもありません")
}
表示結果
2または3です
3 ... 10 にマッチしました 
3です
Switch 文は、Case 節のパターンに一致した場合、対応する処理を実行します。
このとき、C言語ファミリーのSwitch文と異なり、処理が終わると Switch文を終了し以降のCase節は評価されません。
もし、C言語ファミリーのSwitch文のように後続するCase節のパターンも評価したい場合は、キーワード fallthroughで継続して評価する事を明示します。
この様にパターンを超えた制御の継続をフォールスルーと呼びます。
let a = 1

switch a {
case 0:
  print("0です")
case 1:
  print("1です")
  fallthrough  // フォールスルー
case 1, 2, 3:
  print("1 または 2または3です")
default:
  print("どれでもありません")
}
表示結果
1です
1 または 2または3です
パターンによる値束縛
let value = (404, "Not Found", false)

switch value {
case (200...299, _, false), (300...499, _, true):
  print("(※注意  ステータスコードと成否に不整合があります。)")
  fallthrough
case (_, _, true):
  print("成功")
case (404, _, false):
  print("失敗: ファイルが見つかりませんでした")
case (let status, let message, false):
  print("失敗: \(message) (\(status))")
default:
  print("未知のステータス")
}
表示結果
失敗: ファイルが見つかりませんでした
Enumに対するSwitch文
enum RPS: CaseIterable {
  case Rock
  case Paper
  case Scissors
}
let rps = RPS.Rock
switch rps {
case .Rock: print("グー")
case .Paper: print("パー")
case .Scissors: print("チョキ")
}
print(rps)
print(RPS.allCases)
表示結果
グー
Rock 
[Main.RPS.Rock, Main.RPS.Paper, Main.RPS.Scissors]
Swift 4.2からは CaseIterable プロトコルが使えます。
CaseIterable を使うと従来のように追加のコードを書くことなく、.allCases で列挙型の全要素の集合を得ることができます。
case _:は、default :と同じ意味になります。
ASSOC VAL をつかわないでメソッドを追加する方法
enum RPS : CaseIterable {
    case Rock
    case Paper
    case Scissors
    func string() -> String {
        switch self {
        case .Rock:     return "グー"
        case .Paper:    return "パー"
        case .Scissors: return "チョキ"
        }
    }
}

RPS.allCases.forEach { rps in
    print("\(rps) => \(rps.string())")
}
表示結果
Rock => グー
Paper => パー 
Scissors => チョキ

パターン編集

パターンは、単一の値または複合値の構造を表します。たとえば、タプル(1, 2)の構造は、2つの要素をカンマで区切ったリストであります。パターンは特定の値ではなく、値の構造を表すので、さまざまな値とマッチさせることができます。たとえば、パターン(x, y) はタプル (1, 2) と他の任意の2要素のタプルにマッチします。パターンと値のマッチングに加えて、複合値の一部または全部を抽出して、それぞれの部分を定数または変数名に束縛することができます。

Swift では、2 種類の基本的なパターンがあります。どんな種類の値にもうまくマッチするパターンと、実行時に指定した値にマッチしない可能性があるパターンです。

最初の種類のパターンは、単純な変数、定数、およびオプションのバインディングで値を再構築するために使用されます。これには、ワイルドカードパターン、識別子パターン、およびそれらを含むあらゆる値バインディングまたはタプルパターンが含まれます。これらのパターンにタイプアノテーションを指定すると、特定のタイプの値のみにマッチするように制約をかけることができます。

2つ目の種類のパターンは、完全なパターン・マッチングに使用されます。これには、列挙型ケースパターン、オプショナルパターン、式パターン、タイプキャスティングパターンが含まれます。これらのパターンは、switch文のcaseラベル、do文のcatch句、if文、while文、guard文、for-in文のcase条件などで使用されます。

ワイルドカードパターン編集
識別子パターン編集
列挙型ケースパターン編集
オプショナルパターン編集
式パターン編集
タイプキャスティングパターン編集

繰返し文編集

Swiftは、#for-in文#while文#repeat-while文の3種類の繰返し文( Loop Statements )を持ちます。

構文(EBNF)
loop-statement = for-in-statement
               | while-statement
               | repeat-while-statement
繰返し文に関連する部分を抜粋[5]

for-in文編集

構文(EBNF)
for-in-statement = "for" [ "case" ] pattern "in" expression [ where-clause ] code-block
for-in文に関連する部分を抜粋[6]
配列の要素を繰返し
let names = ["太郎", "花子", "一郎"]
for name in names {
  print("\(name)さん、こんにちは!")
}
実行結果
太郎さん、こんにちは!
花子さん、こんにちは!
一郎さん、こんにちは!
整数の範囲を繰返し
for index in 0..<5 {
    print("index: \(index)")
}
実行結果
index: 0
index: 1
index: 2
index: 3
index: 4
stride関数を対象に
for year in stride(from: 1900, to:2000, by:4) {
    print(year)
}
実行結果(抜粋)
1900
1904
// 長いので中略
1988
1992
1996
where句による条件指定
for index in 0..<10 where index % 2 == 1 {
    print(index)
}
実行結果
1
3
5
7
9
ラベル指定による多重ループからのbreak
let array = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
var index1: Int?
var index2: Int?

loop1: for i in 0..<array.count {
  for j in (i + 1)..<array.count {
    if array[i] == array[j] {
      (index1, index2) = (i, j)
      break loop1
    }
  }
}
print(index1)
print(index2)
実行結果
Optional(0)
Optional(9)

while文編集

構文(EBNF)
while-statement = "while" condition-list code-block
condition-list = condition | condition , condition-list
condition = expression | availability-condition | case-condition | optional-binding-condition
availability-condition | ”#available ( availability-arguments )
                       | "#unavailable" ( availability-arguments )
case-condition = "case" pattern initializer
optional-binding-condition = "let" pattern [ initializer ] | "var" pattern [ initializer ]
while文に関連する部分を抜粋[7]
形式
while 条件式 {
  
}

[TODO:条件が #available/#unavailable な while]

Optional束縛
let array = [1, 2, 3, 4, 5]
var arrayIterator = array.makeIterator()

while let item = arrayIterator.next() {
  print("\(item)")
}

repeat-while文編集

構文(EBNF)
repeat-while-statement = "repeat" code-block "while" expression
while文に関連する部分を抜粋[8]
形式
repeat {
  
} while 条件式

条件式の書き方編集

条件式はBool型かBool型の式か、BooleanTypeプロトコルを実装する型でなければなりません。

条件式を書くときに使う論理演算子や比較演算子の仕様は下記のとおり。

比較演算子編集

比較演算
文法 意味
< より小さい
<= 以下
> より大きい
>= 以上
文法 意味
== 等しい
!= 等しくない
=== 同じオブジェクトへの参照
!== 別のオブジェクトへの参照
文法 意味
~= パターンマッチ(左辺の範囲内に右辺が有ればtrue)
print(1...5 ~= 3) // true
var str = "mission control"
print(str.range(of: "control")! ~= str.index(of: "c")!) // true

論理演算子編集

論理演算子
文法 意味
&& 論理AND
|| 論理OR
! 論理NOT

Optional束縛編集

条件式には、optional-binding-condition を取ることができます。 構文的には、変数宣言(あるいは定数宣言)に似ていますが、nilを取り得るOptional型で、コンディションリストの最後の式の値が条件式の評価対象になります。

if let kiwiIndex = find(fruits, "kiwi") {
    print("キウイは\(kiwiIndex!)番目に見つかりました")
} else {
    print("キウイは見つかりませんでした")
}

defer文編集

スコープを抜けた時に、自動的に実行される処理の登録。

func f() {
  print("a start")
  defer {
    print("a end")
  }
  print("b start")
  defer {
    print("b end")
  }
}

f()
実行結果
a start
b start
b end
a end
このように、実行は登録とは逆順になります。

do-try-catch文編集

throwsrethrowsキーワードが指定されたメソッドは、trytry?try!のいずれかを指定してコールする必要があります。

#throwsキーワードも参照のこと。

import Foundation

let decoder = JSONDecoder()
let data = "[\"some string\", \"another string\"]".data(using: .utf8)

do { // tryをdo節で囲むか、メソッド内であればメソッド自体にrethrowsキーワードを指定する必要がある
  let decoded = try decoder.decode([String].self, from: data!) // tryの場合、throwされたら対応するcatch節に処理が移る
  print(decoded)
} catch let error {
  print(error)
}
// 前略

let decoded = try? decoder.decode([String].self, from: data!) // try?の場合、throwされたら結果がnilになる
print(decoded)
// 前略

let decoded = try! decoder.decode([String].self, from: data!) // try!の場合、throwされたら実行時エラーになる
print(decoded)

関数とクロージャ編集

関数とクロージャは第一級関数であり、普通のオブジェクトと同じように変数に入れたり、関数の引数や戻り値として受け渡しできます。

関数編集

Swift の関数の構文は、おおむね下記のようになります。

func 関数名(引数名1: 型名1, 引数名2: 型名2) -> 戻り値の型 {
    // 付随する処理内容などあればここに書く
    return 戻り値
}


func calcBMI(weight: Double, height: Double) -> Double {
    return weight / (height * height) * 10000
}

上記コードは関数を宣言しただけの命令なので、実行しても何も表示されません。

外部引数名編集

上述のような構文で関数を書く場合、関数の呼び出し側では、関数の引数には外部引数名(external parameter name)を指定しなければなりません。下記コード例では5行目のweightが外部引数です。

対して関数内部のみで使用できる引数名はローカル引数名(local parameter names)と呼ぶ。下記コード1行目のweightがローカル引数です。

func calcBMI(weight w: Double, height h: Double) -> Double {
    return w / (h * h) * 10000
}

var a = calcBMI(weight: 60, height: 160)  // 23.4375
print(a)
実行結果
23.4375

関数の呼び出し側にて外部引数名を省略させるためには、宣言時の外部引数名に _ (アンダースコア)を記述します。単に記述しなかった場合、外部引数名とローカル引数名に同じ値を指定したものとみなされます。

func add(first:Int, second:Int) -> Int {
  return first + second
}

print(add(first: 2, second: 3))
// print(add(2, 3)) // 文法エラー
func add(_ first:Int, _ second:Int) -> Int {
  return first + second
}

// print(add(first: 2, second: 3)) // 文法エラー
print(add(2, 3))

可変長引数編集

func maxInt(first: Int, rest: Int...) -> Int {
    var max = first
    for num in rest {
        if max < num {
            max = num
        }
    }

    return max
}

maxInt(first:7, rest:3, 5, 9, 2)  // 9
maxInt(first:6)  // 6

デフォルト引数編集

func sayHello(name: String = "world") {
    print("Hello, \(name)!")
}

sayHello()               // Hello, world!
sayHello(name: "Swift")  // Hello, Swift!

in-out引数編集

func swapInts(a: inout Int, b: inout Int) {
    let tmp = a
    a = b
    b = tmp
}

var x = 42, y = 99
swapInts(&x, &y)
print("x = \(x), y = \(y)")  // x = 99, y = 42

throwsキーワード編集

do/try/throw/catch を使ったエラーハンドリング
enum FactorialError: Error {
  case NegativeError
  case ArithmeticOverflowError
}

// 階乗を求める関数
// エラーをthrowする可能性あるのでthrows が必要
func factorial(of n:Int) throws -> Int { 
  // nが負の数の場合例外を挙げる
  guard n >= 0 else {
    throw FactorialError.NegativeError
  }
  var result = 1
  if n == 0 {
    return result 
  }
  // 正しくオーバーフローを捕捉するため非再帰版
  for i in 1...n {
    // 算術オーバーフロー発生時に例外を挙げる
    if result > Int.max / i {
      throw FactorialError.ArithmeticOverflowError
    }
    result *= i
  }
  return result
}

for i in -10...30 {
  do {
    let fact = try factorial(of: i)
    print("\(i)! = \(fact)")
  } catch FactorialError.NegativeError {
    print("Error: \(i) < 0")
  } catch FactorialError.ArithmeticOverflowError {
    print("Error: \(i)! is too big)")
  }
}
実行結果
Error: -10 < 0
Error: -9 < 0
Error: -8 < 0
Error: -7 < 0
Error: -6 < 0
Error: -5 < 0
Error: -4 < 0
Error: -3 < 0
Error: -2 < 0
Error: -1 < 0
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
11! = 39916800
12! = 479001600
13! = 6227020800
14! = 87178291200
15! = 1307674368000
16! = 20922789888000
17! = 355687428096000
18! = 6402373705728000
19! = 121645100408832000
20! = 2432902008176640000
Error: 21! is too big)
Error: 22! is too big)
Error: 23! is too big)
Error: 24! is too big)
Error: 25! is too big)
Error: 26! is too big)
Error: 27! is too big)
Error: 28! is too big)
Error: 29! is too big)
Error: 30! is too big)

クロージャ編集

Swiftでは中括弧{}で囲まれたコードブロックは事実上全てクロージャとなります。 このとき、引数・戻り値と、本体の処理の間に「in」を書くことで、この2つの部分を区別します。

[1, 2, 3].map({
    (number: Int) -> Int in
    let result = 3 * number
    return result
})

クロージャの型が推論可能な場合等では、各種省略記法を使用できます。

[1, 2, 3].map({
    number -> Int in
    let result = 3 * number
    return result
})
[1, 2, 3].map({ number in 3 * number })

関数の最後の引数がクロージャである場合、それを括弧の外に出すことができます。また、それが唯一の引数である場合は括弧そのものを省略できます。

[1, 2, 3].map(){ 3 * $0 }
[1, 2, 3].map{ 3 * $0 }

キャプチャリスト編集

クロージャでキャプチャする変数の宣言。weakunownedを指定すると、保持されない参照になります。

import Foundation

var closure1 : () -> ()
var closure2 : () -> ()
var closure3 : () -> ()

do {
    var str = "hello" as NSString

    closure1 = { 
        print("str : \(str)")
    }
    closure2 = { [str] in 
        print("str : \(str)")
    }
    closure3 = { [weak str] in 
        print("str : \(str)")
    }

    str = "goodbye"
}

closure1()
closure2()
closure3()

/* 実行結果:
str : goodbye
str : hello
str : Optional(hello)
*/

// 補足: closure2に関する処理を削除すると、closure3の出力結果はnilになります(strを保持する参照が1つもなくなるため)

演算子編集

演算子オーバーロード編集

Swiftでは任意の型に対して、既存の演算子をオーバーロードして独自の振る舞いを定義することができます。
なお = -> . // /* */ は予約されておりオーバーロードしたりカスタム演算子(後述)として定義することはできません。

演算子オーバーロード
struct Vector2D {
  var x = 0.0
  var y = 0.0
}

func + (lhs: Vector2D, rhs: Vector2D) -> Vector2D {
  return Vector2D(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}

prefix func - (rhs: Vector2D) -> Vector2D {
  return Vector2D(x: -rhs.x, y: -rhs.y)
}

func += (lhs: inout Vector2D, rhs: Vector2D) {
  lhs = lhs + rhs
}

prefix func ++ (rhs: inout Vector2D) -> Vector2D {
  rhs += Vector2D(x: 1.0, y: 1.0)
  return rhs
}

let vec1 = Vector2D(x: 2.0, y: 3.0)
let vec2 = Vector2D(x: 5.0, y: 1.0)
print("let vec1 = Vector2D(x: 2.0, y: 3.0)")
print("let vec2 = Vector2D(x: 5.0, y: 1.0)")
print("vec1 ⇒ \(vec1)")
print("vec2 ⇒ \(vec2)")
print("vec1 + vec2 ⇒ \(vec1 + vec2)")
var vec3 = -vec1
print("var vec3 = -vec1")
print("vec3 ⇒ \(vec3)")
vec3 += vec2
print("vec3 += vec2")
print("vec3 ⇒ \(vec3)")
print("++vec3 ⇒ \(++vec3)")
実行結果
let vec1 = Vector2D(x: 2.0, y: 3.0)
let vec2 = Vector2D(x: 5.0, y: 1.0)
vec1 ⇒ Vector2D(x: 2.0, y: 3.0)
vec2 ⇒ Vector2D(x: 5.0, y: 1.0)
vec1 + vec2 ⇒ Vector2D(x: 7.0, y: 4.0)
var vec3 = -vec1
vec3 ⇒ Vector2D(x: -2.0, y: -3.0)
vec3 += vec2
vec3 ⇒ Vector2D(x: 3.0, y: -2.0)
++vec3 ⇒ Vector2D(x: 4.0, y: -1.0)

カスタム演算子編集

Swift標準の定義済み演算子(+*%など)の他に、全く新しい演算子を定義することができます。新しく定義したカスタム演算子は、標準の演算子と同じように演算子オーバーロードを実装できます。

冪乗演算子の定義
import Foundation

precedencegroup Exponentiative {
  associativity: right
  higherThan: MultiplicationPrecedence
}
infix operator **: Exponentiative

func ** (lhs: Double, rhs: Double) -> Double {
  return pow(lhs, rhs)
}

print(2 ** 3)
print(0.5 * 2 ** 3 ** 2)
print(0.5 * (2 ** (3 ** 2)))
実行結果
8.0
256.0
256.0
  1. pow() を使うために
  2.  
  3. precedencegroup は演算子の特性を定義します
  4. associativity は結合方向
    冪乗演算子の結合方向数学でもプログラミングでも流派が別れますが、今回は右で
  5. 演算子の優先順位は乗除算より上にしました。
  6.  
  7. 冪乗は中置なので infix です。演算子のトークン ** と特性 Exponentiative を併せて指定しています。
    ほかに、前置:prefix 後置:postfix があります
  8.  
  9. 関数の実装です。トークンが関数名になります。

脚註編集

  1. ^ Summary of the Grammar -- Branch Statements
  2. ^ Summary of the Grammar
  3. ^ Summary of the Grammar -- guard-statement
  4. ^ Summary of the Grammar -- switch-statement
  5. ^ Summary of the Grammar -- Loop Statements
  6. ^ Summary of the Grammar -- For-In Statements
  7. ^ Summary of the Grammar -- While Statements
  8. ^ Summary of the Grammar -- Repeat-While Statement