奇怪的AnyObject和背后的SwiftValue
奇怪的行为
在日常的开发过程中,我们经常会用到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.testif 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.testlet x = myEnum as! AnyObjectprint(type(of: x)) // __SwiftValueprint(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的 内存结构是怎样样的呢?
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关键字的行为中,我们窥探了Swift和OC的部分桥接的实现细节,interop的操作都被SwiftValue所隐藏在了内部,当我们进行动态的类型检查的时候,原有的Swift类型就被转换成了SwiftValue,这也就解释了我们开文时那段代码奇怪的行为了。
参考
Class and Subtype existentials
SwiftValue Define
TypeLayout
TypeMetadata
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 奇怪的AnyObject和背后的SwiftValue