首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >具有Self类型属性的协议只能用作泛型约束,为什么?

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

Stack Overflow用户
提问于 2016-06-10 12:56:29
回答 3查看 253关注 0票数 2

不可能使用以Self作为类型的属性作为类型、容器类型和参数的协议。我想我需要一个有意义的例子,编译器不能推断类型。

编译定义

代码语言:javascript
复制
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()

不工作代码

但是下面的代码对我来说是有意义的,并且可以工作。因此,必须有一种情况,您不能在编译时推断类型,而我还没有看到这种情况。

代码语言:javascript
复制
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
}

我没看到什么?

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

代码语言:javascript
复制
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
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2016-06-10 14:52:09

Self指的是实现该协议的任何东西的运行时类型。问题是,当您有一个接受Lovable输入的函数(或一个带有Lovable显式注释的变量)时,您将转换为抽象类型Lovable

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

一个潜在的解决方案是将协议中的属性更改为Lovable类型。现在您要说的是,所有符合Lovable的东西都有一个“其他”Lovable的属性。现在您不需要知道什么是“东西”的具体类型,尽管这会破坏您想要建立的重要关系(两个合作伙伴必须是同一类型的!)

维护这种关系的一种方法(至少对您的功能来说是这样)是使用泛型。使用泛型的原因是它们充当具体类型的占位符,当您调用它时,它被提供给函数。现在,您的函数输入知道了什么是Self --它是提供给函数的任何具体类型。

您可以使您的partner函数泛化如下:

代码语言:javascript
复制
func partner<T:Lovable>(one: T) -> T? {
    return one.inLoveTo
}

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

不幸的是,对于变量赋值,没有真正的解决方案不涉及破坏您已经建立的关系(inLoveTo必须与类的类型相同)。您正在尝试将运行时决策(HumanAnimal可能被分配)绑定到编译时给定的静态具体类型,该类型无法工作。

正如您已经注意到的,使用Self作为函数的返回类型并不会使协议具有普遍性。我相信这是由于变化--因为你总是可以把一个子类传递给期望它的超类的东西。因此,由于函数的返回类型将与实例的当前静态类型相匹配,因此静态类型只能引用比实例的动态类型更不特定的类型。因此,您可以自由地返回一个具有相同动态类型的实例,因为描述它的静态类型只能是较少的特定类型。

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

不能简单地将属性视为Loveable,因为这将允许向其分配任何符合条件的实例,即将Animal()分配给Human属性--这是非法的。

票数 2
EN

Stack Overflow用户

发布于 2016-06-10 14:57:49

让我们一个接一个地走:

代码语言:javascript
复制
 let thing13: Lovable = Darwin.random() % 2 == 0 ? thing11 : thing12 

在这种情况下,这对我们作为读者来说是有意义的。但有缺点吗?首先,即使是静态赋值也不会像您这样编译。

代码语言:javascript
复制
 let thing13: Lovable = thing11

还有一个更深层次的含义,为什么不呢?然而,以这种方式思考可能是有意义的开始。当编译器看到这段代码时,它将尝试将thing11转换为Lovable类型。强制转换最终将指定构成此数据结构的位数。例如:

代码语言:javascript
复制
 int b = 10;
 char greeting[] = (char[]) b; //C

为此,您可以将一个int 8位长指定为一个8位长字符==>字符串的数组。但是,该字符串可能并不有用,因为它可能包含垃圾。

这给我们带来了另一件事。可爱不能单独定义来说明它将占用多大的内存位,以及它可能拥有的属性的内存结构。我们将不知道偏移量,我们将不知道这是值类型还是引用类型。从本质上说,记忆结构是不完整的。这种类型据说是Generic Type.

这就是为什么不能将具体的值位分配给结构/类型,而这种结构/类型并不是完全孤立地定义的。

然而,这是我的推理,我不是一个编译器的家伙。如果有适当的解释,请做下面的评论。即使这不合理,我也尽力了。

票数 2
EN

Stack Overflow用户

发布于 2016-06-10 15:11:51

@originalUser2 2将其描述得很好。这里有一种实现我认为您所要求的东西的方法,购买给HumanAnimal一些共同的行为(protocol)。也许这个例子能帮你看清楚吗?

代码语言:javascript
复制
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"
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/37748877

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档