Observer Pattern in Swift

谁都会走 提交于 2019-12-03 08:39:19

A protocol is an abstract set of requirements shared across a number of (potentially very different) other objects. As such, it's illogical to store data in a protocol. That would be like global state. I can see that you want to define the specification for how the observers are stored though. That would also allow 'you' to remove 'someone else' from being an observer, and it's very restrictive about how the observers are stored.

So, your protocol should expose methods to add and remove 'yourself' as an observer. It's then the responsibility of the object implementing the protocol to decide how and where the observers are stored and to implement the addition and removal.


You could create a struct to work with your protocols, something like:

protocol Observer: class {
    func notify(target: Any)
}

protocol Observable {
    mutating func addObserver(observer: Observer)
    mutating func removeObserver(observer: Observer)
}

struct Observation: Observable {
    var observers = [Observer]()

    mutating func addObserver(observer: Observer) {
        print("adding")
        observers.append(observer)
    }
    mutating func removeObserver(observer: Observer) {
        print("removing")
        for i in observers.indices {
            if observers[i] === observer {
                observers.removeAtIndex(i)
                break
            }
        }
    }
    func notify(target: Any) {
        print("notifying")
        for observer in observers {
            observer.notify(target)
        }
    }
}

struct ATarget: Observable {
    var observation = Observation()

    mutating func addObserver(observer: Observer) {
        observation.addObserver(observer)
    }
    mutating func removeObserver(observer: Observer) {
        observation.removeObserver(observer)
    }

    func notifyObservers() {
        observation.notify(self)
    }
}

class AnObserver: Observer {
    func notify(target: Any) {
        print("notified!")
    }
}

let myObserver = AnObserver()
var myTarget: Observable = ATarget()
myTarget.addObserver(myObserver)

if let myTarget = myTarget as? ATarget {
    myTarget.notifyObservers()
}

This is my solution in Swift 3

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        var objectToObserve = ObjectToObserve()

        let observer = Observer()
        let observer1 = Observer()

        objectToObserve.add(observer: observer, notifyOnRegister: true)
        objectToObserve.add(observer: observer1, notifyOnRegister: true)
    }
}

//
// MARK: Protocol
//
protocol Observing: class {
    func doSomething()
    func doSomethingClosure(completion: () -> Void)
}

protocol Observable {

}

extension Observable {

    private var observers: [Observing] {
        get {
            return [Observing]()
        }
        set {
            //Do nothing
        }
    }

    mutating func add(observer: Observing, notifyOnRegister: Bool) {
        if !observers.contains(where: { $0 === observer }) {
            observers.append(observer)

            if notifyOnRegister {
                observer.doSomething()
                observer.doSomethingClosure(completion: {
                    print("Completion")
                })
            }
        }
    }

    mutating func remove(observer: Observing) {
        observers = observers.filter({ $0 !== observer })
    }
}

//
// MARK: Observing
//
class ObjectToObserve: Observable {

    init() {
        print("Init ObjectToObserve")
    }
}

class Observer: Observing {

    init() {
        print("Init Observer")
    }

    func doSomething() {
        print("Do something")
    }

    func doSomethingClosure(completion: () -> Void) {
        print("Do something Closure")
        completion()
    }
}

Well, you can certainly overcome the restriction of not having stored properties on extensions. Maybe that way you can complement one of the proposed solutions with an extension that helps you avoid creating the observer list in each subclass / protocol implementation.

Although extensions can't have stored properties, you can actually get them by using the Objective-C Runtime. Assuming you have a base class for your sensors (BaseSensor) and a protocol for observers (SensorObserver):

import Foundation
import ObjectiveC

private var MyObserverListKey: UInt8 = 0

extension BaseSensor {
  var observers:[SensorObserver] {
    get {
      if let observers = objc_getAssociatedObject( self, &MyObserverListKey ) as? [SensorObserver] {
        return observers
      }
      else {
        var observers = [SensorObserver]()
        objc_setAssociatedObject( self, &MyObserverListKey, observers, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC) )
        return observers
      }
    }
    set(value) {
      objc_setAssociatedObject( self, &MyObserverListKey, observers, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC) )
    }
  }
}

To be clear, even though you would need BaseSensor and all Sensors to inherit from it in order to have this property, BaseSensor wouldn't actually implement the Sensor protocol. It's a bit weird, but I think it would suit your needs:

class BaseSensor {
}

protocol Sensor {
   func switchOn()
}

class LightSensor: BaseSensor, Sensor {
   func switchOn() {
     // whatever
   }
}

With Swift 2.0 this would be much simpler, since you can use Protocol Extensions, so you could simply do this:

protocol Sensor {
  func switchOn()
}

extension Sensor {
  // Here the code from the previous implementation of the extension of BaseSensor
}

class LightSensor : Sensor {
   func switchOn() {
     // whatever
   }
}

Way better.

All answers above incorrectly use an array for retaining the observers, which may create retain cycles because of the strong references.

Also in general you may not want to allow the same observer to register itself twice.

The presented solutions also are not general purpose or lack type safety. I reference my blog post here which presents a full solution in a Swifty manner:

https://www.behindmedia.com/2017/12/23/implementing-the-observer-pattern-in-swift/

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!