Как я могу разработать общий протокол паттернов наблюдателя в Swift?

Я хочу разработать общий протокол Swift для шаблона наблюдателя для использования с разными типами / классами. Проблема в том, что я не могу указать тип для метода notify() наблюдателя.

Первоначально я пытался создать associatedtype с моим Observer протоколом.

protocol Observer {
    associatedtype T
    func notify(_ value: T)
}

protocol Observable {
    var observers: [Observer] { get set }
    func registerObserver(_ observer: Observer)
    func unregisterObserver(_ observer: Observer)
}

Не работает (ошибка компиляции): error: protocol 'Observer' can only be used as a generic constraint because it has Self or associated type requirements.

Поэтому я попытался использовать вместо этого общие методы:

protocol Observer {
    func notify<T>(_ value: T)
}

protocol Observable {
    associatedtype T
    var observers: [Observer] { get set } // This is okay now
}

extension Observable {
    // implement registerObserver()
    // implement unregisterObserver()

    func notifyObservers<T>(_ value: T) {
        for observer in observers {
            observer.notify(value)
        }
    }
}

Это прекрасно работает, но приводит к довольно интересному результату. Чтобы проверить это, я создал FooObserver и FooObservable:

class FooObserver: Observer {
    func notify<T>(_ value: T) {
        print(value)
    }
}

class FooObservable: Observable {
    typealias T = Int // For simplicity I set T to Int type
    var observers: [Observer] = []
}

let a = FooObserver()
let b = FooObserver()
var c = FooObservable()
c.registerObserver(a)
c.registerObserver(b)
c.notifyObservers("hello") // This works, but why?

Мне удалось успешно уведомить двух моих наблюдателей строкой «привет». Я предполагаю, что это связано со стиранием шрифта ...?

Итак, мой вопрос: как я могу реализовать шаблон наблюдателя общего типа, когда я могу быть уверен, что значение в notify() имеет правильный тип?


person Fabian    schedule 28.03.2019    source источник


Ответы (1)


protocol Observer {
    associatedtype T
    func notify(_ value: T)
}

Это говорит о том, что данный наблюдатель может обрабатывать один определенный тип (T), который выбирает наблюдатель.

protocol Observable {
    var observers: [Observer] { get set }
    func registerObserver(_ observer: Observer)
    func unregisterObserver(_ observer: Observer)
}

Это неверно, потому что система не может узнать, что T вы хотите для этих наблюдателей. Каждый наблюдатель может обрабатывать только один T.

protocol Observer {
    func notify<T>(_ value: T)
}

Это в принципе бессмысленно. В нем говорится, что notify можно вызывать с любым типом. Если вы это имеете в виду, вы хотите сказать:

protocol Observer {
    func notify(_ value: Any)
}

Но это означает, что каждый наблюдатель должен иметь дело с Any, что нехорошо. Причина, по которой это работает, заключается в том, что вы выбрали print в качестве теста. print может справиться Any. Если вы хотите протестировать такие вещи, вам нужно попробовать что-то вроде того, что вы действительно хотите сделать в своей программе. Типография работает для всех видов вещей, которые бесполезны для других целей.

Основная проблема в том, что Observer не должно быть протоколом. Это должна быть функция. Например:

typealias Observer<T> = (T) -> Void

protocol Observable {
    associatedtype T
    var observers: [Observer<T>] { get set }
    func registerObserver(_ observer: Observer<T>)
    func unregisterObserver(_ observer: Observer<T>)
}

Проблема с этим подходом в том, что нет возможности реализовать unregisterObserver. Я также не понимаю, как вы реализовали unregisterObserver в своем коде. На самом деле это не кажется возможным.

Вот один очень простой способ построить наблюдаемое:

typealias Observer<T> = (T) -> ()

struct Subscription {
    let cancel: () -> Void
}

final class Observable<T> {
    private var observations: [UUID: Observer<T>] = [:]
    func subscribe(observer: @escaping Observer<T>) -> Subscription {
        let uuid = UUID()
        observations[uuid] = observer
        return Subscription(cancel: { [weak self] in self?.observations[uuid] = nil })
    }
}

(См. https://stackoverflow.com/a/55389143/97337, чтобы узнать, откуда это взялось.)

Более подробно проработанную и очень простую версию см. Observable. Для более сложной и необычной версии см. Stream. Для более мощной и менее необычной версии см. Свойства. Для очень мощной версии, которая устанавливает полный способ программирования, см. RxSwift.

person Rob Napier    schedule 28.03.2019
comment
Спасибо! Это было очень полезно :) Ваша реализация Observable могла быть слишком сложной для моих нужд, но она, безусловно, была очень полезной! - person Fabian; 30.03.2019