Swift進(jìn)階04:內(nèi)存管理&Runtime

第四節(jié)課:內(nèi)存管理&Runtime

內(nèi)存管理 - 強(qiáng)引用

在swift中也是使用ARC來(lái)追蹤和管理內(nèi)存的,下面我們先簡(jiǎn)單看一段代碼來(lái)進(jìn)行分析

class HZMTeacher {
    var age: Int = 18
    var name: String = "HZM"
}
var t = HZMTeacher()
var t1 = t
var t2 = t
內(nèi)存管理01.png
  • 第一位為metadata但是后面的0x0000000600000002跟我們之前的refCounts并不一樣

我們分析下源碼 HeapObject -> InlineRefCounts

struct HeapObject {
  HeapMetadata const *metadata;

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
  ...
}
??
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts
  • 進(jìn)入InlineRefCounts定義,是RefCounts類型的別名,而RefCounts是模板類,真正決定的是傳入的類型InlineRefCountBits
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
??
template <typename RefCountBits>
class RefCounts {
  std::atomic<RefCountBits> refCounts;
  ...
}
  • 分析InlineRefCountBits,是RefCountBitsT類的別名
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
  • 分析RefCountBitsT,有bits屬性
template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {
    ...
      typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
    BitsType;
    ...
    BitsType bits;
    ...
}
??
template <>
struct RefCountBitsInt<RefCountNotInline, 4> {
  //類型
  typedef uint64_t Type;
  typedef int64_t SignedType;
};

其中bits其實(shí)質(zhì)是將RefCountBitsInt中的type屬性取了一個(gè)別名,所以bits的真正類型是uint64_t即64位整型數(shù)組

然后來(lái)繼續(xù)分析swift中對(duì)象創(chuàng)建的底層方法swift_allocObject

分析初始化源碼swift_allocObject

static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
    ...
    new (object) HeapObject(metadata);
    ...
}
??
<!--構(gòu)造函數(shù)-->
 constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }
  • 進(jìn)入Initialized定義,是一個(gè)枚舉,其對(duì)應(yīng)的refCounts方法中
  enum Initialized_t { Initialized };
  
  //對(duì)應(yīng)的RefCounts方法
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}
  • 從這里看出真正干事的是RefCountBits

  • 進(jìn)入RefCountBits定義,也是一個(gè)模板定義

template <typename RefCountBits>
class RefCounts {
  std::atomic<RefCountBits> refCounts;
  ...
}
  • 所以真正的初始化地方是下面這個(gè),實(shí)際上是做了一個(gè)位域操作,根據(jù)的是Offsets
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))
{ }

分析RefCountsBit的結(jié)構(gòu),如下圖所示

內(nèi)存管理02.png

  • isImmortal(0)

  • UnownedRefCount(1-31): unowned的引用計(jì)數(shù)

  • isDeinitingMask(32):是否進(jìn)行釋放操作

  • StrongExtraRefCount(33-62): 強(qiáng)引用計(jì)數(shù)

  • UseSlowRC(63)

重點(diǎn)關(guān)注UnownedRefCountStrongExtraRefCount

內(nèi)存管理3.png

trefCounts用二進(jìn)制展示,我們發(fā)現(xiàn):
1~31的UnownedRefCount 為1
33~62的StrongExtraRefCount 為2

當(dāng)只有t實(shí)例變量時(shí)


內(nèi)存管理04.png

當(dāng)有t + t1時(shí),查看是否有 strong_retain操作

//SIL中的main
alloc_global @main.t1 : main.HZMTeacher       // id: %8
%9 = global_addr @main.t1 : main.HZMTeacher : $*HZMTeacher // user: %11
%10 = begin_access [read] [dynamic] %3 : $*HZMTeacher // users: %12, %11
copy_addr %10 to [initialization] %9 : $*HZMTeacher // id: %11

//其中copy_addr等價(jià)于
- %new = load s*HZMTeacher
- strong_retain %new
- store %new to %9
內(nèi)存管理05.png
//內(nèi)部是一個(gè)宏定義
HeapObject *swift::swift_retain(HeapObject *object) {
  CALL_IMPL(swift_retain, (object));
}
??
//本質(zhì)調(diào)用的就是 _swift_retain_
static HeapObject *_swift_retain_(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
  if (isValidPointerForNativeRetain(object))
    object->refCounts.increment(1);
  return object;
}
??
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;
    }
    //64位bits
    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));
  }
  • 回退到HeapObject,從InlineRefCounts進(jìn)入,其中是c++中的模板定義,是為了更好的抽象,在其中查找bits(即decrementStrongExtraRefCount方法)
LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
bool incrementStrongExtraRefCount(uint32_t inc) {
// This deliberately overflows into the UseSlowRC field.
// 對(duì)inc做強(qiáng)制類型轉(zhuǎn)換為 BitsType
// 其中 BitsType(inc) << Offsets::StrongExtraRefCountShift 等價(jià)于 1<<33位,16進(jìn)制為 0x200000000
//這里的 bits += 0x200000000,將對(duì)應(yīng)的33-63轉(zhuǎn)換為10進(jìn)制,為
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}

例如以trefCounts為例(其中62-33位是strongCount,每次增加強(qiáng)引用計(jì)數(shù)增加都是在33-62位上增加的,固定的增量為1左移33位,即0x200000000

  • 只有t時(shí)的refCounts是 0x0000000200000002

  • t + t1時(shí)的refCounts是 0x0000000400000002 = 0x0000000200000002 + 0x200000000

  • t + t1 + t2 時(shí)的refCounts是 0x0000000600000002 = 0x0000000400000002 + 0x200000000

針對(duì)上面的例子,可以通過(guò)CFGetRetainCOunt獲取引用計(jì)數(shù),發(fā)現(xiàn)依次是 2、3、4,默認(rèn)多了一個(gè)1

內(nèi)存管理06.png

  • 如果將t、t1、t2放入函數(shù)中,還會(huì)再次retain一次
    內(nèi)存管理07.png

為什么是0x200000000
因?yàn)?左移33位,其中4位為一組,計(jì)算成16進(jìn)制,剩余的33-32位0x10,轉(zhuǎn)換為10進(jìn)制為2。其實(shí)際增加引用計(jì)數(shù)就是1

swift與OC強(qiáng)引用計(jì)數(shù)對(duì)比

OC中創(chuàng)建實(shí)例對(duì)象時(shí)為0

swift中創(chuàng)建實(shí)例對(duì)象時(shí)默認(rèn)為1

內(nèi)存管理 - 弱引用

我們先看下面的代碼

class HZMTeacher{
    var age: Int = 20
    var name: String = "HZM"
    var stu : HZMStudent?
}

class HZMStudent {
    var age: Int = 18
    var name: String = "HZM2"
    var teacher: HZMTeacher?
}

func testCount() {
    var t = HZMTeacher()
    weak var t1 = t
    print("end")
}

查看t的引用計(jì)數(shù)變化

弱引用01.png

  • 本質(zhì)上 t1 = t 并沒有增加引用計(jì)數(shù),但是t的地址存放的內(nèi)容卻發(fā)生了變化
  • 弱引用聲明的變量是一個(gè)可選值,因?yàn)樵诔绦蜻\(yùn)行過(guò)程中是允許將當(dāng)前變量設(shè)置為nil

t1處加斷點(diǎn),查看匯編

弱引用02.png

查看 源碼中的 swift_weakInit函數(shù),這個(gè)函數(shù)是由WeakReference來(lái)調(diào)用的,相當(dāng)于weak字段在編譯器聲明過(guò)程中就自定義了一個(gè)WeakReference的對(duì)象,其目的在于管理弱引用

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

進(jìn)入nativeInit

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

進(jìn)入formWeakReference,創(chuàng)建sideTable

template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  //創(chuàng)建 sideTable
  auto side = allocateSideTable(true);
  if (side)
  // 如果創(chuàng)建成功,則增加弱引用
    return side->incrementWeak();
  else
    return nullptr;
}

進(jìn)入allocateSideTable

template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
  // 第一步、先拿到原本的引用計(jì)數(shù)
  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
  //第二步、創(chuàng)建sideTable
  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
  //第三步、將創(chuàng)建的地址給到InlineRefCountBits
  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、先拿到原本的引用計(jì)數(shù)
2、創(chuàng)建sideTable
3、將創(chuàng)建的sideTable地址給InlineRefCountBits,并查看其初始化方法,根據(jù)sideTable地址作了偏移操作并存儲(chǔ)到內(nèi)存,相當(dāng)于將sideTable直接存儲(chǔ)到了64位的變量中

弱引用03.png

所以上面的0xc0000000200abb32HeapObjectSideTableEntry實(shí)例對(duì)象的內(nèi)存地址,即散列表的地址(除去63、62位)

第一反應(yīng)為啥要存它?我們繼續(xù)看
查看HeapObjectSideTableEntry定義,其中有object對(duì)象、refCounts

弱引用04.png

繼續(xù)往里進(jìn)
進(jìn)入SideTableRefCounts,同InlineRefCounts類似,實(shí)際做事的是SideTableRefCountBits,繼承自RefCountBitsT(存的是uint64_t類型的64位的信息),還有一個(gè)uint32_tweakBits,即32位的位域信息

弱引用05.png

0xc0000000200a45c4為例,將62、63位清零,變成0x200A45C4,然后左移3位(即InlineRefCountBits初始化方法),變成0x100522E20HeapObjectSideTableEntry對(duì)象地址,即散列表地址,然后通過(guò)x/8g讀取(直接用編程計(jì)算器敲的)

弱引用06.png

發(fā)現(xiàn)最終結(jié)果的首地址就是我們當(dāng)前的實(shí)例對(duì)象
并且第二行是我們的強(qiáng)引用計(jì)數(shù)弱引用計(jì)數(shù)

總結(jié)一下

對(duì)于HeapObject來(lái)說(shuō),其refCounts有兩種:

  • 無(wú)弱引用:strongCount + unownedCount
  • 有弱引用:object + xxx + (strongCount + unownedCount) + weakCount
HeapObject {
    InlineRefCountBit {strong count + unowned count }
    
    HeapObjectSideTableEntry{
        HeapObject *object
        xxx
        strong Count + unowned Count(uint64_t)//64位
        weak count(uint32_t)//32位
    }
}

循環(huán)引用

func test(){
    var age = 10
    let closure = {
        age += 1
    }
    closure()
    print(age)
}

test()

<!--打印結(jié)果--!>
11

從輸出結(jié)果中可以看出:閉包內(nèi)部對(duì)變量的修改將會(huì)改變外部原始變量的值,主要原因是閉包會(huì)捕獲外部變量,這個(gè)與OC中的block是一致的

來(lái)看下面代碼

class HZMTeacher {
    var age = 18
    //反初始化器(當(dāng)前實(shí)例對(duì)象即將被回收)
    deinit {
        print("HZMTeacher deinit")
    }
}
func test(){
    var t = HZMTeacher()
}
test()

<!--打印結(jié)果--!>
HZMTeacher deinit

修改例子,通過(guò)閉包修改其屬性值

class HZMTeacher {
    var age = 18
    //反初始化器(當(dāng)前實(shí)例對(duì)象即將被回收)
    deinit {
        print("HZMTeacher deinit")
    }
}
var t = HZMTeacher()
let clourse = {
    t.age += 1
}
clourse()

<!--打印結(jié)果--!>
19

再次修改

class HZMTeacher {
    var age = 18
    //反初始化器(當(dāng)前實(shí)例對(duì)象即將被回收)
    deinit {
        print("HZMTeacher deinit")
    }
}


func test(){
    var t = HZMTeacher()

    let clourse = {
        t.age += 1
    }
    clourse()
}
<!--運(yùn)行結(jié)果-->
HZMTeacher deinit

根據(jù)運(yùn)行結(jié)果我們發(fā)現(xiàn),閉包對(duì) t 并沒有強(qiáng)引用

繼續(xù)修改

class HZMTeacher {
    var age = 18
    
    var completionBlock: (() ->())?
    
    deinit {
        print("HZMTeacher deinit")
    }
}

func test(){
    var t = HZMTeacher()
    t.completionBlock = {
        t.age += 1
    }
}
test()

從運(yùn)行結(jié)果發(fā)現(xiàn),沒有執(zhí)行deinit方法,即沒有打印HZMTeacher deinit,所以這里有循環(huán)引用

循環(huán)引用的解決方法

Swift中有兩種解決方式
1.weak
使用weak修飾閉包傳入的參數(shù),其中參數(shù)的類型是optional

func test(){
    var t = HZMTeacher()
    t.completionBlock = { [weak t] in
        t?.age += 1
    } 
}

2.unowned(無(wú)主引用)
使用unowned修飾閉包參數(shù),與weak的區(qū)別在于unowned不允許被設(shè)置為nil,即總是假定有值的,容易出現(xiàn)野指針情況。

func test(){
    var t = HZMTeacher()
    t.completionBlock = { [unowned t] in
        t.age += 1
    } 
}

捕獲列表

[weak t] / [unowned t] 在swift中被稱為捕獲列表
1.定義在參數(shù)列表之前
2.[書寫方式]捕獲列表被寫成用逗號(hào)括起來(lái)的表達(dá)式列表,并用方括號(hào)括起來(lái)
3.如果使用捕獲列表,則即使省略參數(shù)名稱、參數(shù)類型和返回類型,也必須使用in關(guān)鍵字
4.[weak t] 就是取t的弱引用對(duì)象 類似weakself

我們看下面代碼

func test(){
    var age = 0
    var height = 0.0
    //將變量age用來(lái)初始化捕獲列表中的常量age,即將0給了閉包中的age(值拷貝)
    let clourse = {[age] in
        print(age)
        print(height)
    }
    age = 10
    height = 1.85
    clourse()
}

<!--打印結(jié)果--!>
0
1.85

所以從結(jié)果中可以得出:對(duì)于捕獲列表中的每個(gè)常量,閉包會(huì)利用周圍范圍內(nèi)具有相同名稱的常量/變量,來(lái)初始化捕獲列表中定義的常量。有以下幾點(diǎn)說(shuō)明:

  • 捕獲列表中的常量是值拷貝,而不是引用

  • 捕獲列表中的常量的相當(dāng)于復(fù)制了變量age的值

  • 捕獲列表中的常量是只讀的,即不可修改

swift中Runtime探索

class HZMTeacher {
    var age: Int = 18
    func teach(){
        print("teach")
    }
}

let t = HZMTeacher()

func test(){
    var methodCount: UInt32 = 0
    let methodList = class_copyMethodList(HZMTeacher.self, &methodCount)
    for i in 0..<numericCast(methodCount) {
        if let method = methodList?[I]{
            let methodName = method_getName(method)
            print("方法列表:\(methodName)")
        }else{
            print("not found method")
        }
    }
    
    var count: UInt32 = 0
    let proList = class_copyPropertyList(HZMTeacher.self, &count)
    for i in 0..<numericCast(count) {
        if let property = proList?[I]{
            let propertyName = property_getName(property)
            print("屬性成員屬性:\(property)")
        }else{
            print("沒有找到你要的屬性")
        }
    }
    print("test run")
}
test()

<!--打印結(jié)果--!>
test run

結(jié)果發(fā)現(xiàn)并沒有打印方法和屬性

  • 給屬性 和 方法 都加上 @objc,可以打印嗎?
    Runtime01.png

從運(yùn)行結(jié)果看,是可以打印,但是由于類并沒有暴露給OC,所以O(shè)C是無(wú)法使用的,這樣做是沒有意義的

  • 如果swift的類繼承NSObject,沒有@objc修飾屬性和方法,是否可以打印全部屬性+方法?
    Runtime02.png

從結(jié)果發(fā)現(xiàn)獲取的只有init方法,主要是因?yàn)樵?swift.h文件中暴露出來(lái)的只有init方法

如果想讓OC能使用,必須類繼承NSObject + @objc修飾屬性、方法

Runtime03.png

  • 如果去掉@objc修飾屬性,將方法改成dynamic修飾,是否可以打印方法?
    Runtime04.png

從結(jié)果可以看出,依舊不能被OC獲取到,需要修改為@objc dynamic修飾

結(jié)論:

對(duì)于純swift類來(lái)說(shuō),沒有動(dòng)態(tài)特性dynamic(因?yàn)閟wift是靜態(tài)語(yǔ)言),方法和屬性不加任何修飾符的情況下,已經(jīng)不具備runtime特性,此時(shí)的方法調(diào)度,依舊是函數(shù)表調(diào)度即V_Table調(diào)度

對(duì)于純swift類,方法和屬性添加@objc標(biāo)識(shí)的情況下,可以通過(guò)runtime API獲取到,但是在OC中是無(wú)法進(jìn)行調(diào)度的,原因是因?yàn)?code>swift.h文件中沒有swift類的聲明

對(duì)于繼承自NSObject類來(lái)說(shuō),如果想要?jiǎng)討B(tài)的獲取當(dāng)前屬性+方法,必須在其聲明前添加 @objc關(guān)鍵字,如果想要使用方法交換,還必須在屬性+方法前添加dynamic關(guān)鍵字,否則當(dāng)前屬性+方法只是暴露給OC使用,而不具備任何動(dòng)態(tài)特性

objc源碼驗(yàn)證(由于xcode12.2暫時(shí)無(wú)法運(yùn)行objc源碼,下列驗(yàn)證圖片僅供參考)

  • 進(jìn)入class_copyMethodList源碼,斷住,查看此時(shí)的cls,其中data()存儲(chǔ)類的信息
Runtime06.png

進(jìn)入data,打印bits、superclass

Runtime07.png

從這里可以得出swift中有默認(rèn)基類,即_SwiftObject

打印methods

Runtime08.png

swift源碼中搜索_SwiftObject,繼承自NSObject,在內(nèi)存結(jié)構(gòu)上與OC基本類似

#if __has_attribute(objc_root_class)
__attribute__((__objc_root_class__))
#endif
SWIFT_RUNTIME_EXPORT @interface SwiftObject<NSObject> {
 @private
  Class isa;
  //refCounts
  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}

在之前的文章中有提到,其中TargetAnyClassMetadata繼承自TargetHeapMetaData,其中只有一個(gè)屬性kindTargetAnyClassMetadata有四個(gè)屬性:isa、superclass、cacheData、data即bits

Runtime09.png

所以swift為了保留和OC交互,其在底層存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)上和OC是一致的

objc源碼中搜索swift_class_t,繼承自objc_class,保留了OC模板類的4個(gè)屬性,其次才是自己的屬性

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);
    }
};

為什么繼承NSObject?
必須通過(guò)NSObject聲明,來(lái)幫助編譯器判斷,當(dāng)前類是一個(gè)和OC交互的類

元類型、AnyClass、Self

AnyObject

AnyObject:代表任意類的instance、類的類型、僅類遵守的協(xié)議

class HZMTeacher: NSObject {
    var age: Int = 18
}

var t = HZMTeacher()

//此時(shí)代表的就是當(dāng)前HZMTeacher的實(shí)例對(duì)象
var t1: AnyObject = t

//此時(shí)代表的是HZMTeacher這個(gè)類的類型
var t2: AnyObject = HZMTeacher

//繼承自AnyObject,表示JSONMap協(xié)議只有類才可以遵守
protocol JSONMap: AnyObject { }

如果是結(jié)構(gòu)體遵守協(xié)議,會(huì)報(bào)錯(cuò),表示JSONMap協(xié)議只有類才可以遵守


Anyobject01.png

Any

Any:代表任意類型,包括 function類型 或者Optional類型,可以理解為AnyObject是Any的子集

//如果使用AnyObject會(huì)報(bào)錯(cuò),而Any不會(huì)
    var array: [Any] = [1, "HZM", "", true]

AnyClass

AnyClass:代表任意類的實(shí)例的類型 ,類型是AnyObject.Type

查看定義,是public typealias AnyClass = AnyObject.Type

T.self & T.Type

T.self:如果T是實(shí)例對(duì)象,返回的就是它本身,如果T是類,那么返回的是MetaData
T.Type:一種類型
T.selfT.Type類型

簡(jiǎn)單看一個(gè)例子

//此時(shí)的self類型是  HZMTeacher.Type
var t = HZMTeacher.self

再看下下面的例子:

var t = HZMTeacher()
//實(shí)例對(duì)象地址:實(shí)例對(duì)象.self 返回實(shí)例對(duì)象本身
var t1 = t.self
//存儲(chǔ)metadata元類型
var t2 = HZMTeacher.self
Type01.png

type(of:)

type(of:):用來(lái)獲取一個(gè)值的動(dòng)態(tài)類型

var age = 10 as NSNumber
print(type(of: age))

<!--打印結(jié)果--!>
__NSCFNumber


//value - static type 靜態(tài)類型:編譯時(shí)期確定好的
//type(of:) - dynamic type:Int
var age = 10
//value的靜態(tài)類型就是Any
func test(_ value: Any){
    
    print(type(of: value))
}

test(age)

<!--打印結(jié)果--!>
Int

實(shí)踐一下下

class HZMTeacher{
    var age = 18
    var double = 1.85
    func teach(){
        print("HZMTeacher teach")
    }
}
class HZMPartTimeTeacher: HZMTeacher {
    override func teach() {
        print("HZMPartTimeTeacher teach")
    }
}

func test(_ value: HZMTeacher){
    let valueType = type(of: value)
    value.teach()
    print(value)
}
var t = HZMPartTimeTeacher()
test(t)

<!--打印結(jié)果--!>
HZMPartTimeTeacher teach
HZMTest.HZMPartTimeTeacher

運(yùn)行時(shí)value的類型還是HZMPartTimeTeacher,只是傳參的時(shí)候告訴編譯器是HZMTeacher類型

protocol TestProtocol {
    
}
class HZMTeacher: TestProtocol{
    var age = 18
    var double = 1.85
    func teach(){
        print("HZMTeacher teach")
    }
}

func test(_ value: TestProtocol){
    let valueType = type(of: value)
    print(valueType)
}
var t = HZMTeacher()
let t1: TestProtocol = HZMTeacher()
test(t)
test(t1)

<!--打印結(jié)果--!>
HZMTeacher
HZMTeacher

3.如果將test中參數(shù)的類型修改為泛型,此時(shí)的打印是什么?

func test<T>(_ value: T){
    let valueType = type(of: value)
    print(valueType)
}

<!--打印結(jié)果--!>
HZMTeacher
TestProtocol

從結(jié)果中發(fā)現(xiàn),打印并不一致,原因是因?yàn)楫?dāng)有協(xié)議、泛型時(shí),當(dāng)前的編譯器并不能推斷出準(zhǔn)確的類型,需要將value轉(zhuǎn)換為Any,修改后的代碼如下:

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

總結(jié)

當(dāng)無(wú)弱引用時(shí),HeapObject中的refCounts等于 strongCount + unownedCount

當(dāng)有弱引用時(shí),HeapObject中的refCounts等于 object + xxx + (strongCount + unownedCount) + weakCount

循環(huán)應(yīng)用可以通過(guò)weak / unowned修飾參數(shù)來(lái)解決

swift中閉包的捕獲列表值拷貝,即深拷貝,是一個(gè)只讀的常量

swift由于是靜態(tài)語(yǔ)言,所以屬性、方法在不加任何修飾符的情況下時(shí)是不具備動(dòng)態(tài)性即Runtime特性的,此時(shí)的方法調(diào)度是V-Table函數(shù)表調(diào)度

如果想要OC使用swift類中的方法、屬性,需要class繼承NSObject,并使用@objc修飾

如果想要使用方法交換,除了繼承NSObject+@objc修飾,還必須使用dynamic修飾

Any:任意類型,包括function類型、optional類型

AnyObject:任意類的instance、類的類型、僅類遵守的協(xié)議,可以看作是Any的子類

AnyClass:任意實(shí)例類型,類型是AnyObject.Type

T.self:如果T是實(shí)例對(duì)象,則表示它本身,如果是類,則表示metadata.T.self的類型是T.Type

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,559評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,581評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,922評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,096評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,639評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,374評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,591評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,789評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,322評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,554評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容