Swift內(nèi)存管理

OC一樣,Swift中也是通過引用計數(shù)的方式來管理對象的內(nèi)存的。在Swift類 結(jié)構(gòu)探究中,分析過引用計數(shù)refCounts,它是RefCounts類型,class類型,占8字節(jié)大小。

一、強引用

class Animal {
    var age: Int = 10
    var name: String = "dog"
}
var t = Animal()
var t1 = t
var t2 = t

斷點,查看t的的內(nèi)存,refCounts0x0000000600000003

image.png

1.1

HeapObject開始分析引用計數(shù)的真正表示形態(tài),源碼 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_t,即64位整型數(shù)組。

1.2 對象創(chuàng)建

然后,繼續(xù)分析swift中對象創(chuàng)建的底層方法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方法中,干事的是RefCountBits

  enum Initialized_t { Initialized };
  
  //對應(yīng)的RefCounts方法
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}

進(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))
{ }

referCounts的本質(zhì)是RefCountBitsInt中的type屬性,而RefCountBitsInt又是模板類RefCountBitsT的模板類型T,所以RefCountBits(0, 1)實質(zhì)調(diào)用的是模板類RefCountBitsT構(gòu)造方法,strongExtraCount傳值為0unownedCount傳值為1。
RefCountsBit的結(jié)構(gòu)圖,如下所示:

image.png

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

其中需要重點關(guān)注UnownedRefCountStrongExtraRefCount。至此,我們分析一下上面的引用計數(shù)值0x0000000600000003,用二進(jìn)制展示:

image.png

如圖,33位置開始的強引用計數(shù)StrongExtraRefCount0011,轉(zhuǎn)換成十進(jìn)制就是3。
下面通過sil驗證一下:
image.png

關(guān)于copy_addr,sil文檔有解釋,其實現(xiàn)是對object的引用計數(shù)作+1操作。有興趣的可以自己去查一下。
需要注意的是:var t = Animal()此時已經(jīng)有了引用計數(shù),即,OC中創(chuàng)建實例對象時為0;Swift中創(chuàng)建實例對象時默認(rèn)為1。
可以通過CFGetRetainCount獲取引用計數(shù):
image.png

二、弱引用

class Animal {
    var age: Int = 10
    var name: String = "cat"
    var dog: Dog?
}

class Dog {
    var age = 20
    var animal: Animal?
}

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

test()

查看 t的引用計數(shù):

image.png

弱引用聲明變量是一個可選值,因為在程序運行過程中是允許將當(dāng)前變量設(shè)置為nil的。
t1處加斷點,查看匯編,發(fā)現(xiàn):swift_weakInit。查看源碼,這個函數(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;
}

總結(jié):

  1. 先拿到原本的引用計數(shù);
  2. 創(chuàng)建sideTable
  3. 將創(chuàng)建的sideTable地址給InlineRefCountBits,并查看其初始化方法,根據(jù)sideTable地址作了偏移操作并存儲到內(nèi)存,相當(dāng)于將sideTable直接存儲到了64位的變量中。

通過上面的底層流程分析,我們可以get到關(guān)鍵的2點:
通過HeapObjectSideTableEntry初始化散列表

class HeapObjectSideTableEntry {
  // FIXME: does object need to be atomic?
  std::atomic<HeapObject*> object;
  SideTableRefCounts refCounts;
...
}

上述源碼中可知,弱引用對象對應(yīng)的引用計數(shù)refCountsSideTableRefCounts類型,而強引用對象的是InlineRefCounts類型。
接下來我們看看SideTableRefCounts

typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;

繼續(xù)搜索SideTableRefCountBits

image.png

里面包含了成員uint32_t weakBits,即一個32位域的信息。

通過InlineRefCountBits初始化散列表的數(shù)據(jù)

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  RefCountBitsT(HeapObjectSideTableEntry* side)
    : bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
           | (BitsType(1) << Offsets::UseSlowRCShift)
           | (BitsType(1) << Offsets::SideTableMarkShift))
  {
    assert(refcountIsInline);
  }

這里繼承的bits構(gòu)造方法,而bits定義

BitsType bits;

typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
    BitsType;

強引用一樣,來到了RefCountBitsInt,這個之前分析過,就是uint64_t類型,存的是64位域信息。

綜合12兩點的論述可得出:64位 用于記錄 原有引用計數(shù);32位 用于記錄 弱引用計數(shù)

為何t的引用計數(shù)是:0xc0000000200e08ba?

上述分析中我們知道,在 InlineRefCountBits初始化散列表的數(shù)據(jù)時,執(zhí)行了(reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits這句代碼,而

static const size_t SideTableUnusedLowBits = 3;

side右移了3位,所以此時,將0xc0000000200e08ba 左移3位0x1007045D0,就是散列表的地址。再x/8g查看:

image.png

三、循環(huán)引用

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

//打印結(jié)果
11

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

deinit
class Animal {
    var age = 10
    deinit {
        print("Animal deinit")
    }
}
func test(){
    var t = Animal()
    let clourse = {
        t.age += 10
    }
    clourse()
    print(t.age)
}
test()

//打印結(jié)果
//Animal deinit

可見,deinit是在當(dāng)前實例對象即將被回收時觸發(fā)。
接下來,我們把age放到類中,閉包中再去修改時會怎樣:

class Animal {
    var age = 10
    deinit {
        print("Animal deinit")
    }
}

var t = Animal()
let clourse = {
    t.age += 10
}
clourse()
print(t.age)

//打印結(jié)果
//20
//Animal deinit

Animal類增加閉包屬性

class Animal {
    var age = 10
    var completionBlock: (() ->())?
    deinit {
        print("Animal deinit")
    }
}
func test(){
    var t = Animal()
    t.completionBlock = {
        t.age += 10
    }
    print(t.age)
}
test()
//打印結(jié)果
//10

從運行結(jié)果發(fā)現(xiàn),t.age還是1,并且沒有執(zhí)行deinit方法,這里存在循環(huán)引用

循環(huán)引用的處理

1 weak修飾閉包傳入的參數(shù)。weak修飾后的變量是optional類型,所以t?.age += 1。

func test(){
    var t = Animal()
    t.completionBlock = { [weak t] in
        t?.age += 10
    }
    print(t.age)
}

2 unowned修飾閉包參數(shù)

func test(){
    var t = Animal()
    t.completionBlock = { [unowned t] in
        t.age += 10
    }
    print(t.age)
}

[weak t][unowned t],稱為捕獲列表。定義在參數(shù)列表之前,如果使用捕獲列表,那么即使省略參數(shù)名稱、參數(shù)類型和返回類型,也必須使用in關(guān)鍵字

捕獲列表的值
func test(){
    var age = 1
    var height = 1.0
    let clourse = {[age] in
        print(age)
        print(height)
    }
    age = 10
    height = 1.85
    clourse()
}
test()

//打印結(jié)果
//1
//1.85

如上,age值改變了,height未被捕獲,值未變。

捕獲列表特點: 捕獲列表中的常量是值拷貝,而不是引用拷貝,因此,它是只讀的,即不可修改。

Runtime

Swift是一門靜態(tài)語言,本身不具備動態(tài)性,不像OCRuntime運行時的機(jī)制(此處指OC提供運行時API供程序員操作)。但由于Swift兼容OC,所以可以轉(zhuǎn)成OC類和函數(shù),利用OC的運行時機(jī)制,來實現(xiàn)動態(tài)性。

class Animal {
    var age: Int = 18
    func eat(){
        print("eat")
    }
}

let t = Animal()

func test(){
    var methodCount: UInt32 = 0
    let methodList = class_copyMethodList(Animal.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(Animal.self, &count)
    for i in 0..<numericCast(count) {
        if let property = proList?[i]{
            let propertyName = String(utf8String: property_getName(property))
            print("------成員屬性名稱:\(propertyName!)")
        }else{
            print("沒有找到你要的屬性")
        }
    }
    print("test run")
}
test()

嘗試
1、以上代碼,沒有打印。
2、給方法和屬性添加@objc修飾,可以打印
3、類Animal繼承NSObject,不用@objc修飾。只打印了初始化方法,因為在swift.h文件中暴露出來的只有init方法。
注意:如果要讓OC調(diào)用,那么必須 繼承NSObject + @objc修飾
4、去掉@objc修飾,改成dynamic修飾 + NSObject,同3。
5、@objc修飾 + dynamic修飾 + NSObject。
關(guān)于方法調(diào)用,參考Swift方法調(diào)用

補充

AnyObject:代表任意類的instance,類的類型,類遵守的協(xié)議,但struct?不行。
Any:代表任意類型,包括funcation類型或者Optional類型。
AnyClass:代表任意實例的類型。
T.self:如果T為實例對象,返回的就是它本身;T是類,返回的是Metadata。
type(of:):用于獲取一個值的動態(tài)類型,編譯期時,value的類型是Any類型;運行期時,type(of:)獲取的是真實類型。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容