奇怪的AnyObject和背后的SwiftValue

745 阅读4分钟
原文链接: www.jianshu.com

奇怪的行为

在日常的开发过程中,我们经常会用到AnyObject,按照苹果的官方文档来说,所谓的AnyObject就是一个所有Class都隐式遵循的Protocol,原文如下:

/// The protocol to which all classes implicitly conform.
///
/// You use `AnyObject` when you need the flexibility of an untyped object or
/// when you use bridged Objective-C methods and properties that return an
/// untyped result. `AnyObject` can be used as the concrete type for an
/// instance of any class, class type, or class-only protocol.

也就是说,除了一些值类型(Struct, Enum),其他的类型都会遵守AnyObject,这都是说得通的。然而奇怪的是如下代码:

enum MyEnum {
    case test
}

let e = MyEnum.test

if e is AnyObject {
    print("e is AnyObject!")
} else {
    print("e is not AnyObject!")
}

照理来说,MyEnum是一个枚举类型,而不是Class类型,这里输出的应该是 e is not AnyObject!,然而真正运行之后我们得到的却是 e is AnyObject!

当我们将AnyObject用于限定protocol的时候,它的行为又是符合预期的,比如:

protocol MyProtocol: AnyObject {}

struct MyStruct: MyProtocol {}

当我们用AnyObject来限定MyProtocol的时候,编译器就会发出错误的提示:

Non-class type 'MyStruct' cannot conform to class protocol 'MyProtocol'

总结来说,奇怪的点在于看起来,值类型遵守AnyObject协议,但是在用于Protocol限定的时候值类型又不是AnyObject的。这是为什么呢?

探测运行时类型

如果我们使用type方法来动态的探测类型,我们会发现这样一个有趣的行为:

let myEnum = MyEnum.test
let x = myEnum as! AnyObject
print(type(of: x)) // __SwiftValue
print(type(of: myEnum)) // MyEnum

我们可以看到,对待同样的myEnum,在转换成为AnyObject之后它就不再是MyEnum而是__SwiftValue。也就是说,值类型在运行时是允许转换成__SwiftValue这一隐藏类型的。那么到底什么是__SwiftValue呢?

SwiftValue

SwiftValue.mm中,我们可以看到如下的定义:

@interface __SwiftValue : NSObject <NSCopying>
- (id)copyWithZone:(NSZone *)zone;
@end

也就是说,所谓__SwiftValue就是一个继承自NSObject的对象,这也就解释的通为什么在使用is关键字的时候判断为true了,因为MyEnum类型的Swift值被__SwiftValue所包装了一层,真正进行is判断的是__SwiftValue,那结果自然是为真了。

在源代码中我们可以找到封装实现的C++代码:

__SwiftValue *swift::bridgeAnythingToSwiftValueObject(OpaqueValue *src,
                                                    const Metadata *srcType,
                                                    bool consume) {
  size_t alignMask = getSwiftValuePayloadAlignMask(srcType);

  size_t totalSize =
      getSwiftValuePayloadOffset(alignMask) + srcType->getValueWitnesses()->size;

  void *instanceMemory = swift_slowAlloc(totalSize, alignMask);
  __SwiftValue *instance
    = objc_constructInstance(getSwiftValueClass(), instanceMemory);
  auto header = getSwiftValueHeader(instance);
  new (header) SwiftValueHeader();
  header->type = srcType;

  auto payload = getSwiftValuePayload(instance, alignMask);
  if (consume)
    srcType->vw_initializeWithTake(payload, src);
  else
    srcType->vw_initializeWithCopy(payload, src);

  return instance;
}

也就是说,这个方法可以拿到任何Swift的类型实例,然后通过runtime系统的objc_constructInstance方法来动态构造一个__SwiftValue,从而达到OCSwift交互的目的。但是这并不意味着我们可以在OC端访问任意的Swift值,在最新的Swift版本中,所以需要暴露给OC的方法和类都需要显式的加上@objcattribute,然而,如果你是用@objc来限定struct等值类型,编译器就会提示错误,也就是说,从编译阶段Swift就限定了我们不能使用@objc来暴露值类型,虽然在运行时期值类型是可以被OC对象所包装的。

那么SwiftValue的 内存结构是怎么样的呢?

Layout of SwiftValue

所谓的payload就是装载了实体对象的部分,比较有意思的是SwiftValueHeader部分。在SwiftValueHeader中包含了Swift类型的Metadata,所谓的Metadata就是类型的元信息,它的定义如下:

当然除了Metadata还有ClassMetadata,这里就不展开叙述。

typedef struct Metadata {
  void *valueWitnessTable;
  unsigned long kind;
} Metadata;

我们可以看到,Metadata主要包含了两个部分,一个是valueWitnessTable,它是一个函数指针的表,包含了allocating函数、copying函数、destroying函数、内存占用大小以及字节对齐等信息。除此之外,kind则标明了该Swift值的基础类型,所对应的表如下:

Kind 对应类型
0 Class
1 Struct
2 Enum
3 Optional
8 Opaque
9 Tuple
10 Function
12 Protocol
13 Metatype
14 Objective-C Class Wapper
15 Existential metatype

总结

is关键字的行为中,我们窥探了SwiftOC的部分桥接的实现细节,interop的操作都被
SwiftValue所隐藏在了内部,当我们进行动态的类型检查的时候,原有的Swift类型就被转换成了SwiftValue,这也就解释了我们开文时那段代码奇怪的行为了。

参考

Class and Subtype existentials
SwiftValue Define
TypeLayout
TypeMetadata