【问题标题】:Cyclic loop between protocols in swiftswift中协议之间的循环循环
【发布时间】:2017-07-25 16:01:35
【问题描述】:

我正在尝试在 Swift 文件中定义一些协议,但是我注意到如果这些协议有交叉引用,XCode 就会出现错误,并且无法使用该项目。使用的协议示例可能如下所示:

protocol VIPERPresenterProtocol
{
    var view: VIPERViewProtocol? { get set }
    var interactor: VIPERInteractorInputProtocol? { get set }
    var wireFrame: VIPERWireFrame? { get set }

    //    /* Add your extra communication methods here */
    //    /* Presenter -> ViewController */
}

protocol VIPERViewProtocol
{
    var presenter: VIPERPresenterProtocol? { get set }
}

VIPERPresenterProtocol 引用了 VIPERViewProtocol,而这最后一个引用了 VIPERPresenterProtocol。

这在 Objective-C 中有效,但 Swift 不喜欢。我的问题是,这是 Apple 不希望在 Swift 中支持的东西吗,如果它可能是 Swift 语言的一个错误,或者我应该以任何其他方式实现它。

【问题讨论】:

    标签: swift protocols


    【解决方案1】:

    您的问题有以下形式:

    protocol A
    {
        var b: B? { get set }
    }
    
    protocol B
    {
        var a: A? { get set }
    }
    

    然而,虽然这两个协议声明都可以编译,但任何实现它们的尝试都不会:

    protocol A {
        var b: B? { get set }
    }
    
    protocol B {
        var a: A? { get set }
    }
    
    // on their own, the two protocol declerations compile
    
    class Ca: A { // Does not compile: --> Segmentation fault 11
        var b: B?
    }
    

    如果我们打破循环,这不会发生:

    protocol A {}
    
    protocol B {
        var a: A? { get set }
    }
    
    class Ca: A {}
    
    class Cb: B {
        var a: A?
    }
    
    let cb = Cb()   // --> {nil}
    cb.a = Ca()     // --> {{Ca}}
    cb.a            // --> {Ca}
    

    或者如果我们根据具体类型来定义这两个协议:

    protocol A {
        var b: Cb? { get set }
    }
    
    protocol B {
        var a: Ca? { get set }
    }
    
    class Ca: A {
        var b: Cb?
        let i = "A"
    }
    
    class Cb: B {
        var a: Ca?
        let i = "B"
    }
    
    let cb = Cb()   // --> {nil "B"}
    cb.a = Ca()     // --> {{{...}} "B"}
    cb.a            // --> {{nil "A"}}
    cb.a!.b = Cb()  // --> {{{...} "A"}}
    

    我的印象是类型检查器还不能处理递归类型声明。在这一点上,它是否会实现,甚至对于 Apple 来说都是一个悬而未决的问题,但是,由于明确意图促进功能性习语,这至少是一种可能性。

    我之前回答过similar question,但在@newacct 的帮助下我最终意识到,这并不完全相同。

    编辑

    我刚刚升级到 Xcode 6.1 GM Seed,情况发生了变化!下面的 sn-p 现在可以编译并且运行良好!

    protocol A {
        var b: B? { get set }
    }
    
    protocol B {
        var a: A? { get set }
    }
    
    class Ca: A {
        var b: B?
    }
    
    class Cb: B {
        var a: A?
    }
    
    let a = Ca()    // --> {nil}
    let b = Cb()    // --> {nil}
    
    a.b = b         // --> {{{...}}}
    b.a = a         // --> {{{...}}}
    

    (但是,此改进并未扩展到recursively defined associated types。)

    【讨论】:

    • 编译器是对的。 Cb<Ta> 不符合协议 B。要符合协议B,它必须具有A? 类型的属性aCb<Ta> 具有 a 类型的 Ta? 属性。不匹配。
    • 我已经简化了代码,所以甚至应该清楚这两个类CaCb(递归地)分别符合协议AB。 (Cb 通过实现一个变量a 声明符合协议B,该变量的类型符合协议A,尽管它又依赖于协议B。)目前,类型检查器无法处理有了这种类型定义的相互依赖...
    • 编译器仍然正确,原因相同。 Cb 不符合协议B,因为要符合协议B,它必须具有A? 类型的属性aCb 有一个属性 ``a 类型为 Ca?。不匹配。
    • 你说得对!由于我粗心地假设您的问题与我提到的其他问题基本相同,因此我产生了该错误。你坚持的“它不匹配”终于到达了我的身边。事实上,它帮助我完善了对一般问题的理解。请查看更新后的答案。
    • @milos 那么我无法知道我的实现有什么不同。对我来说,XCode 6.0 无法处理协议之间的交叉关系,它会在屏幕中间显示众所周知的警报。那么这仅适用于 XCode 6.1 吗??
    【解决方案2】:

    我也尝试为 VIPER 构建类似的东西,但遇到了同样的问题。

    我为 milos 的好答案添加了一些内容:即使使用关联类型,我也成功地打破了循环,使用了几个支持协议。

    protocol _VIPERViewProtocol {
    }
    
    protocol VIPERViewProtocol : _VIPERViewProtocol {
        typealias P:_VIPERPresenterProtocol
        var presenter: P! {get set}
    }
    
    protocol _VIPERPresenterProtocol {
    
    }
    
    protocol VIPERPresenterProtocol : _VIPERPresenterProtocol {
        typealias W:_VIPERViewProtocol
        var view: W! {get set}
    }
    

    这样做的好处是可以让编译器推断出关联的presenterview 的正确类型,例如:

    class BasePresenter : VIPERPresenterProtocol {
        var view : BaseView!
    }
    
    class BaseView : VIPERViewProtocol {
        var presenter: BasePresenter!
    }
    
    var p = BasePresenter()
    // p.view is correctly recognized as BaseView!
    

    只要您使用关联类型,这就是有效的。由于 Swift 中的属性没有 covariance,因此如果您这样更改,上述代码将无法编译:

    protocol VIPERViewProtocol : _VIPERViewProtocol {
        var presenter: _VIPERPresenterProtocol! {get set}
    }
    

    无论如何,回到 VIPER 架构,这只有助于为具体类提供它们必须实现的模板。 如果我们可以定义一个“构建器”方法,该方法采用符合协议的通用对象(线框、演示器..)并将所有组件连接起来,那将更加有用。

    不幸的是,这样的事情不起作用:

    func builder(p:VIPERPresenterProtocol, v:VIPERViewProtocol) {
        p.view = v
        v.presenter = p
    }
    

    编译器抱怨Protocol 'VIPERViewProtocol' can only be used as a generic constraint because it has Self or associated type requirements

    也许要探索的一种解决方案是泛型,但我仍然需要考虑。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-02
      • 2019-09-08
      • 2011-04-07
      • 2019-06-03
      • 2010-09-06
      相关资源
      最近更新 更多