父子协议关系和ObjC可用性

时间:2017-01-03 11:44:47

标签: objective-c swift protocols

我无法使用以下代码:

@objc protocol Child { }

@objc protocol Parent {
    var child: Child { get }
}

class ChildImpl: Child { 
    func doSomething() { }
}

class ParentImpl: Parent {
    let child = ChildImpl()

    // this would solve the problem, however can't access the ChildImpl members
    // that are not part of the protocol
    // let child: Child = ChildImpl()

    // as well as this, however maintaining two properties is an ugly hack
    // var child: Child { return childImpl }
    // private let childImpl = ChildImpl()
}

我得到的错误:

  

Playground执行失败:错误:Tests.playground:3:7:错误:类型'ParentImpl'不符合协议'Parent'   class ParentImpl:Parent {         ^

     

Tests.playground:4:9:注意:候选人有不匹配类型'ChildImpl'       var child = ChildImpl()

基本上我有两个父子协议,以及两个实现这两个协议的类。但是,编译器仍然抱怨ChildImpl不是Child,这不是真的。

如果我在Parent

上使用关联类型,我可以使错误消失
protocol Parent {
    associatedtype ChildType: Child
    var child: ChildType { get }
}

,但我需要将协议提供给Objective-C,并且还需要能够引用child作为实际的具体类型。

是否有一个解决方案,不涉及重写Objective-C中的协议,或者不添加重复的属性声明只是为了避免这个问题?

5 个答案:

答案 0 :(得分:2)

我在评论中提到了link,显示了您尝试过的内容,使用关联类型或单独属性来完成协议一致性。我的事情Swift将很快支持来自let child: Child & ChildImpl = ChildImpl()等组合类型的推断类型,或仅child: ChildImpl,因为ChildImplChild。但在此之前,我认为我建议另外一种方法是将ChildImpl所需的apis分开,并将它们放在Child继承的单独协议中。这样,当Swift编译器支持此功能时,您不需要重构,只需将其删除即可。

// TODO: Remove when Swift keeps up.
@objc protocol ChildTemporaryBase { }
private extension ChildTemporaryBase {
    func doSomething() {
        (self as? ChildImpl).doSomething()
    }
}

@objc protocol Child: ChildTemporaryBase { }

class ParentImpl: Parent {
    let child: Child = ChildImpl()
    func testApi() {
        child.doSomething?()
    }
}

答案 1 :(得分:2)

您尝试执行的操作称为covariance,而swift不支持符合这些协议的协议或类/结构中的协方差。 您必须使用Type-ErassureassociatedTypes

protocol Child { }

protocol Parent {
    associatedtype ChildType: Child
    var child: ChildType { get }
}

class ChildImpl: Child {
    func doSomething() {
        print("doSomething")
    }
}

class ParentImpl: Parent {
    typealias ChildType = ChildImpl
    let child = ChildImpl()

    func test() {
        child.doSomething()
    }
}
ParentImpl().test() // will print "doSomething"

这里是Parent协议的一般用法的Type-Erased Parent:

struct AnyParent<C: Child>: Parent {
    private let _child: () -> C
    init <P: Parent>(_ _selfie: P) where P.ChildType == C {
        let selfie = _selfie
        _child = { selfie.child }
    }

    var child: C {
        return _child()
    }
}

let parent: AnyParent<ChildImpl> = AnyParent(ParentImpl())
parent.child.doSomething() // and here in Parent protocol level, knows what is child's type and YES, this line will also print "doSomething"

答案 2 :(得分:0)

如果您不介意将扩展属性添加到ParentImpl类:

@objc protocol Child {}

@objc protocol Parent {
    var child: Child! { get }
}

class ChildImpl: Child { }

class ParentImpl: Parent {
    var child: Child!
}

extension ParentImpl {

    convenience init(child: Child?) {
        self.init()
        self.child = child
    }

    var childImpl: ChildImpl! {
        get { return child as? ChildImpl }
        set { child = newValue }
    }

}

let parent = ParentImpl(child: ChildImpl())
let child = parent.child

答案 3 :(得分:0)

您的ParentImpl类没有子类型协议。我解决了这个问题。

class ParentImpl: Parent {
   var child: Child = ChildImpl()
}

答案 4 :(得分:0)

我发现了一个几乎不错的解决方案,现在有了Swift 5.1,就是使用属性包装器:

@propertyWrapper
struct Hider<T, U> {
    let wrappedValue: T

    init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }

    var projectedValue: U { return wrappedValue as! U }
}

@objcMembers class ParentImpl: NSObject, Parent {
    @Hider<Child, ChildImpl> var child = ChildImpl()
}

通过这种方式,child被公开为Child,而$child被公开为ChildImpl,从而允许从ParentImpl内部使用非协议成员。

该解决方案并不理想,因为我还找不到一种方法来描述T应该是U的超类型。