🐤
【Swift】closureをコードでざっくり理解
Swiftの備忘録シリーズ2。今回はclosureです。
closure
closureとは、文中に埋め込む命令の塊のことをいう(関数like)。
(https://developer.apple.com/documentation/swiftui/button)[SwiftUIにおけるButton]を例に出すと、Button構造体のイニシャライザはこんな感じになっている。
init(action: () -> Void, label: () -> Label)
例えば、ボタンを押したときにログで「pressed」と出力し、「ボタン」と書かれたラベルのボタンを作成しようとするとこんな感じのコードを書くことになる。
ContentView.swift
struct ContentView: View {
var body: some View {
Button(action: printLog, label: label)
}
func label() -> Text {
Text("ボタン")
}
func printLog(){
print("pressed")
}
}
しかし、実際のコードの多くではこんな感じで書かれていることが多い。
ContentView.swift
Button(action: { print("pressed") }){
Text("ボタン")
}
上記との違いは2つあり、
- actionの中に関数が入っている
- labelがButton()の中になく、その代わりに()の右側に関数が入っている
この二つはどちらもclosureになる。
actionの中に関数を入れるのは、再利用しないメソッドを外側に書いておくのはコードが煩雑になってよろしくないから中に書いちゃおうね、というお気持ちからくるもの。
labelがなくなって()の右側に関数が入っているのはトレイリングクロージャーといい、「最後の引数がクロージャの場合はそのクロージャを()の外に出して、その引数名を省略することができる」 というルールに基づいて変わっている。
では、SwiftUIじゃない場合はどうか? 実際のコードではこんな感じで書かれていた。
ViewController
// ...
let header = collectionView.dequeueReusableSupplementaryView(
ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: HeaderIdentifier.large.rawValue, for: indexPath
) as! LargeTitleHeaderView
header.setup(title: "Button Title")
header.buttonDidTapClosure = { [weak self] in
self?.viewModel.toggleFilter()
header.setCheckmark(self?.viewModel.isFiltered.value ?? false)
}
// ...
class LargeTitleHeaderView: UICollectionReusableView {
public lazy var toggleFilterButton: UIButton = {
let button = UIButton()
button.setTitle("Toggle", for: .normal)
button.addTarget(self, action: #selector(buttonDidTap), for: .touchUpInside)
button.setImage(Asset.Images.buttonSelectOff.image, for: .normal)
}
func setCheckmark(_ isChecked: Bool) {
let image = isChecked ? Asset.Images.buttonSelectOn.image : Asset.Images.buttonSelectOff.image
toggleFilterButton.setImage(image, for: .normal)
}
var buttonDidTapClosure: (() -> Void)?
@objc private func buttonDidTap() {
buttonDidTapClosure?()
}
}
// ...
ViewModel
// ...
let isFiltered: MutableProperty<Bool> = .init(false)
func toggleFilter() {
isFiltered.value.toggle()
}
// ...
ViewControllerでは、LargeTitleHeaderView
クラスで buttonDidTapClosure
という変数を宣言する(この時、型を() -> Void
と指定)。
実際にインスタンスが生成されたとき、buttonDidTapClosure
に先ほどの宣言と合致した型の関数(クロージャ)を代入することで、ボタンが押されたときの動作(addTarget
のactionに設定)を設定する。
ViewModelでは、isFilteredというプロパティを持っており(Reactiveのコードなので)、これは先ほどのクロージャの中で処理されるボタンが押されているか否かのBoolを表している。
「クロージャがなにか」という疑問というよりは「クロージャを使ったイベント処理の仕方」に対する疑問があったので、今回はその疑問を解消することができました。
Discussion