This question was inspired by mz2's answer on the question Check for object type fails with "is not a type" error.
Consider an empty Swift class:
class MyClass { }
Attempting to call any NSObjectProtocol
methods on an instance of this class will result in a compile-time error:
let obj = MyClass()
obj.isKindOfClass(MyClass.self) // Error: Value of type 'MyClass' has no member 'isKindOfClass'
However, if I cast the instance as AnyObject
, my object now conforms to NSObjectProtocol
and I can call the instance methods defined by the protocol:
let obj: AnyObject = MyClass()
obj.isKindOfClass(MyClass.self) // true
obj.conformsToProtocol(NSObjectProtocol) // true
obj.isKindOfClass(NSObject.self) // false
My object doesn't inherit from NSObject
, but still conforms to NSObjectProtocol
. How does AnyObject
conform to NSObjectProtocol
?
In the Cocoa / Objective-C world, AnyObject is id
. Having cast this object to AnyObject, you can send any known Objective-C message to it, such as isKindOfClass
or conformsToProtocol
. Now, when you say isKindOfClass
or conformsToProtocol
, you're not in the Swift world any more; you're talking to Cocoa with Objective-C. So think about how Objective-C sees this object. All classes in the Objective-C world descend from some base class; a baseless class like MyClass is impossible. And every base class in the Objective-C world conforms to the NSObject protocol (which Swift calls NSObjectProtocol); that's what it is to be (or descend from) a base class! Therefore, to get it into the Objective-C world, Swift presents MyClass as descending from a special bridging base class SwiftObject which does indeed conform to NSObjectProtocol (as you can see here: https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftObject.mm).
If I'm understanding this correctly based on matt's answer, this works when Swift / Objective-C interop is available because in fact Swift class types ultimately inherit from SwiftObject
which when Objective-C interop is compiled in, actually involves an Objective-C class (SwiftObject is in implemented in SwiftObject.mm
which is compiled as Objective-C++ when Objective-C interop is used). So, casting a Swift class typed object as AnyObject kind of "leaks" that information.
Peeking at some relevant bits in the implementation from the Swift source code, file swift/stdlib/public/runtime/SwiftObject.mm
:
#if SWIFT_OBJC_INTEROP
// …
@interface SwiftObject<NSObject> {
SwiftObject_s header;
}
// …
@implementation SwiftObject
// …
- (BOOL)isKindOfClass:(Class)someClass {
for (auto isa = _swift_getClassOfAllocated(self); isa != nullptr;
isa = _swift_getSuperclass(isa))
if (isa == (const ClassMetadata*) someClass)
return YES;
return NO;
}
// …
// #endif
As predicted by this, with Swift 3 in Linux (where there's no Objective-C runtime available as part of the Swift runtime & Foundation implementation as far as I understand?) the example code from this question and the earlier question & answer that inspired this question fails with the following error compiler error:
ERROR […] value of type 'AnyObject' has no member 'isKindOfClass'
Adding some additional information to the already great answers.
I created three programs and looked at the generated assembly from each:
obj1.swift
import Foundation
class MyClass { }
let obj = MyClass()
obj2.swift
import Foundation
class MyClass { }
let obj: AnyObject = MyClass()
obj3.swift
import Foundation
class MyClass { }
let obj: AnyObject = MyClass()
obj.isKindOfClass(MyClass.self)
The differences between obj1 and obj2 are trivial. Any instructions that involve the type of the object have different values:
movq %rax, __Tv3obj3objCS_7MyClass(%rip)
# ...
globl __Tv3obj3objCS_7MyClass .globl __Tv3obj3objPs9AnyObject_
.zerofill __DATA,__common,__Tv3obj3objCS_7MyClass,8,3
# ...
.no_dead_strip __Tv3obj3objCS_7MyClass
vs
movq %rax, __Tv3obj3objPs9AnyObject_(%rip)
# ...
.globl __Tv3obj3objPs9AnyObject_
.zerofill __DATA,__common,__Tv3obj3objPs9AnyObject_,8,3
# ...
.no_dead_strip __Tv3obj3objPs9AnyObject_
Full diff here.
This was interesting to me. If the only differences between the two files are the names of the object type, why can the object declared as AnyObject
perform the Objective-C selector?
obj3 shows how the isKindOfClass:
selector is fired:
LBB0_2:
# ...
movq __Tv3obj3objPs9AnyObject_(%rip), %rax
movq %rax, -32(%rbp)
callq _swift_getObjectType
movq %rax, -8(%rbp)
movq -32(%rbp), %rdi
callq _swift_unknownRetain
movq -24(%rbp), %rax
cmpq $14, (%rax)
movq %rax, -40(%rbp)
jne LBB0_4
movq -24(%rbp), %rax
movq 8(%rax), %rcx
movq %rcx, -40(%rbp)
LBB0_4:
movq -40(%rbp), %rax
movq "L_selector(isKindOfClass:)"(%rip), %rsi
movq -32(%rbp), %rcx
movq %rcx, %rdi
movq %rax, %rdx
callq _objc_msgSend
movzbl %al, %edi
callq __TF10ObjectiveC22_convertObjCBoolToBoolFVS_8ObjCBoolSb
movq -32(%rbp), %rdi
movb %al, -41(%rbp)
callq _swift_unknownRelease
xorl %eax, %eax
addq $48, %rsp
# ...
LBB6_3:
.section __TEXT,__objc_methname,cstring_literals
"L_selector_data(isKindOfClass:)":
.asciz "isKindOfClass:"
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
.align 3
"L_selector(isKindOfClass:)":
.quad "L_selector_data(isKindOfClass:)"
Diff between obj2 and obj3 here.
isKindOfClass
is sent as a dynamically dispatched method as seen with _objc_msgSend
. Both objects are exposed to Objective-C as SwiftObject
(.quad _OBJC_METACLASS_$_SwiftObject
), declaring the type of the object as AnyObject
completes the bridge to NSObjectProtocol
.
In addition to matt's answer, which I think is correct:
Is isKindOfClass in this case actually sent as a dynamically dispatched message, even though the class itself is not an Objective-C visible type and does not use messaging based dispatch for its own methods?
No, isKindOfClass
is sent as a dynamically dispatched method because the class itself is an Objective-C visible type and does use messaging based dispatch for it's own methods.
It does this because of the @objc
in @objc public protocol AnyObject {}
If you cmd-click on AnyObject in XCode you'll see this in the generated headers
/// When used as a concrete type, all known `@objc` methods and
/// properties are available, as implicitly-unwrapped-optional methods
/// and properties respectively, on each instance of `AnyObject`.
And in the docs at https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html
To be accessible and usable in Objective-C, a Swift class must be a descendant of an Objective-C class or it must be marked @objc.
(my emphasis)
Adopting a protocol tagged with @objc
means your class is an @objc
class and is ObjC bridged via the interop mechanism pointed out by mz2 in the answer above.
来源:https://stackoverflow.com/questions/36851866/how-does-anyobject-conform-to-nsobjectprotocol