AnyObject如何符合NSObjectProtocol?

时间:2016-04-25 22:00:13

标签: swift nsobject swift-protocols anyobject

这个问题的灵感来自mz2's answer问题的Check for object type fails 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继承,但仍符合NSObjectProtocolAnyObject如何符合NSObjectProtocol

4 个答案:

答案 0 :(得分:5)

在Cocoa / Objective-C世界中,AnyObject是id。将此对象强制转换为AnyObject后,您可以向其发送任何已知的Objective-C消息,例如isKindOfClassconformsToProtocol。现在,当你说isKindOfClassconformsToProtocol时,你不再是斯威夫特世界了;你正在和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)。

答案 1 :(得分:5)

如果我根据JQuery documentation回答正确理解这一点,那么当Swift / Objective-C互操作可用时,这是有效的,因为实际上Swift类类型最终继承自java.io.FileNotFoundException: /storage/1B0C-330F/monfichier.txt: open failed: EACCES (Permission denied) ,当时目标 - 编译C interop,实际上涉及一个Objective-C类(SwiftObject在SwiftObject中实现,当使用Objective-C interop时编译为Objective-C ++)。因此,将Swift类类型的对象转换为AnyObject类型的"泄漏"那个信息。

matt's文件SwiftObject.mm查看实施中的某些相关位:

swift/stdlib/public/runtime/SwiftObject.mm

正如预测的那样,在Linux中使用Swift 3(根据我的理解,在Swift运行时和基础实现中没有Objective-C运行时可用吗?)来自这个问题的示例代码和启发此问题的Swift source code因以下错误编译错误而失败:

#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

答案 2 :(得分:2)

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

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

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

VS

movq    %rax, __Tv3obj3objPs9AnyObject_(%rip)

# ...

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

# ...

.no_dead_strip  __Tv3obj3objPs9AnyObject_

完全差异here

这对我很有意思。如果两个文件之间的唯一区别是对象类型的名称,为什么声明为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之间的差异here

isKindOfClass作为动态调度方法发送,如_objc_msgSend所示。这两个对象都以SwiftObject.quad _OBJC_METACLASS_$_SwiftObject)的形式向Objective-C公开,声明对象的类型,AnyObject完成了NSObjectProtocol的桥接。

答案 3 :(得分:1)

除了matt's回答,我认为这是正确的:

  

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

不,isKindOfClass是作为动态调度方法发送的,因为类本身 是Objective-C可见类型而 >使用基于消息传递的调度来实现它自己的方法。

这是因为@objc

中的@objc public protocol AnyObject {}

如果你在XCode中点击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的协议意味着您的类是@objc类,并且是通过上面答案中mz2指出的互操作机制桥接的ObjC。