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

Ern*_*sto 15 oop generics swift swift-protocols

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

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

protocol Ownee {
    var owner: Owner<Self> { get }
}
Run Code Online (Sandbox Code Playgroud)

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

但是我var ownerStudent类中的定义上得到以下错误:

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

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

Dan*_*iel 14

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

考虑这个子类:

class FirstGradeStudent: Student {
   // inherited from parent
   // var owner: Owner<Student> {
   //     return professor
   //  }
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,它必须实现,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 }
}
Run Code Online (Sandbox Code Playgroud)

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

protocol Ownee {
    associatedtype T: Ownee
    var owner: Owner<T> { get }
}
Run Code Online (Sandbox Code Playgroud)

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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


Ale*_*ica 7

如果Student是子类,会发生什么?所有者财产仍然存在Owner<Student>,但是Student != StudentSubclass.

通过使您的Student类符合Ownee协议,您必须满足协议的合同.该Ownee协议规定,Owner已经类型约束,使得Owner一般类型是真实符合类型Ownee(Student在这种情况下).

如果编译器允许子类化(即允许你不做Student最终),那么a就有可能StudentSubclass存在.这样的子类将继承Owner类型的属性Owner<Student>,但Student不一样StudentSubclass.该Ownee协议的合同已经被破坏,因此,这样的子类不能被允许存在.


Dav*_*mes 7

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

protocol Ownee {
    associatedtype Owned = Self where Owned:Ownee
    var owner: Owner<Owned> { get }
}
Run Code Online (Sandbox Code Playgroud)

(使用 Swift 4 - Beta 1 测试)


Pat*_*yer 5

如果您来到这个线程只是想寻找一种方法让子类继承一个引用其自身类型(Self 类型)的方法,那么一种解决方法是在子类的类型上参数化父类。例如

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

class Bar: Foo<Bar> { }

Bar().configure { $0... }
Run Code Online (Sandbox Code Playgroud)

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

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... }
Run Code Online (Sandbox Code Playgroud)

如果您在协议定义本身中取消注释该方法,您将收到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编译错误。