奇怪的行为
在日常的开发过程中,我们经常会用到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
网友评论