美文网首页
Swift底层探索(四):内存管理

Swift底层探索(四):内存管理

作者: HotPotCat | 来源:发表于2020-12-15 23:56 被阅读0次

    内存管理

    Swift中使用自动引用计数(ARC)机制来追踪和管理内存。

    class HotpotCat {
        var age: Int = 18
        var name: String = "Hotpot"
    }
    
    var hp = HotpotCat()
    var hp1 = hp
    var hp2 = hp
    

    通过 lldb直接查看refCounted,这里的6其实就是引用计数3

    image.png

    分析下源码看下refCounted到底是什么?

    源码分析

    直接定位到HeapObject.cpp文件

    #define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
      InlineRefCounts refCounts
    
    struct HeapObject {
      /// This is always a valid pointer to a metadata object.
      HeapMetadata const *metadata;
    
      SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
    

    首先refCounted是个宏定义SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS,他的实现是InlineRefCounts refCounts

    typedef RefCounts<InlineRefCountBits> InlineRefCounts;
    

    InlineRefCounts其实是RefCounts类。

    template <typename RefCountBits>
    class RefCounts {
      std::atomic<RefCountBits> refCounts;
    

    RefCounts是一个模板类,那么真正在运行时期类型的决定取决于传进来的模板参数RefCountBits。从源码可以看到传进来的是InlineRefCountBits

    typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
    

    InlineRefCountBits又是一个模板类,RefCountIsInline模板参数定义如下:

    enum RefCountInlinedness { RefCountNotInline = false, RefCountIsInline = true };
    

    参数是一个枚举,只有truefalse两个值。所以直接分析下RefCountBitsT看到有一个属性BitsType

      typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
        BitsType;
    
      BitsType bits;
    
    struct RefCountBitsInt<refcountIsInline, 8> {
      typedef uint64_t Type;
      typedef int64_t SignedType;
    };
    

    它是把结构体RefCountBitsInt中的Type取了个别名叫BitsType。所以本质上BitsType就是一个uint64位整形。所以refCounted本质上就是操作的这64位整形。

    强引用

    我们看下创建对象的时候refCounted干了什么

      HeapObject() = default;
    
      // Initialize a HeapObject header as appropriate for a newly-allocated object.
      constexpr HeapObject(HeapMetadata const *newMetadata) 
        : metadata(newMetadata)
        , refCounts(InlineRefCounts::Initialized)
      { }
    

    创建对象的时候传了两个参数:metadatarefCounts,看下InlineRefCounts::Initialized其实是一个enum

    image.png
    RefCountBits(0, 1)传进去了一个0和一个1。继续看下RefCountBits
    template <typename RefCountBits>
    class RefCounts {
      std::atomic<RefCountBits> refCounts;
    

    其实真正调用的就是RefCounts,看下初始化方法

      constexpr
      RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
        : bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
               (BitsType(1)                << Offsets::PureSwiftDeallocShift) |
               (BitsType(unownedCount)     << Offsets::UnownedRefCountShift))
      { }
    
    • 0和1分别对应uint32_t strongExtraCount, uint32_t unownedCount
    • 分别做了位移操作
      offset具体内容如下:
    struct RefCountBitOffsets<8> {
      /*
       The bottom 32 bits (on 64 bit architectures, fewer on 32 bit) of the refcount
       field are effectively a union of two different configurations:
       
       ---Normal case---
       Bit 0: Does this object need to call out to the ObjC runtime for deallocation
       Bits 1-31: Unowned refcount
       
       ---Immortal case---
       All bits set, the object does not deallocate or have a refcount
       */
      static const size_t PureSwiftDeallocShift = 0;
      static const size_t PureSwiftDeallocBitCount = 1;
      static const uint64_t PureSwiftDeallocMask = maskForField(PureSwiftDealloc);
    
      static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
      static const size_t UnownedRefCountBitCount = 31;
      static const uint64_t UnownedRefCountMask = maskForField(UnownedRefCount);
    
      static const size_t IsImmortalShift = 0; // overlaps PureSwiftDealloc and UnownedRefCount
      static const size_t IsImmortalBitCount = 32;
      static const uint64_t IsImmortalMask = maskForField(IsImmortal);
    
      static const size_t IsDeinitingShift = shiftAfterField(UnownedRefCount);
      static const size_t IsDeinitingBitCount = 1;
      static const uint64_t IsDeinitingMask = maskForField(IsDeiniting);
    
      static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
      static const size_t StrongExtraRefCountBitCount = 30;
      static const uint64_t StrongExtraRefCountMask = maskForField(StrongExtraRefCount);
      
      static const size_t UseSlowRCShift = shiftAfterField(StrongExtraRefCount);
      static const size_t UseSlowRCBitCount = 1;
      static const uint64_t UseSlowRCMask = maskForField(UseSlowRC);
    
      static const size_t SideTableShift = 0;
      static const size_t SideTableBitCount = 62;
      static const uint64_t SideTableMask = maskForField(SideTable);
      static const size_t SideTableUnusedLowBits = 3;
    
      static const size_t SideTableMarkShift = SideTableBitCount;
      static const size_t SideTableMarkBitCount = 1;
      static const uint64_t SideTableMarkMask = maskForField(SideTableMark);
    };
    

    总结如下:

    image.png

    refCounted分析

    接着刚开始的refCounted0x0000000600000003

    image.png
    1~31UnownedRefCount33~62StrongExtraRefCount
    分析一下SIL代码
    image.png
    copy_addr的作用相当于
    %new = load $*HotpotCat
    strong_retain %new
    store %new to %9
    

    strong_retain本质就是调用swift_retain

    HeapObject *swift::swift_retain(HeapObject *object) {
      CALL_IMPL(swift_retain, (object));
    }
    

    CALL_IMPL调用的就是_swift_retain_

    static HeapObject *_swift_retain_(HeapObject *object) {
      SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
      if (isValidPointerForNativeRetain(object))
        object->refCounts.increment(1);
      return object;
    }
    

    这里是调用了refCounts.increment

     // Increment the reference count.
      void increment(uint32_t inc = 1) {
        auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
        
        // constant propagation will remove this in swift_retain, it should only
        // be present in swift_retain_n
        if (inc != 1 && oldbits.isImmortal(true)) {
          return;
        }
        
        RefCountBits newbits;
        do {
          newbits = oldbits;
          bool fast = newbits.incrementStrongExtraRefCount(inc);
          if (SWIFT_UNLIKELY(!fast)) {
            if (oldbits.isImmortal(false))
              return;
            return incrementSlow(oldbits, inc);
          }
        } while (!refCounts.compare_exchange_weak(oldbits, newbits,
                                                  std::memory_order_relaxed));
      }
    

    incrementStrongExtraRefCount实现

      bool incrementStrongExtraRefCount(uint32_t inc) {
        // This deliberately overflows into the UseSlowRC field.
        bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
        return (SignedBitsType(bits) >= 0);
      }
    

    inc做了强制类型转换,bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;意味着对33~64位加1。也就是在33位上+1然后进位。
    逐行断点看下lldb中的变化如下:

    class HotpotCat {
        var age: Int = 18
        var name: String = "Hotpot"
    }
    
    var hp = HotpotCat()
    var hp1 = hp
    var hp2 = hp
    print(CFGetRetainCount(hp as AnyObject))
    
    image.png
    CFGetRetainCount会增加引用计数,这里246是因为31位是IsDeinitingMask标志位。真正引用计数需要<<1也就是除以2。也就是123。强引用默认创建出来的对象引用计数是1。

    弱引用

    首先,我们已经知道在没有使用弱引用前refcountd的内容33~62存储着强引用的引用计数。
    声明一个弱引用,我们可以在编译器看到它是一个可选值:

    image.png
    弱引用声明的变量是一个可选值,因为在程序运行过程中是允许将当前变量设置为nil

    在这里hp = nil会报错,因为这里hp已经确定为HotpotCat类型,不能把'nil'赋值给HotpotCat类型。如果要置为nilhp要声明为可选类型var hp: HotpotCat?

    image.png

    打个断点看看弱引用都干了什么?

    image.png
    可以看到直接调用了swift_weakInit。再看下refcountd
    (lldb) po hp
    (lldb) po hp
    <HotpotCat: 0x1052369c0>
    
    (lldb) po hp2
    ▿ Optional<HotpotCat>
      ▿ some : <HotpotCat: 0x1052369c0>
    
    (lldb) x/8g 0x1052369c0
    0x1052369c0: 0x0000000100008188 0xc000000020a46fbc
    0x1052369d0: 0x0000000000000012 0x0000746f70746f48
    0x1052369e0: 0xe600000000000000 0x000000000000005f
    0x1052369f0: 0x00000009a0080001 0x00007fff80bd3718
    
    

    可以看到refcountd变成了0xc000000020a46fbc,引用计数也找不到了。

    源码分析

    定义了一个weak变量,编译器自动调用了swift_weakInit函数,这个函数是由WeakReference调用的。说明weak字段在编译器声明的过程当中自动生成了WeakReference对象。

    WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
      ref->nativeInit(value);
      return ref;
    }
    

    WeakReference用来管理弱引用。
    nativeInit源码:

     void nativeInit(HeapObject *object) {
        auto side = object ? object->refCounts.formWeakReference() : nullptr;
        nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
      }
    

    object不为空,则调用formWeakReference

    template <>
    HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
    {
      auto side = allocateSideTable(true);
      if (side)
        return side->incrementWeak();
      else
        return nullptr;
    }
    

    这段代码可以看到做了2件事
    1.创建一个sideTable
    2.incrementWeak

    allocateSideTable

    template <>
    HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
    {
      //1.拿到原有的引用计数
      auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
      
      // Preflight failures before allocating a new side table.
      if (oldbits.hasSideTable()) {
        // Already have a side table. Return it.
        return oldbits.getSideTable();
      } 
      else if (failIfDeiniting && oldbits.getIsDeiniting()) {
        // Already past the start of deinit. Do nothing.
        return nullptr;
      }
    
      // Preflight passed. Allocate a side table.
      
      // FIXME: custom side table allocator
      //2.通过HeapObject创建了一个HeapObjectSideTableEntry实例对象
      HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
      //3.将创建的实例对象地址给了InlineRefCountBits,也就是 RefCountBitsT
      auto newbits = InlineRefCountBits(side);
      
      do {
        if (oldbits.hasSideTable()) {
          // Already have a side table. Return it and delete ours.
          // Read before delete to streamline barriers.
          auto result = oldbits.getSideTable();
          delete side;
          return result;
        }
        else if (failIfDeiniting && oldbits.getIsDeiniting()) {
          // Already past the start of deinit. Do nothing.
          return nullptr;
        }
        
        side->initRefCounts(oldbits);
        
      } while (! refCounts.compare_exchange_weak(oldbits, newbits,
                                                 std::memory_order_release,
                                                 std::memory_order_relaxed));
      return side;
    }
    

    这个函数做了三件事:1.拿到原有的引用计数。2.通过HeapObject创建了一个HeapObjectSideTableEntry实例对象。3.将创建的实例对象地址给了InlineRefCountBits,也就是 RefCountBitsT。
    这里调用RefCountBitsT的方法变了

    //强引用
      LLVM_ATTRIBUTE_ALWAYS_INLINE
      constexpr
      RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
        : bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
               (BitsType(1)                << Offsets::PureSwiftDeallocShift) |
               (BitsType(unownedCount)     << Offsets::UnownedRefCountShift))
      { }
    //弱引用
    LLVM_ATTRIBUTE_ALWAYS_INLINE
      RefCountBitsT(HeapObjectSideTableEntry* side)
        : bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
               | (BitsType(1) << Offsets::UseSlowRCShift)
               | (BitsType(1) << Offsets::SideTableMarkShift))
      {
        assert(refcountIsInline);
      }
    

    在弱引用方法中把创建出来的地址做了偏移操作然后存放到了内存当中。
    这里其实是把side地址存放在了uint64_t。就解释了上面refcountd变成了0xc000000020a46fbc的原因,也就是这里存储了HeapObjectSideTableEntry实例对象的地址。

    地址分析

    我们还原下0xc000000020a46fbc

    image.png
    由上面的源码可以看到62好63位是保留字段,我们去掉。
    image.png
    由此得到我们散列表的内存地址为:0x20A46FBC

    HeapObjectSideTableEntry

    拿到地址了,我们看下HeapObjectSideTableEntry里面到底干了什么

    class HeapObjectSideTableEntry {
      // FIXME: does object need to be atomic?
      std::atomic<HeapObject*> object;
      SideTableRefCounts refCounts;
    
      public:
      HeapObjectSideTableEntry(HeapObject *newObject)
        : object(newObject), refCounts()
      { }
    

    可以看到除了object还有一个SideTableRefCounts

    typedef RefCounts<InlineRefCountBits> InlineRefCounts;
    typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
    

    SideTableRefCountBits源码如下:

    class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
    {
      uint32_t weakBits;
    
      public:
      LLVM_ATTRIBUTE_ALWAYS_INLINE
      SideTableRefCountBits() = default;
    
      LLVM_ATTRIBUTE_ALWAYS_INLINE
      constexpr
      SideTableRefCountBits(uint32_t strongExtraCount, uint32_t unownedCount)
        : RefCountBitsT<RefCountNotInline>(strongExtraCount, unownedCount)
        // weak refcount starts at 1 on behalf of the unowned count
    //这里弱引用计数默认从1开始。
        , weakBits(1)
      { }
    
      LLVM_ATTRIBUTE_ALWAYS_INLINE
      SideTableRefCountBits(HeapObjectSideTableEntry* side) = delete;
    
      LLVM_ATTRIBUTE_ALWAYS_INLINE
      SideTableRefCountBits(InlineRefCountBits newbits)
        : RefCountBitsT<RefCountNotInline>(&newbits), weakBits(1)
      { }
    
      
      LLVM_ATTRIBUTE_ALWAYS_INLINE
      void incrementWeakRefCount() {
        weakBits++;
      }
    
      LLVM_ATTRIBUTE_ALWAYS_INLINE
      bool decrementWeakRefCount() {
        assert(weakBits > 0);
        weakBits--;
        return weakBits == 0;
      }
    
      LLVM_ATTRIBUTE_ALWAYS_INLINE
      uint32_t getWeakRefCount() {
        return weakBits;
      }
    
      // Side table ref count never has a side table of its own.
      LLVM_ATTRIBUTE_ALWAYS_INLINE
      bool hasSideTable() {
        return false;
      }
    };
    

    可以看到SideTableRefCountBits继承于RefCountBitsT(存储uint_64_t),这里多了一个weakBits(uint32_t)。
    uint_64_t保留原来的引用计数,uint32_t保存弱引用计数。
    继续还原0x20A46FBC。左移3位拿到弱引用信息变为0x105237DE0,直接读取下:

    (lldb) po hp
    <HotpotCat: 0x1052369c0>
    
    (lldb) po hp2
    ▿ Optional<HotpotCat>
      ▿ some : <HotpotCat: 0x1052369c0>
    
    (lldb) x/8g 0x105237DE0
    0x105237de0: 0x00000001052369c0 0x0000000000000000
    0x105237df0: 0x0000000200000003 0x0000000000000002
    0x105237e00: 0x0000000000000000 0x0000000000000000
    0x105237e10: 0x3e30633936330002 0x00027fff80bc7d98
    
    • 0x00000001052369c0 就是实例对象的地址。
    • 0x0000000200000003就是没有weak的时候的refcounts(这里有一点需要注意下,长时间断点这个值会变,原因暂时不明)
    • 0x0000000000000002就是弱引用计数。
      这里弱引用为2的原因是因为SideTableRefCountBits初始化的时候从1开始,并且进行了incrementWeak
    //初始化为1
     SideTableRefCountBits(uint32_t strongExtraCount, uint32_t unownedCount)
        : RefCountBitsT<RefCountNotInline>(strongExtraCount, unownedCount)
        // weak refcount starts at 1 on behalf of the unowned count
        //弱引用计数默认从1开始。
        , weakBits(1)
      { }
    //+1
    HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
    {
      auto side = allocateSideTable(true);
      if (side)
        return side->incrementWeak();
      else
        return nullptr;
    }
    

    这里也就解释了被引用对象释放了为什么还能直接访问 Side TableSide Table 的生命周期与对象是分离的,当强引用计数为 0 时,只有HeapObject被释放了。
    只有所有的 weak引用者都被释放了或相关变量被置nil后,Side Table 才能得以释放。

    循环引用

    闭包一般默认捕获外部变量

    var age = 10
    //和oc中block一致,都会捕获。
    let closure = {
        age += 1
    }
    
    closure()
    print(age)
    
    11
    (lldb) 
    

    闭包内部对变量的修改会改变外部原始变量的值。
    看一个循环引用的例子:

    class HotpotCat {
        var age = 18
        var closure: (() -> ())?
        deinit {
            print("HotpotCat deinit")
        }
    }
    
    func test() {
        
        var hotpot = HotpotCat()
        
        hotpot.closure = {
            hotpot.age = 1
        }
    }
    
    test()
    

    swift中有两种解决循环引用的方式:weak和unowned,区别是unowned不允许设置为nil。

    //unowned
    func test() {
        var hotpot = HotpotCat()
        hotpot.closure = { [unowned  hotpot] in
            hotpot.age = 1
        }
    }
    
    //weak
    func test() {
        var hotpot = HotpotCat()
        hotpot.closure = { [weak  hotpot] in
            hotpot?.age = 1//weak为可选值
        }
    }
    

    捕获列表

    定义在参数列表之前,捕获列表被写为用逗号括起来的表达式列表,并用方括号括起来。如果使用捕获列表,即使省略参数名称,参数类型和返回值,也必须加关键字in
    那么捕获列表的作用呢?

    func test() {
        var age = 10
        var height = 1.85
        let closure = { [age]  in
            print(age)
            print(height)
        }
        age = 1
        height = 1.75
        closure()
    }
    
    test()
    
    10
    1.75
    

    对于捕获列表中的每个常量,闭包会利用周围范围内具有相同名称的常量或变量来初始化捕获列表中定义的常量。

    Swift Runtime

    对于纯swift类,我们用runtime获取一下它的方法列表和属性列表

    //swift 地层结构与 OC 部分一致
    class HotpotCat {
        var age: Int = 18
        func test(){
            print("test")
        }
    }
    
    let hotpot = HotpotCat() //HotpotCat  .Type
    
    func test(){
        //获取方法列表
        var methodCount: UInt32 = 0
        let methodlist = class_copyMethodList(HotpotCat.self, &methodCount)
        for  i in 0 ..< numericCast(methodCount) {
            if let method = methodlist?[i] {
                let methodName = method_getName(method)
                print("method:\(String(describing: methodName))")
            } else {
                print("not found method")
            }
        }
        //获取属性列表
        var count: UInt32 = 0
        let proList = class_copyPropertyList(HotpotCat.self, &count)
        for  i in 0 ..< numericCast(count) {
            if let property = proList?[i] {
                let propertyName = property_getName(property)
                print("property:\(String(utf8String: propertyName)!)")
            } else {
                print("not found property")
            }
        }
        print("run")
    }
    
    test()
    print("end")
    

    输出

    run
    end
    

    那么添加@objc关键字获取一下:

    class HotpotCat {
        @objc var age: Int = 18
        @objc func test(){
            print("test")
        }
    }
    
    method:test
    method:age
    method:setAge:
    property:age
    run
    end
    

    这个时候可以获取到,但是oc无法调用。
    继承NSObject看下:

    class HotpotCat: NSObject {
        var age: Int = 18
        func test(){
            print("test")
        }
    }
    
    method:init
    run
    end
    

    在这里编译器只对必要方法init添加了@objc

    class HotpotCat: NSObject {
        @objc var age: Int = 18
        @objc func test(){
            print("test")
        }
    }
    
    method:init
    method:test
    method:age
    method:setAge:
    property:age
    run
    end
    

    这个时候我们自己的方法才暴露给了OC类。
    那么这个时候在Swift类中调用HotpotCat的test方法呢?


    image.png

    可以看到仍然是函数表调用,这里编译器优化成了函数表调用。
    那么test改为dynamic修饰呢?

    class HotpotCat: NSObject {
        var age: Int = 18
        dynamic func test(){
            print("test")
        }
    }
    
    method:init
    run
    test
    end
    

    oc依然无法调用test,同时添加@objc + dynamic

    class HotpotCat: NSObject {
        var age: Int = 18
        @objc dynamic func test(){
            print("test")
        }
    }
    
    method:init
    method:test
    run
    test
    end
    
    image.png

    这个时候test的调用已经变成了动态调用。

    • 对于纯Swift类来说,没有动态特性。方法和属性不加任何修饰符的情况下。这个时候不具备我们所谓的Runtime特性。
    • 对于纯Swift类方法和属性添加@objc标识,可以通过Runtime API拿到方法和属性,但是在OC中无法进行调度。
    • 对于继承自NSObject的类来说,如果想要动态获取当前属性和方法,必须在声明前加@objc,否则无法通过Runtime API获取;若要进行方法交换需要同时加上dynamic标识。否则方法也只是暴露给OC使用(前面章节说过内部也是调用swift的方法),不具备动态特性。

    源码分析

    Swift默认基类_SwiftObject

    image.png
    Swift实现了NSObject协议。Swift为了和OC交互在Swift里面保留了这样的数据结构,底层数据结构和OC部分一致。
    TargetAnyClassMetadata中也可以看到对应信息:isa,superclass
    image.png
    在objc源码中可以就看到swift_class_t继承自objc_class
    struct swift_class_t : objc_class {
        uint32_t flags;
        uint32_t instanceAddressOffset;
        uint32_t instanceSize;
        uint16_t instanceAlignMask;
        uint16_t reserved;
    
        uint32_t classSize;
        uint32_t classAddressOffset;
        void *description;
        // ...
    
        void *baseAddress() {
            return (void *)((uint8_t *)this - classAddressOffset);
        }
    };
    

    元类型、AnyClass、Self

    AnyObject

    class HotpotCat {
        var age: Int = 18
        var name: String = "hotpot"
    }
    
    var hp = HotpotCat()
    
    //hp1 代表 HotpotCat 实例对象
    var hp1: AnyObject = hp
    
    //hp2  代表 HotpotCat 类的类型
    var hp2: AnyObject = HotpotCat.self
    
    //这个协议只能类遵守
    protocol Hotpot: AnyObject {
        
    }
    

    在我们不知道对象的具体类型时,可以用AnyObject处理,比如:

    var age: AnyObject = 10 as NSNumber
    

    需要确保对象是instance、类、协议(仅类遵从的)。有点类似ocid的意思。

    Any

    class HotpotCat {
        var age: Int = 18
        var name: String = "hotpot"
    }
    var hp = HotpotCat()
    var age: AnyObject = 10 as NSNumber
    func test() {
        print("test")
    }
    //这里就只能用Any修饰了
    var hp2:HotpotCat?
    var array:[Any] = [hp,1,"hotpot",true,test,age,hp2]
    

    Any包含AnyObject。代表任意类型,包括函数和可选类型。

    AnyClass

    public typealias AnyClass = AnyObject.Type
    

    AnyClassAnyObject.Type类型。

    T.self

    class HotpotCat {
        var age: Int = 18
        var name: String = "hotpot"
    }
    
    var hp = HotpotCat()
    // 本身
    var hp1 = hp.self
    //metadata元类型
    var hp2: AnyObject = HotpotCat.self
    
    (lldb) po hp
    <HotpotCat: 0x105807040>
    
    (lldb) po hp1
    <HotpotCat: 0x105807040>
    
    (lldb) po hp2
    <SwiftARC.HotpotCat: 0x100008188>
    
    (lldb) po hp.age.self
    18
    
    (lldb) po Int.self
    Swift.Int
    
    • instance/值``.self就是其本身。
    • Class返回metadata``.self,类型返回类型本身(Int)。

    .Type

    是一种类型,T.selfT.Type类型。也就是元类型的类型。

    type(of:)

    class Hotpot {
        var age: Int = 18
        func test() {
            print("Hotpot Class")
        }
    }
    
    class HotpotCat: Hotpot {
        override func test() {
            print("HotpotCat Class")
        }
    }
    
    func test(_ value: Hotpot) {//这里编译期类型就是Hotpot
        //这里动态类型是 HotpotCat
        value.test()
        //dynamic type: type(of:)获取动态类型
        print(type(of: value))
    }
    
    var hp = HotpotCat()
    test(hp)
    
    HotpotCat Class
    

    这里相当于做了类型转换。
    如果改成协议呢?

    protocol HPProtocol {
        
    }
    
    class Hotpot: HPProtocol {
        var age: Int = 18
        func test() {
            print("Hotpot Class")
        }
    }
    
    func test(_ value: HPProtocol) {
        print(type(of: value))
    }
    
    var hp = Hotpot()
    var hp1: HPProtocol = Hotpot()
    test(hp)
    test(hp1)
    
    Hotpot
    Hotpot
    

    这里符合我们的预期。那么test改成泛型呢?

    func test<T>(_ value: T) {
        print(type(of: value))
    }
    
    Hotpot
    HPProtocol
    

    在有协议有泛型的参与下hp2type(of:)后变成了HPProtocol。所以在有协议和泛型的时候type(of:)并不能推断出来。那么把value转换一下呢?

    func test<T>(_ value: T) {
        print(type(of: value as Any))
    }
    
    Hotpot
    Hotpot
    

    所以在有协议和泛型的时候如果要获取类型,我们需要先转成Any再获取。

    总结

    image.png
    • AnyObject:代表任意类的instance,类的类型,仅类遵守的协议。
    • Any:代表任意类型,包括funcation类型或者optional类型。
    • AnyClass:代表任意实例的类型,AnyObject.Type。
    • T.self:T是实例,返回实例本身;T是类型,返回Metadata。
    • T.Type:一种类型,T.self是T.Type类型。
    • type(of:):用来获取一个值得动态类型。

    相关文章

      网友评论

          本文标题:Swift底层探索(四):内存管理

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