Handling multiple observers in Swift

It has been a while but I remember that when I switched from .NET to Objective-C I misses few small features that .NET, or specifically, C# had.

One of them was the as operator that allowed the safe conversion between types - it either was that type and you got a reference to it back or got null. It felt so perfect for Objective-C and the way it handles nils. But I kinda fixed that years later.

The other thing I missed was how delegates or events were handled. You added your handler to a list of handlers (usually with the use of += operator) and you were notified along with all others that “subscribed” to that event.

It was natural there that it’s quite probable that more than one object might be interested when some actions occur or some changes are made. That’s why single delegate paradigm felt so alien to me at first. That feeling continued all that time, including the transition to Swift.

Recently while extracting parts of a project to separate frameworks I added a very minimal Swift implementation for handling multiple observers in Swift.

public protocol ObserverRemoving {
    func removeAsObserver()
}

public class ObserverList<T> {
    public typealias ObserverBlock = (T) -> Void

    private var observers: [UUID: ObserverBlock] = [:]

    public init() {}

    public func addObserver(_ observer: @escaping ObserverBlock) -> ObserverRemoving {
        let token = UUID()
        observers[token] = observer
        return ObserverListRemover { [weak self] in
            self?.observers.removeValue(forKey: token)
        }
    }

    public func notifyAll(_ change: T) {
        for observer in observers.values {
            observer(change)
        }
    }

    public func removeAll() {
        observers.removeAll()
    }
}

struct ObserverListRemover: ObserverRemoving {
    private let removeBlock: () -> Void

    init(removeBlock: @escaping () -> Void) {
        self.removeBlock = removeBlock
    }

    func removeAsObserver() {
        removeBlock()
    }
}

If you close all of those 31 lines in one file you could also probably make the struct fileprivate but that’s a detail. What is important is what’s publicly visible as an API.

First, you have a protocol with just one method. It’s used as a token you hang onto until you want to remove your observer. This hides the implementation details and makes it clear and easy to use (previously I used things like random typealiased Strings for tokens and believe this approach is much better).

Then you have the list class itself, where you can add your handler to observe whatever a change you’re interested in (e.g. text in a field changed or an operation in your network manager resulted in an Error and you want to log that error and possibly show some indicator in the UI). And the owner of the list can notifyAll about the event. And that’s pretty much it. Thanks to generics you can use any type you want as an object that’s sent to all the observer. And using tuples you can provide multiple such objects (e.g. let textDidChangeObservers = ObserverList<(TextField, String)>()).

These problems can, of course, be solved with reactive programming (with RxSwift or Combine) but if you don’t want to or can’t use those solutions this tiny one works just fine.

Posted on
Tagged in swift , code
Jarek Pendowski

Want to talk?

comments powered by Disqus