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

本文主要介紹Swift中的內(nèi)存管理,涉及引用計數(shù)、弱引用卵皂、強(qiáng)引用、循環(huán)引用與Runtime等砚亭。

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

在Swift中也是使用ARC來追蹤和管理內(nèi)存的灯变,下面我們通過一個案例來進(jìn)行分析

class SunriseTeacher {
    var age: Int = 18
    var name: String = "Sunrise"
}

var t = SunriseTeacher()
var t1 = t
var t2 = t
  • 查看t的內(nèi)存情況,為什么其中的refCounts是0x0000000600000003
強(qiáng)引用-01

在分析類時(參考這篇文章Swift進(jìn)階 02:類捅膘、對象添祸、屬性)有這么一個類HeapObject,下面繼續(xù)通過這個類來分析t的引用計數(shù)

  • 分析源碼 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的實質(zhì)是將RefCountBitsInt中的type屬性取了一個別名鲤遥,所以bits的真正類型是uint64_t64位整型數(shù)組

然后來繼續(xù)分析Swift中對象創(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定義沐寺,是一個枚舉,其對應(yīng)的refCounts方法中的
enum Initialized_t { Initialized };
  
// 對應(yīng)的RefCounts方法
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}

從這里看出真正干事的是RefCountBits

  • 進(jìn)入RefCountBits定義盖奈,也是一個模板定義
template <typename RefCountBits>
class RefCounts {
  std::atomic<RefCountBits> refCounts;
  ...
}

所以真正的初始化地方是下面這個混坞,實際上是做了一個位域操作,根據(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)钢坦,如下所示

強(qiáng)引用-02
  • isImmortal(0)
  • UnownedRefCount(1-31): unowned的引用計數(shù)
  • isDeinitingMask(32):是否進(jìn)行釋放操作
  • StrongExtraRefCount(33-62): 強(qiáng)引用計數(shù)
  • UseSlowRC(63

重點關(guān)注UnownedRefCountStrongExtraRefCount

  • 將t的refCounts用二進(jìn)制展示究孕,其中強(qiáng)引用計數(shù)為3
強(qiáng)引用-03
分析SIL代碼
  • 當(dāng)只有t實例變量時
image
  • 當(dāng)有t與t1時,查看是否有strong_retain操作
alloc_global @main.t1 : main.SunriseTeacher   // id: %8
%9 = global_addr @main.t1 : main.SunriseTeacher : $*SunriseTeacher // user: %11
%10 = begin_access [read] [dynamic] %3 : $*SunriseTeacher // users: %12, %11
copy_addr %10 to [initialization] %9 : $*SunriseTeacher // id: %11

// 其中copy_addr等價于
- %new = load $* SunriseTeacher
- strong_retain %new
- store %new to %9
image

SIL官方文檔中關(guān)于copy_addr的解釋如下

image
  • 其中的strong_retain對應(yīng)的就是 swift_retain爹凹,其內(nèi)部是一個宏定義厨诸,內(nèi)部是_swift_retain_,其實現(xiàn)是對object的引用計數(shù)作+1操作
// 內(nèi)部是一個宏定義
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.
// 對inc做強(qiáng)制類型轉(zhuǎn)換為 BitsType
// 其中 BitsType(inc) << Offsets::StrongExtraRefCountShift 等價于 1<<33位颗管,16進(jìn)制為 0x200000000
// 這里的 bits += 0x200000000陷遮,將對應(yīng)的33-63轉(zhuǎn)換為10進(jìn)制,為
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}

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

  • 只有t時的refCounts0x0000000200000003
  • t 與 t1時的refCounts·是 ·0x0000000400000003 = 0x0000000200000003 + 0x200000000
  • t 比吭、 t1 绽族、 t2 時的refCounts0x0000000600000003 = 0x0000000400000003 + 0x200000000

針對上面的例子,可以通過CFGetRetainCount獲取引用計數(shù),發(fā)現(xiàn)依次是 2衩藤、3项秉、4,默認(rèn)多了一個1

image
  • 如果將t慷彤、t1娄蔼、t2放入函數(shù)中,還會再次retain一次
image

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

swift與OC強(qiáng)引用計數(shù)對比
  • OC中創(chuàng)建實例對象時為0
  • Swift中創(chuàng)建實例對象時默認(rèn)為1

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

以下面為例:

class SunriseTeacher {
    var age: Int = 18
    var name: String = "Sunrise"
    var student:SunriseStudent?
}

class SunriseStudent {
    var age = 20
    var teacher: SunriseTeacher?
}

func test() {
    var t = SunriseTeacher()
    weak var t1 = t
    
    print("end")
}

test()
  • 查看t的引用計數(shù)變化
image
  • 弱引用聲明的變量是一個可選值前标,因為在程序運(yùn)行過程中是允許將當(dāng)前變量設(shè)置為nil
  • t1處加斷點坠韩,查看匯編
image
  • 查看 swift_weakInit函數(shù),這個函數(shù)是由WeakReference來調(diào)用的炼列,相當(dāng)于weak字段在編譯器聲明過程中就自定義了一個WeakReference的對象只搁,其目的在于管理弱引用
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)
{
  // 1氢惋、先拿到原本的引用計數(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
  // 2、創(chuàng)建sideTable
  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
  // 3稽犁、將創(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焰望、先拿到原本的引用計數(shù)
  • 2、創(chuàng)建sideTable
  • 3已亥、將創(chuàng)建的sideTable地址給InlineRefCountBits熊赖,并查看其初始化方法,根據(jù)sideTable地址作了偏移操作并存儲到內(nèi)存虑椎,相當(dāng)于將sideTable直接存儲到了64位的變量中
image

所以上面的0xc000000020809a6cHeapObjectSideTableEntry實例對象的內(nèi)存地址震鹉,即散列表的地址(除去63的妖、62位)

  • 查看HeapObjectSideTableEntry定義,其中有object對象足陨、refCounts
image
  • 進(jìn)入SideTableRefCounts嫂粟,同InlineRefCounts類似,實際做事的是SideTableRefCountBits墨缘,繼承自RefCountBitsT(存的是uint64_t類型的64位的信息)星虹,還有一個uint32_tweakBits,即32位的位域信息
    • 64位 用于記錄 原有引用計數(shù)
    • 32位 用于記錄 弱引用計數(shù)
image

0xc0000000200e9cf8為例镊讼,將62宽涌、63位清零,變成0x200E9CF8蝶棋,然后左移3位(即InlineRefCountBits初始化方法)卸亮,變成0x10074E7C0HeapObjectSideTableEntry對象地址,即散列表地址玩裙,然后通過x/8g讀取

image
問題:如果此時再加一個強(qiáng)引用t2

查看其refCounts兼贸,t2是執(zhí)行了strong_retain的

image
  • 源碼查看 _swift_retain_ -> increment -> incrementSlow -> incrementStrong
image
總結(jié)

對于HeapObject來說,其refCounts有兩種:

  • 無弱引用: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位
    }
}

內(nèi)存管理 - 循環(huán)引用

主要是研究閉包捕獲外部變量吃溅,以下面代碼為例

var age = 10
let clourse = {
    age += 1
}
clourse()
print(age)

// ********** 打印結(jié)果 **********
11

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

  • 定義一個類决侈,在test函數(shù)作用域消失后螺垢,會執(zhí)行init
class SunriseTeacher {
    var age = 18
    // 反初始化器(當(dāng)前實例對象即將被回收)
    deinit {
        print("SunriseTeacher deinit")
    }
}

func test(){
    var t = SunriseTeacher()
}
test()

// ********** 打印結(jié)果 **********
SunriseTeacher deinit
  • 修改例子,通過閉包修改其屬性值
class SunriseTeacher {
    var age = 18
    // 反初始化器(當(dāng)前實例對象即將被回收)
    deinit {
        print("SunriseTeacher deinit")
    }
}

var t = SunriseTeacher()
let clourse = {
    t.age += 1
}

clourse()
print(t.age)

// ********** 打印結(jié)果 **********
19
  • 【修改1】將上面例子修改為如下赖歌,其中閉包是否對t有強(qiáng)引用枉圃?
class SunriseTeacher {
    var age = 18
    // 反初始化器(當(dāng)前實例對象即將被回收)
    deinit {
        print("SunriseTeacher deinit")
    }
}

func test() {
    var t = SunriseTeacher()
    let clourse = {
        t.age += 1
    }

    clourse()
    print(t.age)
}

test()

// ********** 打印結(jié)果 **********
19

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

  • 【修改2】繼續(xù)修改例子為如下庐冯,是否有強(qiáng)引用孽亲?
class SunriseTeacher {
    var age = 18
    var completionBlock:(() -> ())?
    // 反初始化器(當(dāng)前實例對象即將被回收)
    deinit {
        print("SunriseTeacher deinit")
    }
}

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

test()

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

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

有兩種方式可以解決Swift中的循環(huán)引用

  • 【方式一】使用weak修飾閉包傳入的參數(shù)赁酝,其中參數(shù)的類型是optional
func test(){
    var t = SunriseTeacher()
    t.completionBlock = { [weak t] in
        t?.age += 1
    } 
}
  • 【方式二】使用unowned修飾閉包參數(shù)犯祠,與weak的區(qū)別在于unowned不允許被設(shè)置為nil,即總是假定有值
var t = SunriseTeacher()
t.completionBlock = { [unowned t] in
   t.age += 1
}

捕獲列表

  • [weak t] / [unowned t] 在Swift中被稱為捕獲列表
  • 定義在參數(shù)列表之前
  • 【書寫方式】捕獲列表被寫成用逗號括起來的表達(dá)式列表酌呆,并用方括號括起來
  • 如果使用捕獲列表衡载,則即使省略參數(shù)名稱、參數(shù)類型和返回類型隙袁,也必須使用in關(guān)鍵字
  • [weak t] 就是取t的弱引用對象 類似weakSelf

請問下面代碼的clourse()調(diào)用后痰娱,輸出的結(jié)果是什么弃榨?

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

// ********** 打印結(jié)果 **********
0
1.85

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

  • 捕獲列表中的常量是值拷貝坡贺,而不是引用
  • 捕獲列表中的常量相當(dāng)于復(fù)制了變量age的值
  • 捕獲列表中的常量是只讀的官辈,即不可修改

Swift中Runtime探索

請問下面代碼,會打印方法和屬性嗎遍坟?

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

let t = SunriseTeacher()

func test(){
    var methodCount: UInt32 = 0
    let methodList = class_copyMethodList(SunriseTeacher.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(SunriseTeacher.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()

運(yùn)行結(jié)果如下拳亿,發(fā)現(xiàn)并沒有打印方法和屬性

image
  • 【嘗試1】如果給屬性 和 方法 都加上 @objc,可以打印嗎愿伴?
image

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

  • 【嘗試2】如果swift的類繼承NSObject,沒有@objc修飾屬性和方法怎诫,是否可以打印全部屬性+方法瞬雹?
image

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

  • 如果想讓OC能使用刽虹,必須類繼承NSObject + @objc修飾屬性酗捌、方法
image
  • 如果去掉@objc修飾屬性,將方法改成dynamic修飾涌哲,是否可以打印方法胖缤?
image

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

image
結(jié)論
  • 對于純Swift類來說哪廓,沒有動態(tài)特性dynamic(因為Swift靜態(tài)語言),方法和屬性不加任何修飾符的情況下初烘,已經(jīng)不具備runtime特性涡真,此時的方法調(diào)度,依舊是函數(shù)表調(diào)度即V_Table調(diào)度
  • 對于純Swift類肾筐,方法和屬性添加@objc標(biāo)識的情況下哆料,可以通過runtime的API獲取到,但是在OC中是無法進(jìn)行調(diào)度的吗铐,原因是因為swift.h文件中沒有Swift類的聲明
  • 對于繼承自NSObject類來說东亦,如果想要動態(tài)的獲取當(dāng)前屬性+方法,必須在其聲明前添加@objc關(guān)鍵字唬渗,如果想要使用方法交換典阵,還必須在屬性+方法前添加dynamic關(guān)鍵字奋渔,否則當(dāng)前屬性+方法只是暴露給OC使用,而不具備任何動態(tài)特性
objc源碼驗證

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

  • 進(jìn)入class_copyMethodList源碼嫉鲸,斷住,查看此時的cls歹啼,其中data()存儲類的信息
image
  • 進(jìn)入data充坑,打印bits、superclass
image

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

  • 打印methods
image
  • 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;
}
image

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

  • objc源碼中搜索swift_class_t激才,繼承自objc_class拓型,保留了OC模板類的4個屬性,其次才是自己的屬性
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瘸恼?
必須通過NSObject聲明劣挫,來幫助編譯器判斷,當(dāng)前類是一個和OC交互的類

元類型东帅、AnyClass压固、Self

AnyObject
  • AnyObject:代表任意類的instance類的類型靠闭、僅類遵守的協(xié)議
class SunriseTeacher: NSObject {
    var age: Int = 18
}

var t = SunriseTeacher()

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

// 此時代表的是SunriseTeacher這個類的類型
var t2: AnyObject = SunriseTeacher.self

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

例如如果是結(jié)構(gòu)體遵守協(xié)議,會報錯

image

需要將struct修改成class

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

class SunriseJSONMap: JSONMap {
    
}
Any
  • Any:代表任意類型拦键,包括function類型或者Optional類型,可以理解為AnyObjectAny的子集
// 如果使用AnyObject會報錯扇调,而Any不會
var array: [Any] = [1, "wrs", "", true]
AnyClass
  • AnyClass:代表任意實例的類型 矿咕,類型是AnyObject.Type
    • 查看定義,是public typealias AnyClass = AnyObject.Type
T.self & T.Type
  • T.self
    • 如果T是實例對象狼钮,返回的就是它本身
    • 如果T是碳柱,那么返回的是MetaData
  • T.Type:一種類型
  • T.selfT.Type類型
// 此時的self類型是  SunriseTeacher.Type
var t = SunriseTeacher.self

打印結(jié)果如下:

image
  • 查看t1、t2存儲的是什么熬芜?
class SunriseTeacher {
    var age: Int = 18
}

var t = SunriseTeacher()
// 實例對象地址:實例對象.self 返回實例對象本身
var t1 = t.self
// 存儲metadata元類型
var t2 = SunriseTeacher.self

print("end")
image
type(of:)
  • type(of:):用來獲取一個值的動態(tài)類型
// demo1
var age = 10 as NSNumber
print(type(of: age))

// ********** 打印結(jié)果 **********
__NSCFNumber

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

test(age)

// ********** 打印結(jié)果 **********
Int

實踐

demo1

請問下面這段代碼的打印結(jié)果是什么莲镣?

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

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

// ********** 打印結(jié)果 **********
SunrisePartTimeTeacher teach
_5_Runtime.SunrisePartTimeTeacher
demo2

請問下面代碼的打印結(jié)果是什么?

protocol TestProtocol {}

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

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

var t = SunriseTeacher()
let t1: TestProtocol = SunriseTeacher()
test(t)
test(t1)

// ********** 打印結(jié)果 **********
SunriseTeacher
SunriseTeacher
  • 如果將test中參數(shù)的類型修改為泛型涎拉,此時的打印是什么瑞侮?
func test<T>(_ value: T){
    let valueType = type(of: value)
    print(valueType)
}

// ********** 打印結(jié)果 **********
SunriseTeacher
TestProtocol

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

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

// ********** 打印結(jié)果 **********
SunriseTeacher
SunriseTeacher
demo3

在上面的案例中,如果class_getClassMethod中傳t.self酌住,可以獲取方法列表嗎店归?

protocol TestProtocol {}

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

var t = SunriseTeacher()

func test(){
    var methodCount: UInt32 = 0
    let methodList = class_copyMethodList(t.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(SunriseTeacher.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é)果運(yùn)行看,并不能酪我,因為t.self實例對象本身消痛,即SunriseTeacher,并不是SunriseTeacher.Type類型

總結(jié)

  • 當(dāng)無弱引用時都哭,HeapObject中的refCounts等于strongCount + unownedCount
  • 當(dāng)有弱引用時秩伞,HeapObject中refCounts等于object + xxx + (strongCount + unownedCount) + weakCount
  • 循環(huán)應(yīng)用可以通過weak / unowned修飾參數(shù)來解決
  • Swift中閉包的捕獲列表是值拷貝,即深拷貝欺矫,是一個只讀的常量
  • Swift由于是靜態(tài)語言稠歉,所以屬性、方法在不加任何修飾符的情況下時是不具備動態(tài)性即Runtime特性的汇陆,此時的方法調(diào)度是V-Table函數(shù)表調(diào)度
  • 如果想要OC使用Swift類中的方法怒炸、屬性,需要class繼承NSObject毡代,并使用@objc修飾
  • 如果想要使用方法交換阅羹,除了繼承NSObject+@objc修飾,還必須使用dynamic修飾
  • Any任意類型教寂,包括function類型捏鱼、optional類型
  • AnyObject:任意類的instance、類的類型酪耕、僅類遵守的協(xié)議导梆,可以看作是Any的子類
  • AnyClass任意實例類型,類型是AnyObject.Type
  • T.self:如果T是實例對象,則表示它本身看尼,如果是類递鹉,則表示metadata.T.self的類型是T.Type
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市藏斩,隨后出現(xiàn)的幾起案子躏结,更是在濱河造成了極大的恐慌,老刑警劉巖狰域,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件媳拴,死亡現(xiàn)場離奇詭異,居然都是意外死亡兆览,警方通過查閱死者的電腦和手機(jī)屈溉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抬探,“玉大人子巾,你說我怎么就攤上這事∈荒溃” “怎么了砰左?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長场航。 經(jīng)常有香客問我缠导,道長,這世上最難降的妖魔是什么溉痢? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任僻造,我火速辦了婚禮,結(jié)果婚禮上孩饼,老公的妹妹穿的比我還像新娘髓削。我一直安慰自己,他們只是感情好镀娶,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布立膛。 她就那樣靜靜地躺著,像睡著了一般梯码。 火紅的嫁衣襯著肌膚如雪宝泵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天轩娶,我揣著相機(jī)與錄音儿奶,去河邊找鬼。 笑死鳄抒,一個胖子當(dāng)著我的面吹牛闯捎,可吹牛的內(nèi)容都是我干的椰弊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼瓤鼻,長吁一口氣:“原來是場噩夢啊……” “哼秉版!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起娱仔,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤沐飘,失蹤者是張志新(化名)和其女友劉穎游桩,沒想到半個月后牲迫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡借卧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年盹憎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铐刘。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡陪每,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出镰吵,到底是詐尸還是另有隱情檩禾,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布疤祭,位于F島的核電站盼产,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏勺馆。R本人自食惡果不足惜戏售,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望草穆。 院中可真熱鬧灌灾,春花似錦、人聲如沸悲柱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豌鸡。三九已至嘿般,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間直颅,已是汗流浹背博个。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留功偿,地道東北人盆佣。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓往堡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親共耍。 傳聞我的和親對象是個殘疾皇子虑灰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348

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