swiftgenericsswift-protocolsgeneric-associated-types

Add object to an array that confirm to the protocol that has associated type in it


I have a problem to write the code that puts objects into the observers array. The objects that are problematic implement the Observer protocol. Here is the code that shows what I want to do:

protocol Observer {
    associatedtype ValueType
    func update(value: ValueType)
}

struct Subject<T> {
    private var observers = Array<Observer>()

    mutating func attach(observer: Observer) {
        observers.append(observer)
    }

    func notyfi(value: T) {
        for observer in observers {
            observer.update(value: value)
        }
    }
}

Solution

  • If your deployment target is at least a late 2022 release (iOS 16, macOS 13, etc.), you can use a constrained existential:

    protocol Observer<ValueType> {
        associatedtype ValueType
        func update(value: ValueType)
    }
    
    struct Subject<T> {
        private var observers = Array<any Observer<T>>()
    
        mutating func attach(observer: any Observer<T>) {
            observers.append(observer)
        }
    
        func notify(value: T) {
            for observer in observers {
                observer.update(value: value)
            }
        }
    }
    

    If your deployment target is earlier, the Swift runtime doesn't support constrained existentials. (From SE-0353: “It is worth noting that this feature requires revisions to the Swift runtime and ABI that are not backwards-compatible nor backwards-deployable to existing OS releases.”) But you can use closures instead:

    protocol Observer<ValueType> {
        associatedtype ValueType
        func update(value: ValueType)
    }
    
    struct Subject<T> {
        private var observers: [(T) -> Void] = []
        
        mutating func attach<O: Observer>(observer: O)
        where O.ValueType == T
        {
            observers.append { observer.update(value: $0) }
        }
    
        func notify(value: T) {
            for observer in observers {
                observer(value)
            }
        }
    }