レベルエンター山本大のブログ

面白いプログラミング教育を若い人たちに

Swift 関数とオプショナルの解説を覚書

オプショナルについて

他言語を知っているひとがSwiftを学ぶときに、一番最初につまるのはオプショナルだと思います。

Swiftでは普通の型(IntやStringや自作クラス型などほとんどの型)ではnil (=null)を代入できません。

nilは非常に特殊な状態であると言語仕様で利用を制限し、コンパイラnilチェックを強制してくれることで、「nil安全」なプログラミングを書けるようにしてくれています。


このオプショナルについて、Playgroundで、簡単なサンプルを見てみましょう。
まずは普通の変数や定数を定義して、nilを代入してみます。

import Foundation

// 普通の変数の宣言
var a:Int

//a = nil // 普通の変数にnilを入れるとコンパイルエラー(コメントアウトを外して実行してみてください。)

// Swiftは関数型言語であり、可能な限り定数letを使うことを推奨している。理由は汚染されないから。
// letは定数を宣言するキーワード
// ここでは宣言と初期化を同時に実施
let aLet: Int = 100

// let bLet:Int = nil // let であっても普通の定数にnilを入れることはできない

オプショナル型変数の宣言と利用

普通の変数にはnilが入れられないことが確認できたので、オプショナル型を作ってnilを入れてみます。
オプショナル型の宣言は「Optional<元の型>」とするやり方と「元の型?」とするやり方の2通りがあり、後者の?をつける方が一般的です。

var b:Optional<Int>  // nilが入れるのはOptional型のみ、元の型はジェネリックタイプとして定義
b = nil  // オプショナルなのでnilが入ることができる
b = 10 // オプショナル型にはジェネリックタイプ(今回はInt)の型のデータも入る
print(b) // Optional(10)と表示されるはず、これは単なる10ではなくOptionalに包まれた(wrapされた)変数であることを表す
 
var b2: Int? // オプショナル型の省略記法(こっちの方がメジャー)
b2 = nil // オプショナルなのでnilが入ることができる
b2 = 100
print(b2) 

 

オプショナル型変数・定数から元の型データを取り出す

オプショナル型変数からデータを取り出すには、if let 構文によるオプショナルバインディングを使います。
オプショナルから値を取り出して定数に束縛するということで、オプショナルバインディングと言います。

// オプショナルの解除(アンラッピング)
if let b3 = b2 {
    print(b3)
}


オプショナルバインディングは、新たな名前の定数にバインドするのではなく、元の定数や変数名と同じ名前でバインドすることが推奨されます。
誤ってバインド前の要素にアクセスできないようにするためです。

if let b2 = b2 { // アンラップするときは同じ定数名を使うことが推奨
    print(b2)
}


!マークはオプショナルの強制解除に利用します。nilが入っていたら実行時例外が発生してアプリが落ちます。
!マークは、Swift言語なかで頻繁に登場しますが、異常があれば実行時例外にするという仕様は一貫しています。
!マークは、オプショナルなどを無視するために記述するキーワードなので、コードレビューで!マークを中心に気をつけて読むということも観点にできると思います。

// オプショナルの強制解除
print(b!) // もしnilなら実行時エラー

// オプショナル宣言だが、参照すると自動的に強制解除になる宣言方法
var c: Int!
c = 20
print(c) // もしnilなら実行時エラー

関数と高階関数クロージャ

オプショナルのルールがある程度わかると、次は関数周りです。
関数型の考え方も随分一般的になったと思いますが、Javaだけをやってきたというような人には馴染みが薄いかもしれません。

関数の定義方法(基本)

Swiftの関数は、引数に「外部名」と呼ばれる「呼び出し側」用の引数ラベルをつけることが特徴的です。
関数は、クラスなどに依存せずに記述できます。

import Foundation 

// func 関数名(外部名 内部名:型, ・・・・)->戻り値型

func point(top y:Int , left x: Int ) -> String {
    return "ポイント x位置:\(x) y位置:\(y) "
}

// 呼び出し側では、外部名を引数ラベルとして記述する必要がある
var p = point(  top:10,left:10 )
print(p)

外部名があることで、呼び出し側のコードだけを見て何をやっているかが、はっきりわかるのがいいですね。
引数が多いときに特に便利です。
 

関数の定義の際に外部名を改めてつけるのがめんどくさいというシーンもあります。
定義側で、外部名を省略すると内部名が外部名を兼ねるようになります。

import Foundation 

// 外部名省略すると、内部名が外部名を兼ねる
func line(top: Int, left: Int, size:Int) -> String{
    return point(left:left, top:top ) + "からSize: \(size)の線"
}

// 呼び出し時、内部名が外部名を兼ねる
let ln  = line(top:10, left:30, size:10)
print(ln)


引数の数が1個だけなどの場合、外部名を書かなくてもわかるようなときには、
呼び出し時の外部名指定を省略可能にすることもできます。
外部名の部分に「_」(アンダースコア)を書きます。

// 呼び出し時の外部名を省略可能にする(同じ名前の関数も外部名が異なれば定義可能 オーバーロード)
func circle(_ radius:Int) -> String {
    return " 半径\(radius)"
}

// 呼び出し方法
let circle1 = circle(5)
print(circle1)

関数を変数に代入する

関数がフォーストクラスオブジェクトであり、オブジェクトやクラスなどに依存せずに存在できます。
もっと簡単に言えば、関数を変数に代入できます。

//(続き)

// 前に定義したline関数をdraw変数に代入。
var draw = line;

// 呼びだしかた
// キーワードラベルは消える
let d = draw(10,10,200)
print(d)

関数を引数にとる関数(高階関数

関数を変数に入れられるということは、関数の呼び出し時に引数として渡すことも可能ということです。
関数を引数にとる関数のことを高階関数と呼びます。

処理の一部をコールバックとして定義しておくことができます。
または、コマンドパターンを関数だけで実現できます。

// 引数に関数を取る関数(高階関数)
func payment(action calcFunc: (Double) -> Double ){
    let salary = calcFunc(200_000)
    print("支給額は、\(salary)")
}
 
//
func payOrdinal(salary:Double) -> Double{
    return salary * 0.9
}

// 事前定義した関数を渡して高階関数を呼ぶ(関数は第1級オブジェクトなので変数のように使える)
payment(action: payOrdinal )


クロージャーは無名の関数のことです。」とSwiftでは割り切って言い切っています。
高階関数の呼び出し時に、クロージャーは便利です。

//高階関数の呼び出しにクロージャを使う
paymentSalary(method: {(salary: Double ) -> Double in
    let insurance = salary * 0.13;
    return salary - insurance;
})


// 引数の型、戻り値の型は自明なので省略可能、その際、引数の()も省略できる。
// 処理が1行ならreturn も省略可能
payment( action: { salary in salary })
 

// 引数リストの最後の引数がクロージャなら引数の()の外に追い出せる
payment() { salary in salary * 2 }


// クロージャを追い出したあと、引数が残っていなければ()も省略可能
payment { salary in salary * 3 }
 

 

例外を発生する可能性のある関数

try? またはtry! またはdo{ try } catch で処理する必要がある

import Foundation

func devider(a:Int ,b:Int) throws -> Int{
    if b == 0 {
        throw NSError(domain: "zero divide メッセージ", code: -1, userInfo: nil)
    }
    return a / b
}

// try?をつけて呼び出しエラー時はnilが帰ってくるので、受け取る型をオプショナルにする必要がある
let i :Int? = try? devider(a: 1, b: 0 )
print(i)


// try!をつけて呼び出しエラー時は実行時例外が帰る、受け取る型は普通の型でよい。
let i2 :Int = try! devider(a: 1, b: 0 )


// do { try } catch {}で呼び出し 
do{
    let d3 = try devider(a: 10, b: 0)
    print(d3)
} catch let error {
    print("例外処理" + error.localizedDescription );
}