问题 Swift协议要求,只能通过使用最终类来满足


我在Swift上为owner / ownee方案建模:

class Owner<T: Ownee> {
     // ...
}

protocol Ownee {
    var owner: Owner<Self> { get }
}

然后我有一对班级教授/学生坚持上面的建模类型:

class Professor: Owner<Student> {
    // ...
}

class Student: Ownee {
    let professor: Professor
    var owner: Owner<Student> {  // error here (see below)
        return professor
    }

    init(professor: Professor) {
        self.professor = professor
    }
}

但是我在定义上得到以下错误 var owner 在里面 Student 类:

协议'Ownee'要求'所有者'不能满足   非最终类('学生'),因为它在非参数中使用'Self',   非结果类型的位置

我试图了解这个错误的原因是什么,为什么要上课 Student 最终会修复它,如果有一些解决方法能够以不同的方式对其进行建模,而不是使这个类最终。我已经用Google搜索了这个错误,但到目前为止还没有找到太多。


11016
2018-05-10 14:13


起源

如果发生了什么 Student 是子类?该 owner财产依旧 Owner<Student>但是 Student != StudentSubclass - Alexander
@AMomchilov这是真的,但这是一个问题吗?如果是这样,为什么? - luk2302
是的,因为 Student 符合 Ownee 协议,其类型约束表明 owner 必须是一个 Owner<Self>,哪里 Self 是指符合的类型, Student。子类化会使这个合同变得迟钝,因此不能被允许,因此编译器会建议你这样做 Student 最后。 - Alexander


答案:


错误是正确的。你必须让你的类最终,因为没有子类可以符合你的协议 Ownee

考虑这个子类:

class FirstGradeStudent: Student {
   // inherited from parent
   // var owner: Owner<Student> {
   //     return professor
   //  }
}

如您所见,它必须实现 var owner: Owner<Student> 因为他的父母,但它应该实施 var owner: Owner<FirstGradeStudent> 相反,因为协议包含 var owner: Owner<Self> { get } 在这种情况下 Self 将会 FirstGradeStudent

解决方法

1: 定义一个超类 Ownee,它应该被使用 Owner

class Owner<T: OwneeSuper> {
    // ...
}

protocol OwneeSuper {}    
protocol Ownee: OwneeSuper {
    associatedtype T: OwneeSuper
    var owner: Owner<T> { get }
}

OwneeSuper 只是一个需要克服的解决方法 这个问题,否则我们只会使用:

protocol Ownee {
    associatedtype T: Ownee
    var owner: Owner<T> { get }
}

2。 在符合的类中 Ownee,你必须转动抽象类型 associatedtype 通过定义一个具体的类 typealias

class Student: Ownee {
    typealias T = Student // <<-- define the property to be Owner<Student>
    let professor: Professor
    var owner: Owner<T> { 
        return professor
    }

    init(professor: Professor) {
        self.professor = professor
    }
}

3。 子类现在可以使用属性,该属性将是您定义的类型:

class FirstGradeStudent: Student {
    func checkOwnerType() {
        if self.owner is Owner<Student> { //warning: 'is' test is always true
            print("yeah!")
        }
    }
}

7
2018-05-10 14:32



感谢您的回答。我想知道你是否知道建模我想要的解决方法。我想要 Ownee 对象有一个引用 Owner 他们所属的实例。我的意图是曾经定义过 Student,所有者永远是类型 Owner<Student> 这显然可以是。的所有者 Student 实例以及子类的实例 Student。我期望 FirstGradeStudent().owner 也是类型 Owner<Student> 太。 - Ernesto
@Ernesto我添加了一个解决方法 - Daniel
很好的故障,干得好 - Alexander
论坛链接已经死了 - Andrii Chernenko
该链接适合我... - Daniel


如果发生了什么 Student 是子类?业主财产仍然存在 Owner<Student>但是 Student != StudentSubclass

通过制作你的 Student 班级符合 Ownee 协议,您必须满足协议的合同。该 Ownee 协议声明 Owner 有类型约束,这样的 Owner 泛型类型是符合的类型 Ownee (Student, 在这种情况下)。

如果编译器允许子类化(即允许你不进行子类化) Student 最后),那么就有可能了 StudentSubclass 存在。这样的子类将继承 Owner 财产,类型 Owner<Student>但是 Student 是不一样的 StudentSubclass。该 Ownee 协议的合同已被破坏,因此,不允许存在这样的子类。


4
2018-05-10 14:33



好的,我知道了。但有没有一种解决方法来建模我打算建模的东西?我曾经期待过这一次 owner 财产满意 Student,然后从它继承的类只会使用它,那 StudentSubclass(professor: p).owner === p 会举行。 - Ernesto
您可以将类型约束更改为 T: Self,以便符合类型的子类变得可以接受。什么时候 owner 很满意 Student,它必须是一个 Owner<Student> 类型。如果不破坏通用类型的要求,就不能存在子类 Owner 与符合类型相同 - Alexander
我实际上尝试过,显然它不支持该上下文中的表示法。它给出了错误 Expected '>' to complete generic argument list 在哪里 : 位于 Owner<T: Self>。 - Ernesto
类型约束(“<T: Self>“)进入协议声明,而只是通用类型(”<T>“)用于定义 Owner。这是一个它应该是什么样子的例子: stackoverflow.com/q/24089145/3141234 - Alexander
@Ernesto是的,你是对的,协议不支持泛型类型,只支持关联类型 - Daniel


以下语法应该支持您所追求的内容:

protocol Ownee {
    associatedtype Owned = Self where Owned:Ownee
    var owner: Owner<Owned> { get }
}

(使用Swift 4测试 - Beta 1)


3
2017-08-03 10:41





如果你来这个线程只是想找到一种方法让子类继承一个引用它自己的类型(Self类型)的方法,一个解决方法是参数化子类型的父类。例如

class Foo<T> {
    func configure(_ block: @escaping (T)->Void) { }
}

class Bar: Foo<Bar> { }

Bar().configure { $0... }

但是......似乎您可以在协议扩展中添加一个方法,该方法不会被认为符合协议本身。我不完全理解为什么会这样:

protocol Configurable {
    // Cannot include the method in the protocol definition
    //func configure(_ block: @escaping (Self)->Void)
}

public extension Configurable {
    func configure(_ block: @escaping (Self)->Void) { }
}

class Foo: Configurable { }

class Bar: Foo { }

Bar().configure { $0... }

如果您取消对协议定义本身中的方法的注释,您将获得 Protocol 'Configurable' requirement 'configure' cannot be satisfied by a non-final class ('Foo') because it uses 'Self' in a non-parameter, non-result type position 编译错误。


0
2018-06-15 16:36