奇怪的行为
在日常的开发过程中,我们经常会用到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,从而达到OC和Swift交互的目的。但是这并不意味着我们可以在OC端访问任意的Swift值,在最新的Swift版本中,所以需要暴露给OC的方法和类都需要显式的加上@objc的attribute,然而,如果你是用@objc来限定struct等值类型,编译器就会提示错误,也就是说,从编译阶段Swift就限定了我们不能使用@objc来暴露值类型,虽然在运行时期值类型是可以被OC对象所包装的。
那么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关键字的行为中,我们窥探了Swift和OC的部分桥接的实现细节,interop的操作都被
SwiftValue所隐藏在了内部,当我们进行动态的类型检查的时候,原有的Swift类型就被转换成了SwiftValue,这也就解释了我们开文时那段代码奇怪的行为了。
参考
Class and Subtype existentials
SwiftValue Define
TypeLayout
TypeMetadata