【问题标题】:A Swift protocol requirement that can only be satisfied by using a final class一个 Swift 协议要求,只能通过使用 final 类来满足
【发布时间】:2016-09-05 13:57:30
【问题描述】:

我正在为 Swift 上的所有者/所有者方案建模:

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

但是我在Student 类中对var owner 的定义出现以下错误:

协议 'Ownee' 要求 'owner' 不能满足 非最终类('Student'),因为它在非参数中使用'Self', 非结果型职位

我试图了解导致此错误的原因是什么,为什么将 Student 类设为 final 会修复它,以及是否有一些解决方法能够以不同的方式对此进行建模,而不会使此类成为最终类。我已经用谷歌搜索过这个错误,但到目前为止还没有找到太多。

【问题讨论】:

  • 如果Student 被子类化会怎样? owner 属性仍然是 Owner&lt;Student&gt;,但 Student != StudentSubclass
  • @AMomchilov 这是真的,但这是个问题吗?如果是这样,为什么?
  • 是的,因为Student 符合Ownee 协议,其类型约束规定owner 必须是Owner&lt;Self&gt;,其中Self 指的是符合类型, Student。子类化会违反这个契约,因此不能被允许,所以编译器建议你将Student设为final。

标签: swift oop generics swift-protocols


【解决方案1】:

如果您来到这个线程只是想寻找一种方法让子类继承一个引用其自身类型(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 编译错误。

【讨论】:

    【解决方案2】:

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

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

    (使用 Swift 4 - Beta 1 测试)

    【讨论】:

    • 这应该是公认的答案。
    【解决方案3】:

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

    考虑这个子类:

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

    如您所见,由于他的父母,它必须实现var owner: Owner&lt;Student&gt;,但它应该实现var owner: Owner&lt;FirstGradeStudent&gt;,因为协议包含var owner: Owner&lt;Self&gt; { get },在这种情况下Self将是FirstGradeStudent .

    解决方法

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

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

    OwneeSuper 只是克服this problem 的一种解决方法,否则我们只会使用:

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

    2.在符合Ownee的类中,必须通过定义typealiasassociatedtype的抽象类型转为具体类:

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

    【讨论】:

    • 感谢您的回答。我想知道您是否知道为我打算建模的解决方法。我希望 Ownee 对象能够引用它们所属的 Owner 实例。我的意图是一旦在Student 中定义,所有者将始终是Owner&lt;Student&gt; 类型,它显然可以是Student 实例的所有者,也可以是Student 的子类实例的所有者。我预计FirstGradeStudent().owner 也将是Owner&lt;Student&gt; 类型。
    【解决方案4】:

    如果Student 被子类化会发生什么?所有者属性仍然是 Owner&lt;Student&gt;,但 Student != StudentSubclass

    通过使您的Student 类符合Ownee 协议,您必须满足协议的约定。 Ownee 协议声明 Owner 具有类型约束,因此 Owner 泛型类型是符合 Ownee 的类型(在本例中为 Student)。

    如果编译器允许子类化(即允许您不将 Student 设为 final),那么 StudentSubclass 就有可能存在。这样的子类将继承Owner&lt;Student&gt; 类型的Owner 属性,但StudentStudentSubclass 不同。 Ownee协议的约定已经被破坏,因此不能允许这样的子类存在。

    【讨论】:

    • 好的,知道了。但是,是否有一种解决方法可以对我打算建模的内容进行建模?我期待一旦owner 属性被Student 满足,那么从它继承的类就会使用它,并且StudentSubclass(professor: p).owner === p 将保持不变。
    • 您可以将类型约束更改为T: Self,以便符合类型的子类变得可以接受。当owner 满足Student 时,它必须是Owner&lt;Student&gt; 类型。如果不打破Owner 的泛型类型与符合类型相同的要求,就不能存在子类
    • 我实际上尝试过,显然它不支持这种情况下的表示法。它给出了错误Expected '&gt;' to complete generic argument list,其中: 位于Owner&lt;T: Self&gt;
    • 类型约束 ("&lt;T: Self&gt;") 在协议声明中,而在 Owner 的定义中仅使用泛型类型 ("&lt;T&gt;")。这是一个应该是什么样子的示例:stackoverflow.com/q/24089145/3141234
    • @Ernesto 是的,你是对的,协议不支持泛型类型,只支持关联类型
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多