How Does AnyObject Conform to NSObjectProtocol?

我的未来我决定 提交于 2019-11-30 19:35:41

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

mz2

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.

Steve Trewick

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!