Swiftで理解するdidSetの方法12選

SwiftのコードにおけるdidSetの使用例を表すイラストSwift
この記事は約27分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Swiftのプログラミング言語は、そのシンタックスや機能性から多くの開発者に支持されています。

特に、プロパティオブザーバーという機能はSwiftの中でも注目の部分となっています。

この記事では、プロパティオブザーバーの中でも特に利用頻度が高い「didSet」に焦点を当て、その使い方や魅力、可能性を深く探求します。

12の実践的なサンプルコードを通じて、didSetの真価を理解し、あなたのSwift開発に活かしてみませんか?

●SwiftのdidSetとは

SwiftにおけるdidSetは、プロパティオブザーバーの一部として提供されています。

プロパティオブザーバーとは、プロパティの値が変更された際に自動的に呼び出される特別なメソッド群を指します。

didSetは、プロパティの値が新しい値にセットされた後に自動的に呼び出されるメソッドです。

例えば、ある変数の値が変わった際に何らかの処理を行いたいと思った場合、その変数にdidSetを設定することで、値が変わる度に特定の処理を実行することができます。

これにより、変数の変更を監視するためのコードを簡潔に、そして効率的に記述することが可能となります。

○didSetの基本的な認識

didSetは、次のような形式で記述されます。

var 変数名: 型 {
    didSet {
        // 値が変わった後の処理
    }
}

この構文において、didSetブロック内には、変数の値が変更された後に実行したい処理を記述します。

この特性を利用することで、例えばUIの更新やログの取得、データベースの更新など、値の変更に応じたさまざまな処理を効果的に実装することができます。

また、didSet内では「oldValue」という特殊な変数を使用することができます。

これは、プロパティの変更前の値を参照するための変数で、変更前と変更後の値を比較したり、特定の条件に基づいて処理を分岐させたりする際に非常に役立ちます。

Swiftで理解するdidSetの方法12選

Swiftのプログラミング言語において、didSetは非常に強力なツールです。これはプロパティオブザーバーの一種で、特定のプロパティの値が変更された後に実行されるコードブロックを定義するのに使用されます。この記事では、SwiftにおけるdidSetの使い方を12の具体的なサンプルコードを通じて詳細に解説します。各サンプルコードには、具体的な使用例とその背後にあるロジックを説明する詳細なコメントが含まれています。Swiftでのプログラミングスキルを高めたい方々にとって、この記事が役立つことを願っています。

SwiftのdidSetとは

SwiftにおけるdidSetは、プロパティオブザーバーの一種であり、プロパティの値が新しく設定された後に呼び出される特殊なブロックです。この機能を使うことで、プロパティの値の変更を監視し、それに応じて追加のアクションを実行することができます。例えば、ユーザーインターフェースの更新、データの検証、外部システムへの通知など、さまざまなケースで利用することが可能です。

didSetの基本的な認識

didSetは、プロパティの値が新しい値に設定された後すぐに実行されるコードブロックです。このブロック内では、新しい値がすでに設定されているため、プロパティの最新の状態に基づいた処理を行うことができます。また、オプショナルとして「oldValue」というパラメータが利用でき、これにより変更前の値を参照することが可能です。

●didSetの使い方

SwiftでdidSetを使用する際は、まずプロパティに対して値が設定された後に実行されるべきコードをdidSetブロック内に記述します。

このブロックは、プロパティが新しい値で更新されるたびに自動的に呼び出されます。

ここでは、プロパティの値がどのように変更されたかに基づいて、さまざまなアクションをトリガーできます。

○サンプルコード1:基本的なdidSetの使用方法

このコードでは、基本的なdidSetの使用方法を紹介しています。

ここでは、単純なString型のプロパティにdidSetを設定し、プロパティの値が変更された際にコンソールに通知が出力されるようにしています。

class SampleClass {
    var myProperty: String = "" {
        didSet {
            print("myPropertyが更新されました。新しい値: \(myProperty)")
        }
    }
}

let sample = SampleClass()
sample.myProperty = "テスト"

このコードでは、SampleClassというクラス内にmyPropertyというString型のプロパティを定義しています。

myPropertyに対する値の変更があると、didSetブロックが呼び出され、”myPropertyが更新されました。

新しい値: (myProperty)”というメッセージがコンソールに表示されます。

例としてsample.myProperty = "テスト"を実行すると、myPropertyが”テスト”に更新され、コンソールには”myPropertyが更新されました。新しい値: テスト”と表示されます。

○サンプルコード2:didSetで前の値と新しい値を比較する

このコードでは、didSet内で変更前の値と新しい値を比較する方法を説明しています。

変更前の値はoldValueを用いて参照することができます。

class ScoreTracker {
    var score: Int = 0 {
        didSet {
            if score > oldValue {
                print("新しいスコア(\(score))は古いスコア(\(oldValue))より高いです。")
            } else if score < oldValue {
                print("新しいスコア(\(score))は古いスコア(\(oldValue))より低いです。")
            }
        }
    }
}

let tracker = ScoreTracker()
tracker.score = 10
tracker.score = 8

この例では、ScoreTrackerクラスにscoreというInt型のプロパティが定義されています。

scoreの値が更新されるたびにdidSetが実行され、新旧のスコアを比較して結果をコンソールに出力します。

まずtracker.score = 10でスコアを10に設定すると、”新しいスコア(10)は古いスコア(0)より高いです。”と表示されます。

続いてtracker.score = 8とすると、”新しいスコア(8)は古いスコア(10)より低いです。”というメッセージが出力されます。

○サンプルコード3:didSetを使用したUIの更新

ここでは、didSetを利用してUIを更新する具体的な方法をサンプルコードとともに紹介します。

この例では、didSetを使ってUIコンポーネントの状態を更新し、インターフェースをより動的にします。

例えば、ユーザーの入力やイベントの発生に基づいてラベルのテキストを変更する場合などに有効です。

import UIKit

class ViewController: UIViewController {
    var labelText: String = "" {
        didSet {
            // UIのラベルを更新する
            label.text = labelText
        }
    }

    @IBOutlet weak var label: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        // 画面が読み込まれた時の初期設定
        labelText = "初期状態のテキスト"
    }

    @IBAction func buttonTapped(_ sender: UIButton) {
        // ボタンがタップされたときにラベルのテキストを更新する
        labelText = "更新されたテキスト"
    }
}

このコードでは、ViewControllerクラス内にlabelTextという文字列型のプロパティがあり、その値が変更されるたびにdidSetが呼び出されます。

didSet内では、UILabelコンポーネントのtextプロパティに新しい値を代入しています。

これにより、labelTextの値が変わると自動的にラベルの表示内容も更新される仕組みです。

このコードを実行すると、ユーザーがボタンをタップするとlabelTextの値が「更新されたテキスト」に変わり、画面上のラベルも同時に更新されることになります。

このようなdidSetの利用は、ユーザーインターフェースが動的に変化するアプリケーションにおいて特に有効です。

値の変化を監視し、それに応じてUIを更新することで、よりリアクティブでユーザーフレンドリーなアプリケーションを実現できます。

○サンプルコード4:didSetとwillSetの組み合わせ

次に、didSetともう一つのプロパティオブザーバーであるwillSetを組み合わせた使用方法を見てみましょう。

willSetはプロパティの値が変更される直前に呼び出されるオブザーバーで、didSetと併用することで、値が変更される前後の両方でカスタムの動作を実行できます。

例えば、次のようなコードが考えられます。

import Foundation

class ExampleClass {
    var myProperty: String = "" {
        willSet(newVal) {
            print("値が\(myProperty)から\(newVal)へ変更されようとしています")
        }
        didSet {
            print("値が\(oldValue)から\(myProperty)へ変更されました")
        }
    }
}

let example = ExampleClass()
example.myProperty = "テスト"

このコードでは、ExampleClassmyPropertyというプロパティが定義されており、willSetdidSetの両方が実装されています。

myPropertyの値が変更されると、まずwillSetが実行され、次にdidSetが実行されます。

例えば、このコードでmyPropertyに「テスト」という新しい値を設定すると、コンソールには以下のような出力がされます。

値がからテストへ変更されようとしています
値がからテストへ変更されました

この組み合わせによって、プロパティの値変更の前後で異なる処理を行いたい場合などに非常に便利です。

例えば、値の変更前に入力の検証を行ったり、変更後に他のコンポーネントへの通知を行うなどの用途に使えます。

●didSetの応用例

SwiftのdidSetは単なるプロパティの変更を監視するだけでなく、多彩な応用例を持っています。

ここでは、その応用例としての使用方法を具体的なサンプルコードとともに紹介していきます。

○サンプルコード5:didSetを使った計算プロパティの更新

このコードではdidSetを使って、あるプロパティの変更に応じて計算プロパティを更新する方法を表しています。

具体的には、scoreの値が更新されると、gradeが自動的に計算されて更新される仕組みとなっています。

class Student {
    var score: Int = 0 {
        didSet {
            // スコアに応じて成績を自動計算
            if score >= 90 {
                grade = "A"
            } else if score >= 80 {
                grade = "B"
            } else {
                grade = "C"
            }
        }
    }
    var grade: String = "C"
}

let student = Student()
student.score = 85
print(student.grade) // "B"と出力される

上記の例では、scoreが85に設定された際、didSetが動作してgradeが”B”という文字列に自動的に更新されることが分かります。

○サンプルコード6:didSetを使用したイベントのトリガー

このコードでは、プロパティの変更を検知して特定のイベントをトリガーする方法を表しています。

この例では、volumeの値が更新されると、isMutedの値も自動で変わるようにしています。

class AudioPlayer {
    var volume: Int = 0 {
        didSet {
            // ボリュームが0になったら、ミュート状態にする
            isMuted = (volume == 0)
        }
    }
    var isMuted: Bool = false
}

let player = AudioPlayer()
player.volume = 0
print(player.isMuted) // trueと出力される

このサンプルコードにおいて、volumeが0に設定された場合、didSet内の条件式によりisMutedがtrueに設定され、音声がミュート状態となります。

○サンプルコード7:didSetでの配列の操作

Swiftのプロパティオブザーバーの一つであるdidSetは、プロパティの値が変わった後に何らかの処理を実行するためのものです。

この機能を使用すると、特定の条件下でのみ配列に要素を追加したり、特定の要素を変更したりするような操作がスムーズに行えます。

ここでは、didSetを使用して配列の操作を行う方法を、具体的なサンプルコードを交えながら詳しく解説していきます。

class ArrayManager {
    // 数値の配列を管理するプロパティ
    var numbers: [Int] = [] {
        didSet {
            // 配列の要素数が5を超えた場合、先頭の要素を削除する
            while numbers.count > 5 {
                numbers.remove(at: 0)
            }
        }
    }
}

let manager = ArrayManager()
manager.numbers = [1, 2, 3, 4, 5]
print(manager.numbers)  // [1, 2, 3, 4, 5]

manager.numbers.append(6)
print(manager.numbers)  // [2, 3, 4, 5, 6]

このコードでは、ArrayManagerというクラス内にnumbersという名前のInt型の配列をプロパティとして持っています。

このプロパティにdidSetを使用して、配列の要素数が5を超えた場合に先頭の要素を削除する処理を追加しています。

この例では、numbers配列に6を追加した時点で要素数が6になるため、先頭の1が削除され、結果的に[2, 3, 4, 5, 6]となります。

このようにdidSetを利用することで、配列の操作に関する独自の制約やルールを簡潔に実装することが可能です。

また、この方法は配列に限らず、様々なデータ型やコレクションに対して応用することができます。

○サンプルコード8:didSetを使った複数プロパティの連動

Swiftのプロパティオブザーバー、特にdidSetを用いることで、複数のプロパティ間での連動を実現することも容易になります。

ここでは、あるプロパティの変更が他のプロパティの変更を引き起こすケースを考えてみましょう。

class TemperatureManager {
    // Celsius(摂氏)の温度を管理するプロパティ
    var celsius: Double = 0.0 {
        didSet {
            fahrenheit = celsius * 1.8 + 32
        }
    }

    // Fahrenheit(華氏)の温度を管理するプロパティ
    var fahrenheit: Double = 32.0 {
        didSet {
            celsius = (fahrenheit - 32) / 1.8
        }
    }
}

let tempManager = TemperatureManager()
tempManager.celsius = 25
print(tempManager.fahrenheit)  // 77.0

tempManager.fahrenheit = 98.6
print(tempManager.celsius)  // 37.0

このコードでは、TemperatureManagerというクラスが摂氏と華氏の温度を管理するプロパティを持っています。

摂氏の温度が変更された際には、対応する華氏の温度も自動的に変更され、逆もまた然りとなります。

この例では、摂氏を25度に設定すると、華氏は77.0度になります。

そして、華氏を98.6度に設定すると、摂氏は37.0度になります。

これにより、どちらの温度単位での入力も受け付けることができるようになり、それぞれの変更が自動的に連動して反映されるため、管理が容易になります。

○サンプルコード9:didSetを利用したカスタムビューの更新

このコードでは、カスタムビューの背景色を変更する際に、didSetを使って関連するテキストカラーも自動的に更新する方法を表しています。

背景色が明るい場合はテキストカラーを黒に、暗い場合は白に自動的に変更します。

import UIKit

class CustomView: UIView {

    // 背景色を設定するカスタムプロパティ
    var customBackgroundColor: UIColor? {
        didSet {
            // 背景色に応じて、テキストカラーを更新する
            updateTextColorBasedOnBackgroundColor()
        }
    }

    private let label = UILabel()

    private func updateTextColorBasedOnBackgroundColor() {
        guard let bgColor = customBackgroundColor else { return }
        let components = bgColor.cgColor.components ?? [0, 0, 0]
        let brightness = (components[0] * 299 + components[1] * 587 + components[2] * 114) / 1000

        // 明るさに応じて、テキストカラーを変更
        label.textColor = brightness > 0.5 ? .black : .white
    }
}

この例では、カスタムビューの背景色(customBackgroundColor)が変更された際、その色の明るさに基づいてテキストカラーを自動的に変更しています。

具体的には、背景色のRGB成分をもとに明るさを計算し、その結果に応じてテキストカラーを黒または白に設定しています。

このようにして、背景色が変わるたびにテキストカラーも自動的に調整されるので、ビューのコントラストを保つことができます。

○サンプルコード10:didSetでのエラーハンドリング

エラーハンドリングはアプリケーション開発において非常に重要です。

このコードでは、ユーザーが入力した情報が適切かどうかをチェックし、不適切な場合はエラーメッセージを表示しています。

import UIKit

class UserDataInputView: UIView {

    // ユーザーの入力データ
    var userData: String? {
        didSet {
            validateUserData()
        }
    }

    private let errorLabel = UILabel()

    private func validateUserData() {
        guard let data = userData else { return }

        if data.count < 5 {
            errorLabel.text = "入力が短すぎます。5文字以上入力してください。"
        } else if data.count > 100 {
            errorLabel.text = "入力が長すぎます。100文字以下で入力してください。"
        } else {
            errorLabel.text = ""
        }
    }
}

この例では、ユーザーが入力したデータ(userData)の長さをチェックしています。

入力が短すぎるか、長すぎるかに応じてエラーメッセージを表示しています。

didSetを使用することで、ユーザーの入力が変更されるたびに自動的にこのチェックを行うことができます。

○サンプルコード11:didSetを使用したデータベースの更新

Swiftでデータベースの更新を行う場面では、プロパティの変更を検知して自動的にデータベースの更新を行いたいことがあります。

ここで、didSetプロパティオブザーバーの力を借りれば、非常に効率的にこのような動作を実現することができます。

データベースに関する操作を行うサンプルとして、シンプルなUserクラスを考えてみましょう。

このUserクラスは名前と年齢の2つのプロパティを持ち、それぞれのプロパティが変更された場合にデータベースの更新を行う動作を想定します。

下記のコードでは、Userクラス内でdidSetを用いて、プロパティの変更を検知し、データベースに更新を行うコードを表しています。

この例では、updateDatabase関数を用いて名前と年齢の更新をデータベースに反映しています。

class Database {
    static func update(name: String?, age: Int?) {
        // ここでデータベースへの更新処理を行う
        // 実際のデータベース操作は省略
    }
}

class User {
    var name: String {
        didSet {
            Database.update(name: name, age: nil)
        }
    }

    var age: Int {
        didSet {
            Database.update(name: nil, age: age)
        }
    }

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let user = User(name: "Taro", age: 30)
user.name = "Jiro"  // データベースが名前を"Jiro"で更新される
user.age = 31       // データベースが年齢を31で更新される

このコードを用いると、Userクラスのインスタンスのnameageプロパティが変更されるたびに、Database.update関数が呼び出され、データベースへの更新が行われる仕組みになっています。

このように、SwiftのdidSetプロパティオブザーバーを利用することで、プロパティの変更を簡単に検知し、それに応じた処理(この場合はデータベースの更新)を自動的に行うことができます。

○サンプルコード12:didSetでの外部APIの呼び出し

Swiftでのアプリケーション開発では、外部APIを呼び出してデータを取得・更新することがよくあります。

didSetを使用することで、特定のプロパティの変更を検知して自動的にAPIの呼び出しを行うことができます。

下記のコードでは、商品情報を管理するProductクラスを考えてみましょう。

このProductクラスは価格のプロパティを持ち、価格が変更された場合に外部のAPIを呼び出して価格情報の更新を行う動作を想定します。

class API {
    static func updatePrice(productId: String, newPrice: Int) {
        // ここで外部APIへの更新処理を行う
        // 実際のAPI操作は省略
    }
}

class Product {
    let productId: String
    var price: Int {
        didSet {
            API.updatePrice(productId: productId, newPrice: price)
        }
    }

    init(productId: String, price: Int) {
        self.productId = productId
        self.price = price
    }
}

let product = Product(productId: "P001", price: 1000)
product.price = 1200  // 外部APIが呼び出され、価格が1200に更新される

このコードを用いると、Productクラスのインスタンスのpriceプロパティが変更されるたびに、API.updatePrice関数が呼び出され、外部APIを通じて価格情報の更新が行われる仕組みになっています。

このように、SwiftのdidSetプロパティオブザーバーを利用することで、プロパティの変更を簡単に検知し、それに応じた処理(この場合は外部APIの呼び出し)を自動的に行うことができます。

●注意点と対処法

SwiftにおけるdidSetプロパティオブザーバーを使う際の注意点とそれに対する対処法を、詳細に解説していきます。

didSetは非常に便利な機能ですが、適切に使わないと予期しない動作を引き起こすことがあります。

ここでは、そのようなトラブルを避けるための方法を具体的なサンプルコードとともにご紹介します。

○didSet内での無限ループの回避

didSet内でプロパティの値を更新すると、再びdidSetが呼び出される可能性があり、これが無限ループを引き起こす原因となることがあります。

例えば、下記のコードを見てみましょう。

class SampleClass {
    var count: Int = 0 {
        didSet {
            count += 1
        }
    }
}

このコードでは、countの値が更新されるたびにdidSet内で再びcountの値が更新されるため、無限ループが発生します。

解決策としては、didSet内での値の更新を避けるか、特定の条件下でのみ更新を行うようにします。

class SampleClass {
    var count: Int = 0 {
        didSet {
            if count < 10 {
                count += 1
            }
        }
    }
}

この例では、countが10未満の場合のみ更新を行います。

○didSetの過度な使用とその影響

didSetは便利ですが、過度に使用するとパフォーマンスの低下やコードの可読性の低下を引き起こすことがあります。

特に、重い処理をdidSet内で行うと、プロパティの更新のたびにその処理が走るため、アプリの動作が遅くなる可能性があります。

例えば、下記のようなコードを考えます。

class SampleClass {
    var data: [Int] = [] {
        didSet {
            processData()
        }
    }

    func processData() {
        // 重い処理
    }
}

この例では、dataの更新のたびにprocessDataメソッドが呼び出されます。

processDataメソッドが重い処理を含む場合、パフォーマンスの問題が生じる可能性があります。

解決策としては、didSet内での処理を最小限に抑える、または必要なタイミングでのみ処理を行うようにします。

○didSetと他のプロパティオブザーバーとの組み合わせ

Swiftには、didSetの他にもwillSetというプロパティオブザーバーがあります。

これらを組み合わせて使用する場合、その順序やタイミングに注意が必要です。

例えば、下記のコードを考えます。

class SampleClass {
    var value: Int = 0 {
        willSet {
            print("willSet: \(newValue)")
        }
        didSet {
            print("didSet: \(oldValue) -> \(value)")
        }
    }
}

このコードでは、valueの値が更新される前にwillSetが呼び出され、更新された後にdidSetが呼び出されます。

●カスタマイズ方法

SwiftのプロパティオブザーバーであるdidSetは非常に強力であり、多岐にわたるカスタマイズが可能です。

ここでは、そのカスタマイズ方法について、具体的なコードを交えながら解説していきます。

○didSetのカスタマイズの基本

didSetをカスタマイズする最も基本的な方法は、プロパティの値が変更されたときの動作を変更することです。

具体的には、didSetの中で実行する処理を変えることで、異なる動作をさせることができます。

例えば、下記のサンプルコードは、scoreというプロパティの値が変わるたびにその値をコンソールに表示するものです。

var score: Int = 0 {
    didSet {
        print("スコアが更新されました: \(score)")
    }
}

このコードでは、scoreを使ってスコアを管理しています。

didSet内でprint関数を使用して、スコアが更新されるたびにコンソールにその値を表示しています。

○didSetの動作タイミングのカスタマイズ

didSetの動作タイミングをカスタマイズすることも可能です。

例えば、特定の条件下でのみdidSetの中の処理を実行するということが考えられます。

下記のサンプルコードでは、temperatureというプロパティの値が一定の範囲を超えた場合のみ、警告メッセージを表示します。

var temperature: Double = 20.0 {
    didSet {
        if temperature > 30.0 {
            print("注意!高温になっています。")
        } else if temperature < 0.0 {
            print("注意!低温になっています。")
        }
    }
}

この例では、temperatureが30.0度を超えた場合や0.0度未満の場合に、それぞれの警告メッセージを表示しています。

○didSetの拡張機能の活用

Swiftのextensionを利用することで、既存の型にdidSetを追加することもできます。

これにより、ライブラリやフレームワークの型を拡張して、特定のプロパティに対してdidSetの動作を追加することが可能となります。

例えば、下記のサンプルコードでは、String型にdidSetを追加し、文字列が変更されるたびにその長さを表示する機能を追加しています。

extension String {
    var lengthWithDidSet: Int {
        didSet {
            print("文字列の長さは\(lengthWithDidSet)文字です。")
        }
    }
}

この例を実行すると、lengthWithDidSetプロパティの値が変更されるたびに、その長さがコンソールに表示されます。

まとめ

SwiftのプロパティオブザーバーdidSetは、プロパティの値が変更された際に自動的に実行される機能を提供しており、これにより様々なカスタマイズや動的な処理を実装することができます。

基本的な使用方法から、より高度なカスタマイズ方法、動作タイミングの調整、さらには拡張機能としての活用まで、didSetの可能性は非常に広いことがお分かりいただけたかと思います。

特に、条件に応じた動作のカスタマイズや、既存の型への拡張を通じてのdidSetの追加などは、Swiftのコーディングにおいて非常に役立つテクニックと言えます。

これにより、コードの簡潔性や再利用性、さらにはメンテナンス性も向上することが期待できます。

今回の記事を通じて、SwiftのdidSetの魅力やその活用方法について深く理解することができたことでしょう。

これを日常の開発に取り入れることで、より効率的で高品質なSwiftアプリケーションの開発を進めることができるでしょう。