Swiftでアプリ開発をしていると、「プロパティの値が変わった時に何かしたい」という場面がよくあります。そんな時に活躍するのがdidSetです。今回は、didSetの基本から実践的な使い方まで、詳しく解説していきます!
目次
didSetとは?
didSetは、プロパティオブザーバーと呼ばれる機能の一つで、プロパティの値が変更された直後に自動的に実行されるコードブロックです。
基本的な書き方
var プロパティ名: 型 = 初期値 {
didSet {
// プロパティが変更された後に実行したい処理
}
}
なぜdidSetが便利なの?
従来の方法とdidSetを使った方法を比較してみましょう。
didSetを使わない場合
class CounterViewController {
private var _count: Int = 0
var count: Int {
get { return _count }
set {
_count = newValue
updateCountLabel() // 毎回手動で呼び出す必要がある
}
}
private func updateCountLabel() {
print("カウント: \(_count)")
}
}
didSetを使った場合
class CounterViewController {
var count: Int = 0 {
didSet {
updateCountLabel() // 自動的に実行される
}
}
private func updateCountLabel() {
print("カウント: \(count)")
}
}
結果: コードがシンプルになり、更新処理を忘れる心配がなくなりました!
didSetの重要な特徴
1. oldValueが使える
didSet内では、変更前の値にoldValue
でアクセスできます。
class Temperature {
var celsius: Double = 20.0 {
didSet {
let change = celsius - oldValue
if change > 0 {
print("温度が\(change)度上昇しました")
} else if change < 0 {
print("温度が\(abs(change))度下降しました")
}
}
}
}
let temp = Temperature()
temp.celsius = 25.0 // "温度が5.0度上昇しました"
temp.celsius = 22.0 // "温度が3.0度下降しました"
2. 初期化時は実行されない
プロパティに初期値を設定する時や、初期化子(イニシャライザ)内での代入時には、didSetは呼ばれません。
class Example {
var value: Int = 10 { // この時点ではdidSetは呼ばれない
didSet {
print("値が変更されました: \(value)")
}
}
init(initialValue: Int) {
value = initialValue // この代入でもdidSetは呼ばれない
}
}
let example = Example(initialValue: 5)
example.value = 20 // この時だけ "値が変更されました: 20" が出力される
3. 自分自身の値も変更できる
didSet内で、そのプロパティ自体の値を変更することも可能です(ただし注意が必要)。
class BoundedValue {
var value: Int = 0 {
didSet {
if value < 0 {
print("負の値は許可されていません。0に設定します。")
value = 0 // 再帰的にdidSetが呼ばれることはない
} else if value > 100 {
print("100を超える値は許可されていません。100に設定します。")
value = 100
}
}
}
}
let bounded = BoundedValue()
bounded.value = -5 // "負の値は許可されていません。0に設定します。"
bounded.value = 150 // "100を超える値は許可されていません。100に設定します。"
実践的な使用例
1. UIの自動更新
class ProgressView {
var progress: Float = 0.0 {
didSet {
updateProgressBar()
updateProgressLabel()
}
}
private func updateProgressBar() {
// プログレスバーのUI更新
print("プログレスバー更新: \(Int(progress * 100))%")
}
private func updateProgressLabel() {
// ラベルのテキスト更新
print("進行状況: \(Int(progress * 100))%完了")
}
}
let progressView = ProgressView()
progressView.progress = 0.3 // UI が自動的に更新される
progressView.progress = 0.7 // UI が自動的に更新される
2. データの保存とログ記録
class UserSettings {
var username: String = "" {
didSet {
saveToUserDefaults()
logChange()
}
}
var theme: String = "light" {
didSet {
applyTheme()
saveToUserDefaults()
}
}
private func saveToUserDefaults() {
// UserDefaultsへの保存処理
print("設定を保存しました")
}
private func logChange() {
print("ユーザー名が '\(oldValue)' から '\(username)' に変更されました")
}
private func applyTheme() {
print("テーマを '\(theme)' に変更しました")
}
}
3. バリデーションと通知
class PasswordField {
var password: String = "" {
didSet {
validatePassword()
notifyStrengthChange()
}
}
private var isValid: Bool = false
private func validatePassword() {
isValid = password.count >= 8 &&
password.rangeOfCharacter(from: .decimalDigits) != nil &&
password.rangeOfCharacter(from: .uppercaseLetters) != nil
print("パスワード有効性: \(isValid ? "有効" : "無効")")
}
private func notifyStrengthChange() {
let strength = calculateStrength()
print("パスワード強度: \(strength)")
}
private func calculateStrength() -> String {
switch password.count {
case 0..<4: return "弱い"
case 4..<8: return "普通"
default: return isValid ? "強い" : "普通"
}
}
}
willSetとの使い分け
SwiftにはdidSetの他にwillSetもあります。
class DataManager {
var data: [String] = [] {
willSet {
print("データを変更予定: \(data.count)件 → \(newValue.count)件")
// 変更前の準備処理
}
didSet {
print("データ変更完了: \(oldValue.count)件 → \(data.count)件")
// 変更後の処理
}
}
}
let manager = DataManager()
manager.data = ["A", "B", "C"]
// 出力:
// データを変更予定: 0件 → 3件
// データ変更完了: 0件 → 3件
使い分けのポイント:
- willSet: 変更前の準備処理(バックアップ、バリデーションなど)
- didSet: 変更後の処理(UI更新、保存、通知など)
注意すべきポイント
1. 無限ループを避ける
// 危険な例:無限ループになる可能性
var badExample: Int = 0 {
didSet {
if badExample < 0 {
badExample = 0 // これ自体がdidSetを呼び出す
}
}
}
2. パフォーマンスを考慮する
// 頻繁に変更されるプロパティでは軽い処理を心がける
var position: CGPoint = .zero {
didSet {
// 重い処理は避ける
updateUI() // 軽い処理にする
}
}
3. stored propertyでのみ使用可能
// OK: stored property
var storedProperty: Int = 0 {
didSet {
print("変更されました")
}
}
// NG: computed propertyでは使えない
var computedProperty: Int {
get { return storedProperty * 2 }
set { storedProperty = newValue / 2 }
// didSet { } // コンパイルエラー
}
まとめ
didSetは、プロパティの変更を自動的に監視して処理を実行できる便利な機能です。主なメリットは:
- 自動実行: 値の変更を忘れずに処理できる
- コードの簡潔性: 手動での処理呼び出しが不要
- 保守性: 変更処理を一箇所に集約できる
- oldValueアクセス: 変更前の値も利用できる
UIの更新、データの保存、バリデーションなど、様々な場面で活用できます。ただし、無限ループやパフォーマンスには注意が必要です。
ぜひプロジェクトでdidSetを活用して、より保守しやすいコードを書いてみてください!
参考リンク