10

这个问题的灵感来自mz2Check for object type failed with "is not a type" error问题的回答。

考虑一个空的 Swift 类:

class MyClass { }

尝试NSObjectProtocol在此类的实例上调用任何方法将导致编译时错误:

let obj = MyClass()
obj.isKindOfClass(MyClass.self) // Error: Value of type 'MyClass' has no member 'isKindOfClass'

但是,如果我将实例转换为AnyObject,我的对象现在符合NSObjectProtocol并且我可以调用协议定义的实例方法:

let obj: AnyObject = MyClass()
obj.isKindOfClass(MyClass.self) // true
obj.conformsToProtocol(NSObjectProtocol) // true
obj.isKindOfClass(NSObject.self) // false

我的对象不继承自NSObject,但仍符合NSObjectProtocol. 如何AnyObject符合NSObjectProtocol

4

4 回答 4

6

在 Cocoa/Objective-C 世界中,AnyObject 是id. 将此对象转换为 AnyObject 后,您可以向其发送任何已知的 Objective-C 消息,例如isKindOfClassconformsToProtocol。现在,当你说isKindOfClassconformsToProtocol,你已经不在 Swift 世界里了;您正在使用 Objective-C 与 Cocoa 交谈。所以想想Objective-C是如何看待这个对象的。Objective-C 世界中的所有类都来自某个基类;像 MyClass 这样没有基础的课程是不可能的。并且 Objective-C 世界中的每个基类都符合 NSObject 协议(Swift 称之为 NSObjectProtocol);这就是成为(或继承自)基类的意义!因此,为了让它进入 Objective-C 世界,Swift 将 MyClass 呈现为一个特殊的桥接基类 SwiftObject 的后代,它确实符合 NSObjectProtocol(如您在此处看到的:https ://github.com/apple/swift/blob /master/stdlib/public/runtime/SwiftObject.mm)。

于 2016-04-25T22:47:21.777 回答
6

如果我根据matt 的回答正确理解了这一点,那么当 Swift / Objective-C 互操作可用时,这是有效的,因为事实上 Swift 类类型最终继承自SwiftObject其中,当编译 Objective-C 互操作时,实际上涉及一个 Objective-C 类( SwiftObject 已实现,在SwiftObject.mm使用 Objective-C 互操作时编译为 Objective-C++)。因此,将 Swift 类类型对象转换为 AnyObject 类型会“泄漏”该信息。

从Swift 源代码文件中查看实现中的一些相关位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

正如所预测的那样,在 Linux 中使用 Swift 3(据我所知,没有可用的 Objective-C 运行时作为 Swift 运行时和 Foundation 实现的一部分?)这个问题的示例代码和启发了这个的早期问题和答案问题因以下错误编译器错误而失败:

ERROR […] value of type 'AnyObject' has no member 'isKindOfClass'
于 2016-04-25T23:29:29.157 回答
4

在已经很好的答案中添加一些额外的信息。

我创建了三个程序并查看了每个程序生成的程序集:

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)

obj1 和 obj2 之间的区别是微不足道的。任何涉及对象类型的指令都有不同的值:

movq    %rax, __Tv3obj3objCS_7MyClass(%rip)

# ...

globl   __Tv3obj3objCS_7MyClass         .globl  __Tv3obj3objPs9AnyObject_
.zerofill __DATA,__common,__Tv3obj3objCS_7MyClass,8,3

# ...

.no_dead_strip  __Tv3obj3objCS_7MyClass

对比

movq    %rax, __Tv3obj3objPs9AnyObject_(%rip)

# ...

.globl  __Tv3obj3objPs9AnyObject_
.zerofill __DATA,__common,__Tv3obj3objPs9AnyObject_,8,3

# ...

.no_dead_strip  __Tv3obj3objPs9AnyObject_

完全差异在这里

这对我来说很有趣。如果两个文件之间的唯一区别是对象类型的名称,为什么声明为的对象可以AnyObject执行 Objective-C 选择器?

obj3 显示了isKindOfClass:选择器是如何被触发的:

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

obj2 和 obj3 的区别在这里

isKindOfClass以动态调度的方法发送,如_objc_msgSend. SwiftObject这两个对象都作为( )暴露给 Objective-C .quad _OBJC_METACLASS_$_SwiftObject,声明对象的类型为AnyObject完成到NSObjectProtocol.

于 2016-04-26T18:00:49.350 回答
1

除了马特的回答,我认为这是正确的:

在这种情况下,isKindOfClass 是否实际上是作为动态分派的消息发送的,即使该类本身不是 Objective-C 可见类型并且不为自己的方法使用基于消息的分派?

不,isKindOfClass是作为动态调度方法发送的,因为该类本身一个 Objective-C 可见类型,并且确实为它自己的方法使用了基于消息传递的调度。

它这样做是@objc因为@objc public protocol AnyObject {}

如果您在 XCode 中 cmd-click AnyObject,您将在生成的标题中看到这一点

/// 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`.

https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html的文档中

为了在 Objective-C 中可访问和可用,Swift 类必须是 Objective-C 类的后代,或者必须标记为 @objc。

(我的重点)

采用带有标记的协议@objc意味着您的类是一个类,并且是通过上述答案中mz2@objc指出的互操作机制桥接的ObjC 。

于 2016-04-26T11:44:17.647 回答