プロパティオブザーバ(Property Observer)とは、格納型プロパティ(Stored Property)の値が更新される際、それをトリガーとしてあるブロックを実行させることができる仕組みです。
プロパティオブザーバにはその処理方法によって、値が格納される前の処理(
willSet)と、値が格納されたあとの処理(
didSet)の2つが存在します。
ここでは、UIKit と組み合わせて使用する例を紹介します。
基本的な文法
プロパティオブザーバの基本的な文法は以下の通りです。
1 2 3 4 5 6 7 8 9 |
var プロパティ名 : 型 [= 初期値] { willSet [(仮引数)] { //プロパティに値が代入される前に行う処理 } didSet [(仮引数)] { // プロパティに値が代入された後に行う処理 } } |
ここで、
[ ]で囲まれた部分は省略できることを意味しています。
didSet,
willSet はどちらか一つだけ記述することも可能です(もちろん、目的に合わせて両方記述しても構いません)。
なお、プロパティオブザーバの詳しい説明や実験的コードについては、下記の書籍「詳解 Swift 第5版」のpp. 88-90 が詳しいので、そちらを参考にすることもお勧めします。
応用例:UITableView と組み合わせて使う
ここでは、UITableView と組み合わせて実行する例を紹介したいと思います。
この例は非常に単純な例で、画面下部に配置されたボタンを押すと、3桁の乱数が UITableVIew に追加されていく例となります。
メインとなる
ViewController は次のような感じとなります。
StoryBoard 等含めたプロジェクト全体は GitHub にも上げてありますので、この記事の末尾にあるリンクからダウンロードしてみてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
import UIKit class ViewController: UIViewController { // プロパティオブザーバを利用して、UI パーツの詳細を設定する @IBOutlet weak var tableView: UITableView! { didSet { // dataSource の設定 tableView.dataSource = self } } @IBOutlet weak var addButton: UIButton! { didSet { // ボタンを角丸にする addButton.layer.cornerRadius = 25 // 背景色を UIColor.systemPink にする addButton.backgroundColor = .systemPink // 文字色を白にする addButton.tintColor = .white } } // UITableView に表示するデータ var datas: [String] = [] { didSet { // 配列に値が追加された後実行される内容 tableView.reloadData() } } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. // プロパティオブザーバを使ったので、ここで UI パーツの設定を行う必要はない。 } @IBAction func addButton(_ sender: UIButton) { // 100...999 の間の乱数を作成し、String 型にする let data = Int.random(in: 100...999).description // 配列に追加 datas.append(data) // ここで tableView.reloadData() を実行しなくても、 // プロパティオブザーバを使うことで値が更新されたら自動的に // didSet の部分が実行される } } extension ViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return datas.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = datas[indexPath.row] return cell } } |
ここでは、
- UITableView に表示させるデータとなるプロパティ
- UITableView のアウトレット( @IBOutlet)
- UIBUtton のアウトレット
の3つにプロパティオブザーバを適用しています。
では、順に処理をみてみることにします。
適用例1:プロパティの値が更新されたら reloadData() を実行する
上記の例では、 datas と名付けた [String] のプロパティを UITableView のセルに表示させています。
たとえば、データを追加するなどして
datas の値を変更した場合、逐一
reloadData() を実行し TableView を更新する必要があります。
したがって、値を変更したのに
reloadData() の実行を忘れてしまうと、値が変わったにもかかわらず TableView が更新されない、という事態が発生してしまいます。
このような事態を避ける一つの方法として、プロパティオブザーバを併用が考えられます。
1 2 3 4 5 6 7 |
// UITableView に表示するデータ var datas: [String] = [] { didSet { // 配列に値が追加された後実行される内容 tableView.reloadData() } } |
datas の値が変更されたあと、続けて didSet のブロックが実行されるようになっています。
didSet のブロックには tableView.reloadData() が記述されていますので、プロパティの値を変更するだけで TableView の更新まで行うことが可能となります。
適用例2: StoryBoard で設定した UI パーツに関連づけられたプロパティの値を設定する
@IBOutlet で設定されたプロパティ(StoryBoard 上に配置した UI パーツと関連づけられたプロパティ)もプロパティですので、プロパティオブザーバを適用することが可能です。たとえば、UITableView のプロパティと関連づける場合、次のような例が考えられます。
1 2 3 4 5 6 7 |
// プロパティオブザーバを利用して、UI パーツの詳細を設定する @IBOutlet weak var tableView: UITableView! { didSet { // dataSource の設定 tableView.dataSource = self } } |
dataSource などの設定は viewDidLoad() で記述することが多いかと思いますが、プロパティオブザーバを併用することで、プロパティの宣言と関連づけて設定することも可能となります。
このあたりは個人的なこだわりや開発チームにおけるポリシーにも関わるので一概には言えませんが、このようにまとめておくことでコードを見やすくすることも可能です。
UIButton だと次のような方法が考えられます。
1 2 3 4 5 6 7 8 9 10 |
@IBOutlet weak var addButton: UIButton! { didSet { // ボタンを角丸にする addButton.layer.cornerRadius = 25 // 背景色を UIColor.systemPink にする addButton.backgroundColor = .systemPink // 文字色を白にする addButton.tintColor = .white } } |
上記のように、ボタンの見た目の変更をまとめて didSet のブロックに記述することも可能です。
プロパティオブザーバ利用上の注意
プロパティオブザーバを使うことで、プロパティに関連づけられた処理を自動で実行できるため非常に便利なのですが、一方思わぬ落とし穴も存在します。
今回の例だとセルの削除については記述していませんが、もしセルの削除のためのコードを行い、削除のアニメーションを追加した場合には実行時エラーが生じる可能性があります。
たとえば、 deleteRows(at:with:) のようなアニメーションを伴い自動的にセルを削除するようなコード、あるいは挿入にアニメーションを伴うコードと併用した場合には、データ数の不一致が原因となり実行時エラーが生じる可能性が高くなります。
そのような場合には、別途対策が必要となりますが、具体的な対策については追って追記したいと思います。
ソースコード
今回のテーマの参考になりそうなコードは GitHub の以下の場所に置いてありますので、Download するなどして活用していただければ幸いです。
リファレンス
- Properties
The Swift Programming Language (Swift 5.3) - UITableView
A view that presents data using rows arranged in a single column. - UIButton
A control that executes your custom code in response to user interactions.