【问题标题】:Why is '@objc' required to check for protocol conformance in Swift?为什么需要“@objc”来检查 Swift 中的协议一致性?
【发布时间】:2014-10-24 18:27:24
【问题描述】:

Apple 的 Swift Programming Language Guide 声明

只有在您的协议被标记时,您才能检查协议的一致性 带有@objc 属性

如果我不与 Objective-C 互操作,为什么需要这样做?

【问题讨论】:

    标签: objective-c oop swift


    【解决方案1】:

    Swift 1.2 更新

    • 正如 RyanM 所指出的,有些语言的变化已经消除了对 @objc 关键字的需求。

    确实,以下简单示例现在可以在没有 @objc 关键字的情况下使用:

    protocol Ap {
        func hello()
    }
    
    class A: Ap {
        func hello() {
            println("hello, world")     
        }
    }
    
    var a = A()
    if (a as AnyObject) is Ap {
        a.hello()
    } else {
        println("nope")
    }
    
    // hello, world
    

    此外,链接现在看起来像这样:

    protocol-conformance-1-2:
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
        /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
        @rpath/libswiftCore.dylib (compatibility version 0.0.0, current version 0.0.0)
    

    原文:

    让我们看一个例子。请注意,我还使用了额外的(varName as AnyObject) 调用,否则编译器会报错'is' test is always true——因为它在编译时确切地知道类型是什么。

    import Foundation
    
    protocol Swifty {
        func s()
        //  protocol-conformance.swift:5:2: error: 'optional' can only be applied to members of an @objc protocol
        //          optional var a: Int { get }
        //          ^
        /*
        optional var a: Int { get }
        */
    }
    
    protocol SwiftyClass: class {
        func scl()
        //  protocol-conformance.swift:13:2: error: 'optional' can only be applied to members of an @objc protocol
        //          optional var a: Int { get }
        //          ^
        /*
        optional var a: Int { get }
        */
    }
    
    @objc protocol SwiftyConformance {
        func scon()
        optional var a: Int { get }
    }
    
    class SwiftyOnly: Swifty {
        func s() {
            println("s")
        }
    }
    class SwiftyClassOnly: SwiftyClass {
        func scl() {
            println("scl")
        }
    }
    class SwiftyConformanceOnly: SwiftyConformance {
        func scon() {
            println("scon")
        }
    }
    class SwiftyConformanceWithOptional: SwiftyConformance {
        func scon() {
            println("sconwo")
        }
        var a: Int {
            get { return 1; }
        }
    }
    
    println("swifty")
    var swifty = SwiftyOnly()
    //protocol-conformance.swift:49:26: error: cannot downcast from 'AnyObject' to non-@objc protocol type 'Swifty'
    //if (swifty as AnyObject) is Swifty {
    //   ~~~~~~~~~~~~~~~~~~~~~ ^  ~~~~~~
    /*
    if (swifty as AnyObject) is Swifty {
        println("swifty is Swifty")
    }
    */
    //  protocol-conformance.swift:47:34: error: cannot downcast from 'AnyObject' to non-@objc protocol type 'Swifty'
    //  if let s = (swifty as AnyObject) as? Swifty {
    //             ~~~~~~~~~~~~~~~~~~~~~ ^   ~~~~~~
    /*
    if let s = (swifty as AnyObject) as? Swifty {
        s.s()
    }
    */
    println("")
    
    println("swiftyClass")
    var swiftyClass = SwiftyClassOnly()
    //protocol-conformance.swift:61:31: error: cannot downcast from 'AnyObject' to non-@objc protocol type 'SwiftyClass'
    /*
    if (swiftyClass as AnyObject) is SwiftyClass {
        println("swiftyClass is SwiftyClass")
    }
    */
    //protocol-conformance.swift:80:39: error: cannot downcast from 'AnyObject' to non-@objc protocol type 'SwiftyClass'
    //if let s = (swiftyClass as AnyObject) as? SwiftyClass {
    //           ~~~~~~~~~~~~~~~~~~~~~~~~~~ ^   ~~~~~~~~~~~
    /*
    if let s = (swiftyClass as AnyObject) as? SwiftyClass {
        s.scl()
    }
    */
    println("")
    
    println("swiftyConformanceOnly")
    var swiftyConformanceOnly = SwiftyConformanceOnly()
    if (swiftyConformanceOnly as AnyObject) is SwiftyConformance {
        println("swiftyConformanceOnly is SwiftyConformance")
    }
    if let s  = (swiftyConformanceOnly as AnyObject) as? SwiftyConformance {
        s.scon()
        if let a = s.a? {
            println("a: \(a)")
        }
    }
    println("")
    
    println("swiftyConformanceWithOptional")
    var swiftyConformanceWithOptional = SwiftyConformanceWithOptional()
    if (swiftyConformanceWithOptional as AnyObject) is SwiftyConformance {
        println("swiftyConformanceWithOptional is SwiftyConformance")
    }
    if let s  = (swiftyConformanceWithOptional as AnyObject) as? SwiftyConformance {
        s.scon()
        if let a = s.a? {
            println("a: \(a)")
        }
    }
    println("")
    

    ...并且(不取消注释损坏的代码测试用例),输出为:

    swifty
    
    swiftyClass
    
    swiftyConformanceOnly
    swiftyConformanceOnly is SwiftyConformance
    scon
    
    swiftyConformanceWithOptional
    swiftyConformanceWithOptional is SwiftyConformance
    sconwo
    a: 1
    

    因此,简单的答案就像文档所述:protocol conformance testing(和optionals)需要@objc

    在 Swift 中,objc 只是一个声明属性,一般表示对编译器的提示或修改代码的生成方式。

    但是更长的答案则引出了一个问题:“但是为什么要以这种方式编写语言或运行时?”,这更难解决;我的猜测是@objc 属性会生成真正的Objective-C 对象/协议引用,而一致性测试只是在运行时使用它来实现。

    您可以在上面示例中的代码中一次注释掉 /* 和 */ 之间的代码,并查看编译器何时何地抱怨。

    更新:编译器和链接器更新

    如果我们编译上面的代码:xcrun swiftc -sdk $(xcrun --show-sdk-path --sdk macosx) protocol-conformance.swift 并检查它与 otool -L protocol-conformance 的链接,我们会看到

    协议一致性:

    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
    /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
    @rpath/libswiftCore.dylib (compatibility version 0.0.0, current version 0.0.0)
    @rpath/libswiftCoreGraphics.dylib (compatibility version 0.0.0, current version 0.0.0)
    @rpath/libswiftDarwin.dylib (compatibility version 0.0.0, current version 0.0.0)
    @rpath/libswiftDispatch.dylib (compatibility version 0.0.0, current version 0.0.0)
    @rpath/libswiftFoundation.dylib (compatibility version 0.0.0, current version 0.0.0)
    @rpath/libswiftObjectiveC.dylib (compatibility version 0.0.0, current version 0.0.0)
    @rpath/libswiftSecurity.dylib (compatibility version 0.0.0, current version 0.0.0)
    

    ...所以我认为更公平地说,要进行这些一致性测试,您需要与 Objective-C 运行时交互,但我不一定说您需要与 Objective-C 交互(对我来说意味着你必须故意编写一些 objc 代码)。

    看一个非常简单的协议使用程序:

    protocol Ap {
        func hello()
    }
    class A: Ap {
        func hello() {
            println("hello, world")     
        }
    }
    var a = A()
    a.hello()
    
    //$ otool -L hello-world
    //hello-world:
    //  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
    //  /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
    //  @rpath/libswiftCore.dylib (compatibility version 0.0.0, current version 0.0.0)
    

    ...但如果只是尝试实现 @objc 协议而不进行其他更改:

    @objc protocol Ap {
        func hello()
    }
    class A: Ap {
        func hello() {
            println("hello, world")     
        }
    }
    var a = A()
    a.hello()
    
    //$ xcrun swiftc -sdk $(xcrun --show-sdk-path --sdk macosx) hello-world.swift 
    //hello-world.swift:1:2: error: @objc attribute used without importing module 'Foundation'
    //@objc protocol Ap {
    // ^~~~
    

    ...然后如果我们导入 Foundation:

    import Foundation
    
    @objc protocol Ap {
        func hello()
    }
    class A: Ap {
        func hello() {
            println("hello, world")     
        }
    }
    var a = A()
    a.hello()
    
    //$ otool -L hello-world
    //hello-world:
    //  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
    //  /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
    //  @rpath/libswiftCore.dylib (compatibility version 0.0.0, current version 0.0.0)
    //  @rpath/libswiftCoreGraphics.dylib (compatibility version 0.0.0, current version 0.0.0)
    //  @rpath/libswiftDarwin.dylib (compatibility version 0.0.0, current version 0.0.0)
    //  @rpath/libswiftDispatch.dylib (compatibility version 0.0.0, current version 0.0.0)
    //  @rpath/libswiftFoundation.dylib (compatibility version 0.0.0, current version 0.0.0)
    //  @rpath/libswiftObjectiveC.dylib (compatibility version 0.0.0, current version 0.0.0)
    //  @rpath/libswiftSecurity.dylib (compatibility version 0.0.0, current version 0.0.0)
    

    我什至会说 Swift 标准库和运行时绝对利用了 Objective-C 运行时,并期望访问事实上的核心 Objective-C 框架,如 Foundation 的核心功能。

    【讨论】:

    • 那么 TL;DR 是因为协议一致性测试是在 Objective-C 运行时(而不是在 Swift 中)实现的吗?换句话说——这是关键——协议一致性检查是否与Objective-C交互(即使“我不是”)?
    • @raxacoricofallapatorius 有点——我在上面更新了我的观点,但答案基本上是“是”——你必须与 Objective-C 运行时交互。
    • @RyanM:鉴于(reported) language changes,暂时不接受。
    • @raxacoricofallapatorius 感谢您的提醒——新的语言功能让事情变得更加简单。我用一个简单的例子更新了我的答案来反映这一点。
    【解决方案2】:

    自从接受答案以来,Swift 已经进化,现在的答案是:

    不是。

    来自The Swift Programming Language

    您可以使用类型转换中描述的 is 和 as 运算符来检查协议一致性,并转换为特定协议。检查并转换为协议遵循与检查并转换为类型完全相同的语法...

    protocol PersonBasedView {
    
        var person: Person? {get set}
    }
    
    class EmployeeView : UIView, PersonBasedView {
    
        var person: Person?
    }
    
    // Elsewhere
    
    var view = ...
    
    if view is PersonBasedView { ... }
    
    // or
    if let personView = view as? PersonBasedView { ... }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-02
      • 2014-11-09
      • 2019-12-31
      • 2021-10-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多