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

内存管理

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:):用来获取一个值得动态类型。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容