观察 Swift 5 中变化的通用协议

Generic protocol for observing changes in Swift 5

努力编写 Swift5 下面的 Java 示例。

一般来说,我想要一个 Observable 协议,它将被多个其他协议采用。我需要将这些协议作为函数参数的类型,以便这些函数可以添加额外的观察者。

在Java中,非常容易做到。代码打印出来:

Observer 1 changed to 10
Observer 2 changed to 10

,

interface Observable<O> {
    void addObserver(O observer);
}

interface Settings extends Observable<SettingsObserver> {
    void setInterval(int interval);
}

interface SettingsObserver {
    void intervalChanged(int interval);
}

class AppSettings implements Settings {
    private List<SettingsObserver> observers = new ArrayList<>();

    @Override public void addObserver(SettingsObserver observer) { observers.add(observer); }
    @Override public void setInterval(int interval) { observers.forEach(observer -> observer.intervalChanged(interval)); }
}

class Observer1 implements SettingsObserver {
    @Override public void intervalChanged(int interval) {
        System.out.println("Observer 1 changed to " + interval);
    }
}

class Observer2 implements SettingsObserver {
    @Override public void intervalChanged(int interval) {
        System.out.println("Observer 2 changed to " + interval);
    }
}

class Main {
    public static void main(String[] args) {
        Observer1 observer1 = new Observer1();

        Settings settings = new AppSettings();
        settings.addObserver(observer1);

        Main main = new Main();
        main.run(settings);
    }

    void run(Settings settings) {
        Observer2 observer2 = new Observer2();
        settings.addObserver(observer2);

        settings.setInterval(10);
    }
}

根据您的需要,您可以在 Swift 中使用 property observers。它允许您在 属性 将要更改或已经更改时采取行动。它也没有完整的可观察类型复杂。

这是 Swift 手册中 Apple 的示例:

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

您可能需要使用 didSet() 函数。您还可以在观察者中调用另一个函数。

如果你不想使用 RxSwift 或 Apple 的新框架,你也可以使用 属性 观察者写一个简单的 observable-like class合并.

这是一个简单的例子,它只使用闭包而不是 classes:

class ClassToWatch {
    typealias ObservingFunc = (ClassToWatch) -> Void
    private var observers: [ObservingFunc] = []
    func addObserver(_ closure: @escaping ObservingFunc) {
        observers.append(closure)
    }

    private func valueChanged() {
        observers.forEach { observer in
            observer(self)
        }
    }

    var value1: Int = 0 {
        didSet {
            valueChanged()
        }
    }

    var value2: String = "" {
        didSet {
            valueChanged()
        }
    }
}

var myclass = ClassToWatch()
myclass.addObserver { object in
    print("Observer 1: \(object.value1) \(object.value2)")
}
myclass.addObserver { object in
    print("Observer 2: \(object.value1) \(object.value2)")
}

myclass.value1 = 3
myclass.value2 = "Test"

您的 Java 代码可以直接转换为 Swift 代码。这是我的翻译,有一定程度的 "Swiftification":

protocol Observable {
    associatedtype ObserverType

    func addObserver(_ observer: ObserverType)
}

protocol Settings : Observable where ObserverType == SettingsObserver {
    var interval: Int { get set }
}

protocol SettingsObserver {
    func intervalDidChange(newValue: Int)
}

class Observer1 : SettingsObserver {
    func intervalDidChange(newValue: Int) {
        print("Observer 1 changed to \(newValue)")
    }
}
class Observer2 : SettingsObserver {
    func intervalDidChange(newValue: Int) {
        print("Observer 2 changed to \(newValue)")
    }
}

class AppSettings: Settings {
    var interval: Int = 0 {
        didSet {
            observers.forEach { [=10=].intervalDidChange(newValue: interval) }
        }
    }
    private var observers: [SettingsObserver] = []

    func addObserver(_ observer: SettingsObserver) {
        observers.append(observer)
    }
}

let settings = AppSettings()
settings.addObserver(Observer1())
settings.addObserver(Observer2())
settings.interval = 10

虽然 Observable 不能用作参数类型,但从它派生的协议也指定关联类型,可以。

您可以更进一步,使 SettingsObserver 成为 (Int) -> Void 的类型别名。这样你就不需要所有那些不同的 ObserverX 类.

typelias SettingsObserver = (Int) -> Void

addObserver 调用将变为:

settings.addObserver { print("Observer 1 changed to \([=12=])") }
settings.addObserver { print("Observer 2 changed to \([=12=])") }

并且 didSet 中的调用将更改为:

observers.forEach { [=13=](interval) }

另外,我不明白为什么会有Settings。不能直接让 AppSettings 符合 Observable 吗?我的意思是,我知道程序接口的想法等等,但我觉得这有点过分了......

虽然创建一个您可以添加自己的可观察对象的通用包装器很简单,但您应该改用两个 native 解决方案。

  1. 通知。

    值更改时,使用NotificationCenter.default 发送通知。观察员应收听这些通知。通知是生态系统的重要组成部分:

    class AppSettings {
        enum Notifications {
            static let intervalChanged = Notification.Name("AppSettingsIntervalChangedNotification")
        }
    
        var interval: TimeInterval = 0 {
            didSet {
                NotificationCenter.default.post(name: Notifications.intervalChanged, object: self)
            }
        }
    }
    
    let settings = AppSettings()
    let observer = NotificationCenter.default.addObserver(
        forName: AppSettings.Notifications.intervalChanged,
        object: settings,
        queue: nil
    ) { [weak settings] _ in
        guard let settings = settings else { return }
        print(settings.interval)
    }
    
    settings.interval = 10
    
  2. Key-value 观察 (KVO)

    如果您从 NSObject 继承您的对象,您可以简单地将直接观察者添加到任何 Obj-C 兼容值:

    class AppSettings: NSObject {
        @objc dynamic var interval: TimeInterval = 0
    }
    
    let settings = AppSettings()
    
    let observer: NSKeyValueObservation = settings.observe(\.interval, options: .new) { _, change in
        print(change.newValue)
    }
    
    settings.interval = 10
    

    https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift

为了完整起见,这里有一个简单的通用观察者:

class Observable<ValueType> {
    typealias Observer = (ValueType) -> Void

    var observers: [Observer] = []
    var value: ValueType {
        didSet {
            for observer in observers {
                observer(value)
            }
        }
    }

    init(_ defaultValue: ValueType) {
        value = defaultValue
    }

    func addObserver(_ observer: @escaping Observer) {
        observers.append(observer)
    }
}

class AppSettings {
    let interval: Observable<TimeInterval> = Observable(0)
}

let settings = AppSettings()
settings.interval.addObserver { interval in
    print(interval)
}
settings.interval.value = 10

请注意,我所有的观察者都是简单的闭包。由于 Java 的限制,Java 使用对象作为观察者的原因主要是历史原因。 Swift.

中不需要 ObservableObserver 协议