Swiftのプロパティオブザーバについて

# はじめに
この記事ではプSwiftの機能の一つであるプロパティオブザーバについてまとめています。
個人開発アプリ[シンプル歩数計 - SimpleWalking](https://apps.apple.com/jp/app/%E3%82%B7%E3%83%B3%E3%83%97%E3%83%AB%E6%AD%A9%E6%95%B0%E8%A8%88-simplewalking/id1590245946)の中でプロパティオブザーバを使用していましたが、どのような機能なのか理解出来ていなかったので記事にまとめてみました。
間違えている箇所や、こっちの説明の方が分かりやすくなるよ！などのアドバイスがありましたら、コメントに書いていただけると嬉しいです。

## プロパティオブザーバってなに？
プロパティオブザーバは、ストアドプロパティ(格納プロパティ)の値の変更を監視するための仕組みだそうです。
プロパティオブザーバには、willSetキーワード、didSetキーワードの2つがあります。
willSetキーワードはプロパティの値が変更される前に実行される処理を指定します。
didSetキーワードはプロパティの値が変更された後に実行される処理を指定します。
この2つのキーワードを利用することで、プロパティの変更前、変更後で異なる処理を実装することが出来ます。
また、他のプロパティとの連携も可能だそうです。

## 書式
```
var プロパティ名: 型名(= 初期値） {
 
    willSet(仮引数){
        変更前に実行したい処理を指定する
    }
 
    didSet(仮引数){
        変更後に実行したい処理を指定する
    }
 }
```

## コード
```
// アプリ内のユーザーのIDを管理するクラス
class ID {
    var id: Int = 0
    func getID() -> Int {
    // IDの重複を避けるためにgetIDメソッドを呼び出すたびに1を足していく
        id += 1
        return id
    }
}

class User {
    let id: Int

    var name: String {
        // プロパティオブザーバを使用することで、 他のプロパティと連携を取ることが出来る。
        willSet {
            print("氏名が変更されようとしています: \(name)")
        }
        
        didSet {
            print("氏名が正常に変更されました: \(name)")
            // IDの重複を避けるために、氏名が変更されるたびにIDをカウントアップして、変更回数を記録する
            count += 1
        }
    }
    // 氏名の変更回数を格納するプロパティ
    var count: Int

    // イニシャライザ
    init(id: ID, name: String) {
        self.id = id.getID()
        self.name = name
        self.count = 0
    }
    
    // ユーザーの情報を出力する
    func info() {
        var str = "ID: \(self.id)\n"
        str += "氏名: \(self.name)\n"
        str += "変更回数: \(self.count)"
        print(str)
    }
}

// インスタンス化
let id = ID()
var user = User(id: id, name: "Yamada")
user.info()
/*
 ID: 1
 氏名: Yamada
 変更回数: 0
*/

// 氏名を変更
// nameの値が変わるとwillSet,didSetの処理が動く
user.name = "Suzuki"
/*
 氏名が変更されようとしています: Yamada
 氏名が正常に変更されました: Suzuki
*/

user.info()
/*
 ID: 1
 氏名: Suzuki
 変更回数: 1
*/
```
## プロパティオブザーバの暗黙の引数
willSetとdidSetにはそれぞれ仮引数を指定する事が出来ます。
### willSet
willSetでは、プロパティに格納される直前の新しい値を仮引数で参照する事が出来ます。
仮引数を省略する場合は、willSetの中には新しく代入された値である「newValue」という定数が暗黙的に用意されます。
### didSet
didSetでは、プロパティに今まで格納されていた古い値を仮引数で参照できます。
仮引数を省略する場合は、didSetの中には変更前の値である「oldValue」という定数が暗黙的に用意されます。

省略する場合は、oldValueで参照できます。
```
// 在庫数を管理するクラス
class Product {
    var name : String

    // イニシャライザ
    init(name: String) {
        self.name = name
    }
　   // 在庫数を管理するプロパティ
    var stockNum: Int = 150 {
        willSet {
             print("\(self.name)の在庫数が変更されようとしています")
            // パラメータに名前をつけない場合は、デフォルトパラメータ名としてnewValueが使用できる
            // newValueには変更後の値が格納されている
            print("変更内容: \(stockNum) -> \(newValue)")
        }
        
        didSet {
            print("\(self.name)の在庫数が変更されました")
            // パラメータに名前をつけない場合は、デフォルトパラメータ名としてoldValueが使用できる
            // oldValueには変更前の値が格納されている
            print("変更内容: \(oldValue) -> \(stockNum)")
        }
    }
}
// インスタンス化
var parasol = Product(name: "日傘")

// stockNumの値が変わるとwillSet,didSetの処理が動く
parasol.stockNum = 10
/*
 日傘の在庫数が変更されようとしています
 変更内容: 150 -> 10
 日傘の在庫数が変更されました
 変更内容: 150 -> 10
*/

//プロパティオブザーバは新しい値が新しい値がプロパティの現在の値と同じ場合でも、プロパティの値が設定されるたびに呼び出される
parasol.stockNum = 10
/*
 日傘の在庫数が変更されようとしています
 変更内容: 10 -> 10
 日傘の在庫数が変更されました
 変更内容: 10 -> 10
*/

```
## まとめ
今回は変更前、変更後の処理を分けることが出来るプロパティオブザーバについてまとめてみました。
具体的にアプリの中で使ってみないと使用例がわからないですが、アプリ開発でよく出てくる機能だと思うので、今後も手を動かして使いながら訓練していきたいと思います。
最後までご覧いただきありがとうございました。

## 参考文献
Swiftの学習を始めた初学者が、まず最初に見た方が良いYouTubeチャンネルかなと思っています。
Swiftの基礎文法などを、初学者でも分かるようにホワイトボードとPlaygroundeを使用して解説しているので、本やドキュメンントだけではなかなか理解出来ないという人がいましたら、ぜひ見てみてください。
そういうことだったのか！とすんなり理解できるか事が多いかと思いますのでおすすめです。
- [#7 本気で学ぶSwift講座Ⅲ【プロパティオブザーバ】はじめてのプログラミング~iOSアプリ開発までの道
](https://www.youtube.com/watch?v=OuUvjkSmcwo&list=PL3nfHFjHVxFa23wq1wlZD6NswfBMxvGnG&index=8)


[the swift programming language swift 5.7](https://docs.swift.org/swift-book/)の日本語版ドキュメントです。
英語が読めないので、翻訳の精度が良いのかどうかは分からないのですが、サンプルコードが多く載っているため、手を動かしながら学習する事が出来るのでおすすめです。
- [The Swift Programming Language(日本語版)](https://www.swiftlangjp.com/language-guide/properties.html#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3properties)