SwiftUI ObservableObjectのobjectWillChange

前回、ObservableObjectを継承したクラスを使用したコードを紹介しました。そのクラスを使用しobjectWillChangeイベントを試してみます。

class TObserver:ObservableObject  {
    @Published var num: Int
    init(_ i1: Int) {
        self.num = i1
    }
}

前回と同じく ObservableObjectを継承したTObserverという名称のクラスを作成しました。
クラス内にはnumという数値型の変数を用意しています。

View側で@ObservedObjectを指定した TObserverを用意し VStackのonAppear時にobjectWillChangeイベントを設定します。

onAppearはビューが表示されるときに実行されます。

下記は実装したコードです。

struct ContentView: View {
    @ObservedObject var obj1 = TObserver(100)
    @State var func1: Any?
    var body: some View {
        VStack{
            Button("Button1", action: {
                self.obj1.num = 200
            })
        }.onAppear(perform: {
            self.func1 = self.obj1.objectWillChange.sink(receiveValue: self.changeAction1)
        })
    }
    
    func changeAction1(){
        print("Change before = " + String(obj1.num))
        print("「obj1」の内容変更を知らせる")
    }
}

onAppear内の処理は、@Stateで指定したfunc1: AnyにobjectWillChange.sink()を代入しています。これは、obj1TObserver」の内容変更があった場合にchangeAction1を実行するように指定しています。func1を用意しない場合changeAction1は実行されませんでした。“Button1”のactionには、obj1.num200を入れて変更させています。この“Button1″をクリックするとchangeAction1が実行されるようになります。obj1の変更される前の状態がobjectWillChangeに入ります。

SwiftUI ObservableObjectの使い方

ObservableObjectとは

ObservableObjectは、Combineフレームワーク内の一部の機能オブジェクトです。このオブジェクトを継承元クラスにして使用すると、SwiftUIで監視するすべてのプロパティに@Publishedをマークする必要がありますが、部品にBindingしているプロパティの変更した値が部品に反映できます。

下記コードはObservableObjectを継承したシンプルなクラスです。

class TObserver: ObservableObject {
    @Published var name: String
    init(_ s1: String) {
        self.name = s1
    }
}

nameという文字列を配置していて、@Publishedにしています。下記は、作成したTObserverViewで利用するコードです。

struct ContentView: View {
    @ObservedObject var obj1 = TObserver("初期値")
    var body: some View {
        VStack{
            TextField("Edit1", text: $obj1.name)
            Button("Button1", action: {
                self.obj1.name = "ボタンクリック後"
            })
        }
    }
}

View内でTObserverを作成していますが@ObservedObjectにしています。TextField(“Edit1”, text: $obj1.name)でTObserverのnameとBindingしています。ボタンがクリックされるとnameの文字列を変更し、TextFieldBindingしているので同時に変更します。TObserver@Stateにするとエラーは出ませんがTextFieldの値は変更しません。

@ObservedObjectが使えるのは、ObservableObject継承しているときのみです。従って、このTObserverクラスのObservableObjectを消した場合下記のエラーがでます。

Generic struct 'ObservedObject' requires that 'TObserver' conform to 'ObservableObject'

実行し、ボタンをクリックすると下記のような画面が表示されます。

macOSアプリとして実行した場合の画面

Swiftで関数ポインタっぽい処理

Swift言語で関数ポインタぽい処理ができるのか試してみました。

関数ポインタとはC/C++などでよくつかう下記のようなヤツです。

typedef void (*TNotifyEvent)(System::TObject *Sender);

これはC++で書いたものですがこれを使う場合

void hoge(TObject *Sender){
}
void foo(TObject *Sender){
 TNotifyEvent event1 = &hoge;
 event1(Sender);
}

event1をコールするとhoge()関数が実行されます。
ここまではC++の例です。

Swiftの場合

Xcode11.5 Swift5.1で 同じような処理を行う場合下記のように書きました。

typealias TNotifyEvent = (_ sender: Any) -> Void

class THoge{
 var event1: TNotifyEvent!
 func f1(_ i: Int){
  event1(i as Any)
 }
}

class TFoo{
 var hoge1: THoge
 init() {
  hoge1 = THoge()
  hoge1.event1 = act1
 }
 func act1(_ sender: Any){
  let i:Int = sender as? Int ?? 0
  print(“Action “ + String(i))
 }
}

上記のように記述することができます。

let foo1 = TFoo()
foo1.hoge1.f1(100)

TFooの中のTHoge.f1()というファンクションを実行してTFoo.act1()が実行されます。


下記のコードは、先程作成したTNotifyEventAnyではなくジェネリックスで試してみました。

typealias TNotifyEvent<T> = (_ sender: T) -> Void

class THoge{
 var event1: TNotifyEvent<Int>!
 func f1(_ i: Int){
  event1(i)
 }
}

THogeクラス内のTNotifyEventTNotifyEvent<Int>として利用できます。
このことによってAnyへのキャストの必要もなくなりコードがシンプルに実装できます。