クロージャ(基本)/ { } / in
| 対応: | Swift 1.0(2014) |
|---|
Swiftの『クロージャ』は、処理をまとめて変数や引数として渡せる無名関数です。『{ 引数 in 処理 }』の形式で記述します。
構文
// クロージャの基本形
{ (引数名: 型) -> 戻り値の型 in
return 値
}
// 型を変数に代入
let クロージャ名: (引数型) -> 戻り値型 = { 引数名 in
return 値
}
// 関数型の型エイリアス
typealias 型名 = (引数型) -> 戻り値型
構文一覧
| 構文 | 概要 |
|---|---|
| { (引数: 型) -> 戻り値型 in 処理 } | クロージャの完全な記法です。 |
| { 引数 in 処理 } | 型推論によって型を省略した記法です。 |
| let f: (Int) -> Int = { ... } | クロージャを変数に代入します。 |
| func 名前(completion: (型) -> Void) { } | クロージャを引数として受け取る関数を定義します。 |
| closure(引数) | 変数に代入したクロージャを呼び出します。 |
サンプルコード
closure_basic.swift
// クロージャを変数に代入
let greet: (String) -> String = { (name: String) -> String in
return "こんにちは、\(name)さん!"
}
print(greet("狡噛慎也"))
// 型推論で省略した書き方
let double: (Int) -> Int = { number in
return number * 2
}
print(double(5))
// 戻り値なしのクロージャ
let printMessage: (String) -> Void = { message in
print("メッセージ: \(message)")
}
printMessage("Hello, Swift!")
// クロージャを引数として渡す
func applyOperation(_ value: Int, operation: (Int) -> Int) -> Int {
return operation(value)
}
let tripled = applyOperation(4) { number in
return number * 3
}
print(tripled) // 12
// 周囲の変数をキャプチャ(クロージャの特性)
func makeCounter() -> () -> Int {
var count = 0
let counter = { () -> Int in
count += 1
return count
}
return counter
}
let counter = makeCounter()
print(counter()) // 1
print(counter()) // 2
print(counter()) // 3
swift closure_basic.swift こんにちは、狡噛慎也さん! 10 メッセージ: Hello, Swift! 12 1 2 3
キャプチャリスト
クロージャが外部の変数をキャプチャするとき、デフォルトでは参照としてキャプチャします。クロージャ内でクラスインスタンス(self)をキャプチャすると循環参照(メモリリーク)が起きる可能性があります。『[weak self]』または『[unowned self]』をキャプチャリストで指定して防ぐことができます。
| キャプチャ方法 | 概要 |
|---|---|
| [weak self] | Optional(nil になりえる)な弱参照でキャプチャします。クロージャ実行時に self が nil になる可能性がある場合に使うのが安全です。 |
| [unowned self] | 非Optional な参照でキャプチャします。クロージャの実行中に必ず self が生存していると保証できる場合のみ使います(誤用するとクラッシュします)。 |
キャプチャリストなしで self をキャプチャすると、Inspector と onDone が互いを強参照して循環参照になります。
import Foundation
class Inspector {
var name: String
var onDone: (() -> Void)?
init(name: String) {
self.name = name
}
func setCallbackBad() {
onDone = {
print("\(self.name) の検査が完了") // self を強参照でキャプチャ
}
}
deinit {
print("\(name) が解放されました")
}
}
[weak self] を使うと self が解放されても安全に処理できます。
sample_closure_capture.swift
import Foundation
class Inspector {
var name: String
var onDone: (() -> Void)?
init(name: String) {
self.name = name
}
func setCallbackGood() {
onDone = { [weak self] in
guard let self = self else {
print("インスペクターは既に解放されました")
return
}
print("\(self.name) の検査が完了(weak)")
}
}
deinit {
print("\(name) が解放されました")
}
}
// キャプチャリストで値をコピーする
var count = 0
let snapshot = { [count] in
print("スナップショット時のcount: \(count)") // コピーした値を使います
}
count = 10
snapshot() // スナップショット時のcount: 0(コピーなので変化しません)
print("現在のcount: \(count)") // 10
var inspector: Inspector? = Inspector(name: "Kogami Shinya")
inspector?.setCallbackGood()
inspector?.onDone?()
inspector = nil // ここで解放されます
swift sample_closure_capture.swift スナップショット時のcount: 0 現在のcount: 10 Kogami Shinya の検査が完了(weak) Kogami Shinya が解放されました
@escaping / non-escaping
関数の引数として渡すクロージャには2種類あります。デフォルトはnon-escaping(関数のスコープを超えて保持されない)です。非同期処理や後で実行されるコールバックとして保持する場合は『@escaping』の指定が必須です。
| 種類 | 概要 | 適した場面 |
|---|---|---|
| non-escaping(デフォルト) | 関数の実行中のみ使われます。関数が返ると消えます | mapやfilterのように即座に実行するクロージャ |
| @escaping | 関数の外側(後のタイミング)で実行される可能性があります | 非同期コールバック・プロパティへの保存 |
sample_closure_escaping.swift
import Foundation
// non-escaping: 関数の実行中に使い終わります(デフォルト)
func executeNow(action: () -> Void) {
action() // 呼び出し元が終わる前に実行されます
}
// @escaping: 関数の外に保持され、後から実行されます
var savedCallbacks: [() -> Void] = []
func scheduleCallback(_ callback: @escaping () -> Void) {
savedCallbacks.append(callback) // 関数スコープの外に保存します
}
// @escaping クロージャでの self キャプチャ: 明示的に self が必要です
class Analyst {
var name: String = "Tsunemori Akane"
func startAnalysis() {
// @escaping クロージャ内では self を明示します
scheduleCallback { [weak self] in
guard let self = self else { return }
print("\(self.name) が分析を開始します")
}
}
}
executeNow {
print("今すぐ実行されます")
}
let analyst = Analyst()
analyst.startAnalysis()
// 後から保存したコールバックを実行します
savedCallbacks.forEach { $0() }
swift sample_closure_escaping.swift 今すぐ実行されます Tsunemori Akane が分析を開始します
よくあるミス1: 循環参照
クロージャが self を強参照すると循環参照になります。
class ViewModel {
var handler: (() -> Void)?
func setup() {
handler = { self.update() } // 循環参照: ViewModel ↔ クロージャ
}
func update() { print("更新") }
deinit { print("ViewModelが解放されました") }
}
var vm: ViewModel? = ViewModel()
vm?.setup()
vm = nil // deinit が呼ばれない(循環参照でメモリリーク)
class ViewModel {
var handler: (() -> Void)?
func setup() {
handler = { [weak self] in self?.update() }
}
func update() { print("更新") }
deinit { print("ViewModelが解放されました") }
}
var vm: ViewModel? = ViewModel()
vm?.setup()
vm = nil // ViewModelが解放されました
よくあるミス2: var の参照キャプチャ
var は参照でキャプチャされるため、クロージャ実行時の最新値が使われます。
sample_closure_mistakes.swift
var score = 0
let showScore = { print("スコア: \(score)") } // 参照キャプチャ
score = 100
showScore() // スコア: 100(var は参照でキャプチャされるため最新値が使われます)
// コピーが欲しい場合は [score] と書きます
let showScoreCopy = { [score] in print("コピースコア: \(score)") }
score = 200
showScoreCopy() // コピースコア: 100(キャプチャ時点の値)
swift sample_closure_mistakes.swift スコア: 100 コピースコア: 100
よくあるミス3: @escaping の付け忘れ
後で呼ばれるコールバックには @escaping が必要です。
// func register(callback: () -> Void) {
// DispatchQueue.main.async { callback() } // コンパイルエラー
// }
sample_closure_escaping2.swift
import Foundation
func register(callback: @escaping () -> Void) {
DispatchQueue.main.async { callback() }
}
register { print("登録されたコールバックが実行されました") }
swift sample_closure_escaping2.swift 登録されたコールバックが実行されました
概要
クロージャは周囲のスコープにある変数や定数を「キャプチャ」できます。上記の『makeCounter』の例では、クロージャが関数スコープ内の『count』をキャプチャし、関数が返った後も値を保持し続けます。
クロージャは参照型です。同じクロージャを複数の変数に代入するとキャプチャした値を共有します。クロージャがselfをキャプチャする場合、循環参照に注意が必要です(キャプチャリストで『[weak self]』を指定して防ぎます)。
省略記法やトレイリングクロージャについてはトレイリングクロージャ / 省略記法 / $0を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。