A Swift protocol requirement that can only be satisfied by using a final class

前端 未结 4 996
不知归路
不知归路 2020-12-05 18:41

I\'m modeling a owner/ownee scheme on Swift:

class Owner {
     // ...
}

protocol Ownee {
    var owner: Owner { get }
}
         


        
相关标签:
4条回答
  • 2020-12-05 19:17

    What happens if Student is subclassed? The owner property remains Owner<Student>, but Student != StudentSubclass.

    By making your Student class conform to the Ownee protocol, you must satisfy the contract of the protocol. The Ownee protocol states that Owner has type constraint, such that the Owner generic type is the type that's conforming to Ownee (Student, in this case).

    If the compiler were to allow subclassing (i.e. by allowing you to not make Student final), then it would be possible for a StudentSubclass to exist. Such a subclass would inherit the Owner property, of type Owner<Student>, but Student is not the same as StudentSubclass. The Ownee protocol's contract has been breached, thus, such a subclass cannot be allowed to exist.

    0 讨论(0)
  • 2020-12-05 19:18

    If you are coming to this thread just looking for a way to have subclasses inherit a method referring to its own type (type of Self) one workaround would be to parameterize the parent class on the child's type. e.g.

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

    BUT... it also seems that you can add a method in a protocol extension that would not be considered to conform in the protocol itself. I don't fully understand why this works:

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

    If you uncomment the method in the protocol definition itself you'll get the 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 compile error.

    0 讨论(0)
  • 2020-12-05 19:19

    The following syntax should support what you're after:

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

    (tested using Swift 4 - Beta 1)

    0 讨论(0)
  • 2020-12-05 19:37

    The Error is correct. You have to make your class final, since no subclasses could conform your protocol Ownee.

    Consider this subclass:

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

    As you can see, it would have to implement var owner: Owner<Student> because of his parent, but it should be implementing var owner: Owner<FirstGradeStudent> instead, because the protocol contains var owner: Owner<Self> { get } and in this case Self would be FirstGradeStudent.

    Workaround

    1: Define a superclass to Ownee, it should be used by Owner:

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

    OwneeSuper is just a workaround to overcome this problem, otherwise we would just be using:

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

    2. In classes that conform to Ownee, you must turn the abstract type of the associatedtype into a concrete class by defining a 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. Subclasses can now make use of the property, which will be of your defined type:

    class FirstGradeStudent: Student {
        func checkOwnerType() {
            if self.owner is Owner<Student> { //warning: 'is' test is always true
                print("yeah!")
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题