2015-07-12 175 views
2

有两种相互作用的泛型类集合是一种很好的设计模式。一个简单的例子是Observable-Observer模式。可观察的事件向观察者发布事件,但是无论观察到什么类型的事件,模式都是相同的。Swift中协议和泛型的限制

我的第一个想法是,首选的方法是定义两个通用协议。这应该提供最小的耦合,随着代码库的增长,这种耦合会变得很好。

protocol ProtocolObserver { 
    typealias EventType 
    func update<O:ProtocolObservable where O.EventType == EventType>(observable:O, event:EventType) -> Void 
} 

protocol ProtocolObservable { 
    typealias EventType 
    func registerObserver<O:ProtocolObserver where O.EventType == EventType>(observer:O) -> Bool 
    func unregisterObserver<O:ProtocolObserver where O.EventType == EventType>(observer:O) -> Void 
} 

试图定义实现上述协议的类竟然是一个受伤的世界。我没有找到任何方法来做到这一点。

然而,实现泛型基类将是一个可接受的解决方案。

protocol GenericObserver { 
    func update<EventType>(observable:GenericObservable<EventType>, event:EventType); 
} 

class GenericObservable<EventType> { 
    private var observers:[GenericObserver] = [] 
    func registerObserver(observer:GenericObserver) -> Bool { 
     // Code to avoid registering the same observer twice 
     observers.append(observer) 
     return true 
    } 
    func unregisterObserver(observer:GenericObserver) -> Void { 
     // Code to remove the observer if present in observers 
    } 
    func notifyObservers(event:EventType) -> Void { 
     for observer in observers { 
      observer.update(self, event: event) 
     } 
    } 
} 

这次没有定义一些实现协议的类的问题。将它们添加到通用观察器的实例并未显示出我期望的行为。

let numberObservable = GenericObservable<NSNumber>() 

class NumberObserver : GenericObserver { 
    func update<NSNumber>(observable:GenericObservable<NSNumber>, event:NSNumber) { 
     print("Number Event \(event)") 
    } 
} 

let numberObserver = NumberObserver() 

numberObservable.registerObserver(numberObserver) 

class DataObserver : GenericObserver { 
    func update<NSData>(observable:GenericObservable<NSData>, event:NSData) { 
     print("Data Event \(event)") 
    } 
} 

let dataObserver = DataObserver() 

numberObservable.registerObserver(dataObserver) 

numberObservable.notifyObservers(NSNumber(int: 42)) 

我预计numberObservable.registerObserver(dataObserver)会导致编译错误。相反,它高兴地打印输出

Number Event 42 
Data Event 42 

这一切给我留下了两个问题:

  1. 我有什么误解,当我期望编译器不接受numberObservable.registerObserver(dataObserver)

  2. 有没有一种方法可以分别实现一对符合ProtocolObserverProtocolObservable的类?

回答

4

您的问题1和2实际上是强相关的。

在开始之前,我应该指出,当你有一流的功能时,observable/observer模式几乎完全是多余的。而不是强制回调接口,你可以提供一个闭包。我将在问题2的答案中显示此内容。

首先,1.您遇到的问题是类型擦除。你的基类是只在您已经定义registerObserver的地方,它看起来像这样:

class GenericObservable<EventType> { 
    private var observers:[GenericObserver] = [] 
    func registerObserver(observer:GenericObserver) -> Bool { 
     // Code to avoid registering the same observer twice 
     observers.append(observer) 
     return true 
    } 
    //... 
} 

也就是说,它会采取和存储协议参考任何类型。对于什么类型没有限制,它可以是任何东西。例如,您可以通知的Int

extension Int: GenericObserver { 
    func update<EventType>(observable:GenericObservable<EventType>, event:EventType) { 
     print("Integer \(self)") 
    } 
} 

numberObservable.registerObserver(2) 

问题会时被调用者尝试使用EventTypeEventType可能是任何东西。它类似于这样的功能:

func f<T>(t: T) { } 

T可以是任何你喜欢的类型 - 一个String,一个Int,一个Foo。但你无法做任何事情,因为它提供了零担保。为了使泛型有用,你必须限制它(即保证它具有某些特征,例如它可以被加/减),或者将它传递给另一个不受约束的泛型函数(比如把它放在一个通用的集合中,或者拨打printunsafeBitCast,它将以任何类型操作)。

基本上,你的观察者都声明“我有一套方法,update,你可以用任何你喜欢的类型打电话”。这不是很有用,除非你写的东西像map或像数组这样的泛型集合,在这种情况下你不在乎T是什么。

这可能有助于澄清一些混乱 - 这并没有做你认为它的作用:

class DataObserver : GenericObserver { 
    func update<NSData>(observable:GenericObservable<NSData>, event:NSData) { 
     print("Data Event \(event)") 
    } 
} 

在这里,你有没有宣布特别需要一个NSData类。您刚刚命名了通用占位符NSData。类似于命名变量NSData - 这并不意味着变量是什么,只是这就是你所说的。你可以写这样的:

class DataObserver : GenericObserver { 
    func update<Bork>(observable:GenericObservable<Bork>, event: Bork) { 
     print("Data Event \(event)") 
    } 
} 

确定,所以如何实现与相关类型可观察到的协议(即在协议的typealias)。这是一个例子。但请注意,没有Observer协议。相反,Observable将采用任何接收适当事件类型的函数。

protocol Observable { 
    typealias EventType 
    func register(f: EventType->()) 
} 
// No need for an "Observer" protocol 

现在,让我们实现这一点,固定EventType是一个Int

struct FiresIntEvents { 
    var observers: [Int->()] = [] 

    // note, this sets the EventType typealias 
    // implicitly via the types of the argument 
    mutating func register(f: Int->()) { 
     observers.append(f) 
    } 

    func notifyObservers(i: Int) { 
     for f in observers { 
      f(i) 
     } 
    } 
} 

var observable = FiresIntEvents() 

现在,如果我们想通过一个类来观察,我们可以:

class IntReceiverClass { 
    func receiveInt(i: Int) { 
     print("Class received \(i)") 
    } 
} 

let intReceiver = IntReceiverClass() 
// hook up the observing class to observe 
observable.register(intReceiver.receiveInt) 

但我们还可以注册任意功能:

observable.register { print("Unowned closure received \($0)") } 
在同一个接收器个

或注册两个不同的功能:

extension IntReceiverClass { 
    func recieveIntAgain(i: Int) { 
     print("Class recevied \(i) slightly differently") 
    } 
} 

observable.register(intReceiver.recieveIntAgain) 

现在,当你触发事件:

observable.notifyObservers(42) 

将得到以下的输出:

Class received 42 
Unowned closure received 42 
Class recevied 42 slightly differently 

但与此技术,如果您尝试注册错误事件类型的函数,则会出现编译错误:

observable.register(IntReceiverClass.receiveString) 
// error: cannot invoke 'register' with an argument list of type '(IntReceiverClass -> (String) ->()) 
+0

非常感谢你提供了这个非常有趣和有启发性的答案。是的,旧的C++程序员确实相信他以与实例化C++模板相同的方式引入了两种不同的类型。 只是为了澄清我的第二个问题的一点。 Swift不允许我使用'Observable'协议作为变量或函数参数。我是否正确地说,这使得我无法使用一对协议,例如我在示例中使用的'ProtocolObserver'和'ProtocolObservable'? –

+0

是的,没有部分专业化是使用Swift访问C++开发人员的东西。请参阅[这个答案](http://stackoverflow.com/a/30483738/3925941)关于你不能使用'ProtocolObserver'作为参数的原因的更多信息,因为它有一个关联的类型。 –

+0

@Airspeed你可以请解释一下,'observable.register(intReceiver.receiveInt)'工作如何?我相信这个方法接受一个函数(严格地说,指向代码段中的某个内存的指针),而不需要任何关联的数据。我错了吗? 'observable'如何捕获'intReceiver'来稍后调用一个方法? – slashdot