具有Self类型属性的协议只能用作通用约束,为什么?

时间:2016-06-10 12:56:29

标签: swift generics

无法使用具有Self属性的协议作为类型,容器类型,参数。 我想我需要一个有意义的例子,编译器无法推断出类型。

编译的定义

internal protocol Lovable {
    var inLoveTo: Self? {
        get
    }
}

internal final class Human: Lovable {
    var inLoveTo: Human? = nil
}

internal final class Animal: Lovable {
    var inLoveTo: Animal? = nil
}

internal let thing11: Human = Human()
internal let thing12: Animal = Animal()

不工作代码

但是下面的代码对我来说很有意义并且可以工作。所以必须有一种情况,你无法在编译时推断出类型,我还看不到它。

import Darwin

let thing13: Lovable = Darwin.random() % 2 == 0 ? thing11 : thing12 // So you do not know which dynamicType thing13 has at compile time, but it should be Lovable, error: mismatching types

thing13.inLoveTo // It could be Lovable

// Does not work, even though it makes sense for me, since inLoveTo adopts Lovable
internal func partner(one: Lovable) -> Lovable {
    return one.inLoveTo
}

我没看到什么?

工作示例在编译时您不知道类型

protocol Foo {
}

final class Bar1: Foo {
    let bla: Int8 = 100
}

final class Bar2: Foo {
    let bla: Int64 = 600000
}

internal let thing21: Foo = Bar1()
internal let thing22: Foo = Bar2()
internal let thing23: Foo = Darwin.random() % 2 == 0 ? thing21 : thing22 // So you do not know which type it has at compile time

3 个答案:

答案 0 :(得分:2)

Self指的是实现该协议的运行时类型。问题是当你有一个带Lovable输入的函数(或带有Lovable明确注释的变量)时,你需要向上转换为抽象类型Lovable

通过这样做,您将丢失Self所属的类型信息。是Animal还是Human?编译器需要这样才能将协议用作类型,因为它具有类型为Self的属性,无法解析。因此,这意味着您不能将Selfassociatedtype要求的协议用作实际类型,您只能将其用作通用约束。

一种可能的解决方案是将协议中的属性更改为Lovable类型。现在你要说的是,符合Lovable的所有内容都有“其他”的属性。 Lovable。你现在不需要知道具体类型的东西'某些东西'是的,虽然这会打破你想要建立的重要关系(两个伙伴必须属于同一类型!)

维持这种关系(至少对你的功能而言)的一种方法是使用泛型。使用泛型的原因是它们作为具体类型的占位符,在调用它时提供给函数。现在你的函数输入知道Self是什么 - 它是不是提供给函数的具体类型。

您可以使partner函数具有通用性:

func partner<T:Lovable>(one: T) -> T? {
    return one.inLoveTo
}

这也保证了函数的返回类型与输入具有相同的具体类型(尽管包装在可选中),从而提供更好的类型安全性。

不幸的是,对于您的变量分配,没有真正的解决方案不会涉及破坏您已建立的关系(inLoveTo必须与类相同)。您正试图将运行时决策(HumanAnimal)分配给编译时给出的静态具体类型,这种类型无效。

正如您所指出的,使用Self作为函数的返回类型并不能使协议具有通用性。我相信这是由于方差 - 因为你总是可以将子类传递给期望其超类的东西。因此,由于函数的返回类型将与实例的当前静态类型匹配,因此静态类型只能引用比实例的动态类型更不具体的类型。因此,您可以自由地返回具有相同动态类型的实例,因为描述它的静态类型只能减少类型特定。

此行为与属性不同,因为协议对其实现方式的控制有限。 {get}可以作为任何类型的属性实现,{get set}可以作为带有setter的存储或计算属性实现。在这两种情况下,都可以在符合要求的类中设置属性。现在我们遇到了原始问题。 Self是类的具体类型,因此我们必须知道具体类型才能分配给它,而在上传时会丢失它。

您不能简单地将该属性视为Loveable,因为这样可以为其分配任何符合要求的实例,即将Animal()分配给Human属性 - 这是非法的

答案 1 :(得分:2)

让我们一一去:

 let thing13: Lovable = Darwin.random() % 2 == 0 ? thing11 : thing12 

在这种情况下,作为读者,我们是有道理的。但是有一个缺点吗?首先,即使是静态赋值也不会像你那样编译。

 let thing13: Lovable = thing11

为什么不能有更深刻的含义。但是,以这种方式思考可能有意义开始。当编译器看到此代码时,它将尝试将thing11转换为类型Lovable。转换最终会指定构成此数据结构的位数。例如:

 int b = 10;
 char greeting[] = (char[]) b; //C

这样做你指定一个int [8 bit long]到[8 bit long] chars ==&gt;的数组。串。但是字符串可能没用,因为它可能包含垃圾。

这带我们去做另一件事。 Lovable不能单独定义来说明它将占用多大的内存位以及它可能具有的属性的内存结构。我们不知道偏移量,我们不知道这是一个值类型还是引用类型。实质上,内存结构并不完整。该类型被称为Generic Type.

这就是为什么你不能将具体值[bits]分配给一个未完全定义的结构/类型。

然而,这是我的推理而且我不是编译器人。如果有正确的解释,请在下面做评论。即使这没有意义,我也尽力了。

答案 2 :(得分:1)

@ originalUser2在上面描述了它。这是一种实现我认为您要求的方式,购买HumanAnimal一些共同的其他行为(protocol)。也许这个例子可以帮助你看到它?

internal protocol Lovable {
  var inLoveTo: LivingThing? { get }
}

extension Lovable {
  func partner() -> LivingThing? {
    return inLoveTo
  }
}

protocol LivingThing {
  var name: String? { get }
}

internal final class Human: LivingThing, Lovable {
  var inLoveTo: LivingThing? = nil
  var name: String?
}

internal final class Animal: LivingThing, Lovable {
  var inLoveTo: LivingThing? = nil
  var name: String?
}

internal let thing11 = Human()
thing11.name = "Wilson"
internal let thing12 = Animal()
thing12.name = "Fido"
internal let thing13 = Human()

thing13.inLoveTo = thing11
print(thing13.partner()?.name)  // "Wilson"
thing13.inLoveTo = thing12
print(thing13.partner()?.name)  // "Fido"
相关问题