美文网首页iOS Developer随笔-生活工作点滴
奇怪的AnyObject和背后的SwiftValue

奇怪的AnyObject和背后的SwiftValue

作者: Maru | 来源:发表于2019-07-09 10:48 被阅读120次

    奇怪的行为

    在日常的开发过程中,我们经常会用到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

    相关文章

      网友评论

        本文标题:奇怪的AnyObject和背后的SwiftValue

        本文链接:https://www.haomeiwen.com/subject/mqgtkctx.html