Why is "@objc" required for protocol conformance validation in Swift?

Apple 's Swift Programming Language Guide states that

You can only check for protocol compliance if your protocol is marked with the attribute @objc

Why is this necessary if I am not interacting with Objective-C?

+3


source to share


2 answers


UPDATE for Swift 1.2

  • As RyanM pointed out, there are language changes that remove the need for a keyword @objc

    .

Indeed, the following simple example now works without the keyword @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

      

Also, the link now looks something like this:

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)

      

ORIGINAL:

Let's take a look at an example. Note that I also used extra calls (varName as AnyObject)

, otherwise the compiler would complain 'is' test is always true

- as it knew exactly what type it was at compile time.

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("")

      

... and (without terminating the broken code test cases) the output is:

swifty

swiftyClass

swiftyConformanceOnly
swiftyConformanceOnly is SwiftyConformance
scon

swiftyConformanceWithOptional
swiftyConformanceWithOptional is SwiftyConformance
sconwo
a: 1

      

Well, the simple answer is similar to the state of the documents: you need @objc

to test compliance with the protocol (and variants ).

In Swift, objc is just a declaration attribute that usually represents a hint to the compiler or modifies the way the code is generated.

But the longer answer then asks the question, "But why is the language or runtime written this way?" And it's harder to address; my guess is that the attribute @objc

will generate valid Objective-C object / protocol references, and conformance testing is simply implemented using what is done at runtime.



You can comment out the code in the above example between / * and * / one at a time and see when and where the compiler is complaining.

UPDATE: Compiler and Linker Updates

If we compile the above: xcrun swiftc -sdk $(xcrun --show-sdk-path --sdk macosx) protocol-conformance.swift

and see what it refers to otool -L protocol-conformance

, we see

compliance protocol:

/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)

      

... so I think it's fairer to say that you need to interact with the Objective-C runtime to run these conformance tests, but I won't say you need to interact with Objective-C (which for me implies some amount of objc code. which you should write down).

Take a look at a very simple program using the protocol:

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)

      

... but if just try to implement the @objc protocol without any changes:

@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 {
// ^~~~

      

... and then if we import 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)

      

I would even say that the Swift standard library and runtime absolutely use the Objective-C runtime and expect access to deacto core Objective-C frameworks like Foundation for core functionality.

+8


source


Swift has evolved from the accepted answer and now the answer is:

This is not true.

From Rapid Programming Language :



You can use the is and as operators described in Type Casting to check if a protocol is compliant and to apply a specific protocol. Checking and shutting down to the protocol should be exactly the same as checking and casting for type ...

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

      

+1


source







All Articles