Swift协议和泛型的限制 [英] Restrictions around Protocols and Generics in Swift

查看:76
本文介绍了Swift协议和泛型的限制的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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

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

协议ProtocolObservable {
typealias EventType
func registerObserver< O:ProtocolObserver其中O.EventType == EventType>(观察者:O) - > Bool
func unregisterObserver< O:ProtocolObserver where O.EventType == EventType>(observer:O) - >无效
}

尝试定义实现上述协议的类实际上是一个世界伤害。我没有找到任何方法来做到这一点。然而,实现一个通用的基类将是一个可接受的解决方案。

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

class GenericObservable< EventType> {
private var observers:[GenericObserver] = []
func registerObserver(观察者:GenericObserver) - > Bool {
//代码避免两次注册同一个观察者
observers.append(observer)
返回true
}
func unregisterObserver(观察者:GenericObserver) - > ; void {
//代码移除观察者(如果存在于观察者中)
}
func notifyObservers(event:EventType) - >无效{
观察者在观察者{
observer.update(self,event:event)
}
}
}

定义一些实现此协议的类没有问题。

  let numberObservable = GenericObservable< NSNumber>()将数据添加到通用观察器的实例中并不显示我期望的行为。 
$ b class NumberObserver:GenericObserver {
func update< NSNumber>(observable:GenericObservable< NSNumber> ;, event:NSNumber){
print(Number Event \(event)) )


$ b $让numberObserver = NumberObserver()

numberObservable.registerObserver(numberObserver)
$ b $ 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) COM编辑错误。相反,它愉快地打印输出

 数字事件42 
数据事件42


这给我留下了两个问题:


  1. 当我期望编译器不接受 numberObservable.registerObserver(dataObserver)


  2. 时,我误解了什么? p>是否有一种方法可以分别实现一对符合 ProtocolObserver ProtocolObservable 的类?



解决方案

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



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



首先,1.您遇到的问题是类型擦除。您的基类是您定义 registerObserver 的唯一地方,如下所示: class GenericObservable< EventType> {
private var observers:[GenericObserver] = []
func registerObserver(观察者:GenericObserver) - > Bool {
//代码避免两次注册相同的观察者
observers.append(观察者)
返回true
}
// ...
}

也就是说,它会将协议引用存储并存储到任何类型。对于什么类型没有限制,它可以是任何东西。例如,您可以通知 Int

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

numberObservable.registerObserver(2)

当被调用者尝试使用 EventType EventType 可以是 。它与此函数类似:

  func ff (t:T){} 

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

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



这可能有助于消除一些混淆 - 这确实不符合您的想法:

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

em> not 声明 DataObserver 特别需要 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->())
}
//不需要Observer协议

现在,我们来实现这个,修复 EventType 为一个 Int

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

//注意,这会通过参数
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 ()
//连接观察类以观察
observable.register(intReceiver.receiveInt)

但我们也可以注册任意函数:

  observable.register {print(Unowned closure received \ ($ 0))} 

或者在同一个接收器上注册两个不同的函数:

 扩展IntReceiverClass {
func recieveIntAgain(i:Int){
print(Class received | \\(i)略有不同)
}
}

observable.register(intReceiver.recieveIntAgain)

现在,当您触发事件时:

  observable.notifyObservers(42)

您将获得以下输出:

 班级收到42 
无主关闭收到42
类收到42略有不同

但使用这种技术,如果您尝试注册一个错误事件类型的函数,会得到一个编译错误:

$ $ $ $ $ $ $ c $ observable.register(IntReceiverClass.receiveString)
//错误:无法用类型为'(IntReceiverClass - >')的参数列表调用'register' (字符串) - > ())


There are scenarios when two interacting sets of generic classes is a good design pattern. A simple example is the Observable-Observer pattern. The observable posts events to the observer but the pattern is the same no matter what type of event that is being observed.

My first thought was that the preferred way would be to define two generic protocols. This should offer the smallest coupling, which tends to be good as the code base grows.

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
}

Trying to define classes that implement the above protocols turned out to be a world of hurt. I failed to find any way to do it.

Implementing a generic baseclass would, however, be an acceptable solution.

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)
        }
    }
}

No problem defining some classes that implement the protocol this time. Adding them to an instance of the generic observer did not show the behaviour I expected.

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))

I expected numberObservable.registerObserver(dataObserver) to cause a compilation error. Instead it happily printed the output

Number Event 42
Data Event 42

This all leaves me with two questions:

  1. What have I misunderstood when I expect the compiler to not accept numberObservable.registerObserver(dataObserver)?

  2. Is there a way to implement a pair of classes that conform to ProtocolObserver and ProtocolObservable respectively?

解决方案

Your questions, 1 and 2, are in fact strongly related.

Before getting started though, I should point out that when you have first-class functions, the observable/observer pattern is almost entirely redundant. Instead of mandating interfaces for callbacks, you can just supply a closure. I’ll show this in the answer to question 2.

First, 1. The problem you are experiencing is type erasure. Your base class is the only place in which you have defined registerObserver and it looks like this:

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
    }
   //...
}

That is, it will take and store a protocol reference to any type. There is no constraint on what that type is, it can be anything. For example, you could notify an Int:

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

numberObservable.registerObserver(2)

The problem will come when the callees try to use EventType. EventType could be anything. It is similar to this function:

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

T can be any type you like – a String, an Int, a Foo. But you will not be able to do anything with it, because it provides zero guarantees. To make a generic type useful, you have to either constrain it (i.e. guarantee it has certain features, like that it’s an IntegerType that can be added/subtracted for example), or pass it on to another generic function that similarly unconstrained (such as put it in a generic collection, or call print or unsafeBitCast which will operate on any type).

Basically, your observers have all declared "I have a family of methods, update, which you can call with any type you like". This is not very useful, unless you’re writing something like map or a generic collection like an array, in which case you don’t care what T is.

This might help clear up some confusion – this does not do what you think it does:

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

Here you have not declared that DataObserver specifically takes an NSData class. You’ve just named the generic placeholder NSData. Similar to naming a variable NSData – it doesn’t mean that is what the variable is, just that’s what you’ve called it. You could have written this:

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


Ok so how to implement an observable protocol with an associated type (i.e. a typealias in the protocol). Here’s an example of one. But note, there is no Observer protocol. Instead, Observable will take any function that receives the appropriate event type.

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

Now, let’s implement this, fixing EventType to be an 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()

Now, if we want to observe via a class, we can:

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

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

But we can also register arbitrary functions:

observable.register { print("Unowned closure received \($0)") }

Or register two different functions on the same receiver:

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

observable.register(intReceiver.recieveIntAgain)

Now, when you fire the events:

observable.notifyObservers(42)

you get the following output:

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

But with this technique, if you try to register a function of the wrong event type, you get a compilation error:

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

这篇关于Swift协议和泛型的限制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆