7

I want to implement an observer pattern, but I do not find the proper programming language constructs in Swift (also 2.0). The main problems are:

  1. protocol and extension does not allow stored properties.
  2. In classes we could add stored properties, but we can not force a subclass to override some of its inherited methods.

This is what I want:

{class|protocol|extension|whathaveyou} Sensor {
    var observers = Array<Any>() // This is not possible in protocol and extensions 
    // The following is does not work in classes
    func switchOn() 
    func switchOff()
    var isRunning : Bool {
        get
    }
}

class LightSensor : Sensor {
    //...
    override func switchOn() {
        // turn the sensor on
    }
}

// In the class C, implementing the protocol 'ObserverProtocol'

var lightSensor = LightSensor()
lightSensor.switchOn()
lightSensor.registerObserver(self) // This is what I want

And here comes what is possible to my knowledge:

class Sensor {
    private var observers = Array<Observer>()

    func registerObserver(observer:ObserverDelegate) {
        observers.append(observer)
    }
}

protocol SensorProtocol {
    func switchOn()
    func switchOff()
    var isRunning : Bool {
        get
    }
}

class LightSensor : Sensor, SensorProtocol {
    func switchOn() {
        //
    }
    func switchOff() {
        //
    }

    var isRunning : Bool {
        get {
            return // whatever
        }
    }
}

But this is not very convenient, because both Sensor and SensorProtocol should come hand in hand, and are both requirements the subclass LightSensor has to fulfill.

Any ideas?

Michael Dorner
  • 17,587
  • 13
  • 87
  • 117
  • This is probably not possible because you are can't create vars in extension right? In my case, this works on a regular class – Pavel Smejkal Jun 22 '15 at 10:19
  • Yes, but as I wrote, in a class I cannot enforce method which are required to be implemented! – Michael Dorner Jun 22 '15 at 10:38
  • I updated the demo code to make clear what I want and what is not possible. – Michael Dorner Jun 22 '15 at 10:48
  • In that case why dont you create a base class, which adopts to Sensor protocol and both LightSensor and ExternalSensor adopt to this protocol. See my changes. – Sandeep Jun 22 '15 at 10:57
  • Because this does not make sense: `LightSensor` will fulfill the sensor protocol due to its inheritance. – Michael Dorner Jun 22 '15 at 10:59
  • 1
    You might note that CocoaTouch already has an observer pattern based on `NSNotification` and `NSNotificationCenter` classes. Basically, the observers registration is centralized. Not decentralized to the observed classes. – Sulthan Jun 22 '15 at 12:03
  • Thank you for this remark, but it does not solve this problem, because I can think of other examples, where we need properties or a logic for a contract represented by a protocol. – Michael Dorner Jun 22 '15 at 12:18
  • 1
    This library seems to have some merit, at least worth checking out when deciding your own implementation: https://github.com/slazyk/Observable-Swift – Logan Jun 22 '15 at 21:00

4 Answers4

5

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()
}
Wain
  • 118,658
  • 15
  • 128
  • 151
  • I totally agree with your definition about a protocol. However, I use the protocol just because there is no option for forcing the subclass to override a method. This is what I actually want... – Michael Dorner Jun 22 '15 at 10:29
  • You should get build errors for protocol functions not implemented – Wain Jun 22 '15 at 10:52
  • Yes I do, but I can not add stored attributes in protocols such as an array, what I will need for the observer pattern! – Michael Dorner Jun 22 '15 at 10:54
  • It looks good, one thing to add is that Observation class should implement Observer protocol in case Observer will change – evya Aug 16 '16 at 07:47
  • @evya `Observation` implementing a func `notify` is a convenience to demonstrate usage, it could be any func that calls `notify` on the actual observer... – Wain Aug 16 '16 at 08:26
  • The comparison of === will not work as expected because it will not compare the values, it will check the isentity. http://stackoverflow.com/a/24096764/1442541 – evya Aug 17 '16 at 11:18
  • in my example we want an identity check - they need to be the same instance. if you want more flexibility then you can define what equality means, implement the appropriate protocol and user that instead @evya – Wain Aug 17 '16 at 11:29
  • @Wain In you example they will never be the same instance because append method create observer with new identity. – evya Aug 17 '16 at 11:43
  • modifying to remove and re-notify and it works correctly in a swift 2.2 playground @evya - because in this case the observer is a class so it's by reference – Wain Aug 17 '16 at 11:50
1

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()
    }
}
Nico S.
  • 3,056
  • 1
  • 30
  • 64
  • 1
    i believe there's a bug in `private var observers: [Observing]` where the getter will always return an empty array :] – pxpgraphics Jun 30 '17 at 19:15
1

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/

Werner Altewischer
  • 10,080
  • 4
  • 53
  • 60
0

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.

ncerezo
  • 1,331
  • 12
  • 11