Как я могу разработать общий шаблон шаблонов наблюдателей в Swift? - PullRequest
1 голос
/ 28 марта 2019

Я хочу разработать общий протокол 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() имеетправильный тип?

1 Ответ

0 голосов
/ 28 марта 2019
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 .

...