本文主要介紹Swift中的內存管理,涉及引用計數(shù)盖桥、弱引用、強引用题翻、循環(huán)引用揩徊、Runtime等
內存管理 - 強引用
在Swift中也是使用自動引用計數(shù)(ARC
)機制來追蹤和管理內存的,下面我們通過一個案例來進行分析
class HTTeacher {
var name: String = "teacher"
var age: Int = 18
}
var t = HTTeacher()
var t1 = t
var t2 = t
- 通過
LLDB
指令查看t的內存情況 藐握,為什么其中的refCounts
是0x0000000600000002靴拱?
源碼分析
在分析類時(參考這篇文章Swift進階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
- 進入
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
其實質是將RefCountBitsInt
中的type屬性取了一個別名,所以bits的真正類型是uint64_t
即64
位整型數(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);
...
}
??
<!--構造函數(shù)-->
// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
- 進入
Initialized
定義他托,是一個枚舉掖肋,其對應的refCounts
方法中,
enum Initialized_t { Initialized };
//對應的RefCounts方法
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}
從這里看出真正起作用的是 RefCountBits
- 進入
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
的結構,如下所示纫溃,
- isImmortal(0)
- UnownedRefCount(1-31): unowned的引用計數(shù)
- isDeinitingMask(32):是否進行釋放操作
- StrongExtraRefCount(33-62): 強引用計數(shù)
- UseSlowRC(63)
重點關注UnownedRefCount
和StrongExtraRefCount
- 將t的
refCounts
用二進制展示腰涧,其中強引用計數(shù)為3
分析SIL代碼
- 當只有t實例變量時
- 當有t + t1時,查看是否有
strong_retain
操作
//SIL中的main
alloc_global @main.t1 : main.HTTeacher // id: %8
%9 = global_addr @main.t1 : main.HTTeacher : $*HTTeacher // user: %11
%10 = begin_access [read] [dynamic] %3 : $*HTTeacher // users: %12, %11
copy_addr %10 to [initialization] %9 : $*HTTeacher // id: %11
//其中copy_addr等價于
- %new = load s*HTTeacher
- strong_retain %new
- store %new to %9
SIL官方文檔中關于 copy_addr
的解釋如下
- 其中的
strong_retain
對應的就是swift_retain
紊浩,其內部是一個宏定義窖铡,內部是_swift_retain_
,其實現(xiàn)是對object
的引用計數(shù)作+1
操作
//內部是一個宏定義
HeapObject *swift::swift_retain(HeapObject *object) {
CALL_IMPL(swift_retain, (object));
}
??
//本質調用的就是 _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
進入费彼,其中是c++中的模板定義,是為了更好的抽象口芍,在其中查找bits
(即decrementStrongExtraRefCount
方法)
LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
bool incrementStrongExtraRefCount(uint32_t inc) {
// This deliberately overflows into the UseSlowRC field.
// 對inc做強制類型轉換為 BitsType
// 其中 BitsType(inc) << Offsets::StrongExtraRefCountShift 等價于 1<<33位敌买,16進制為 0x200000000
//這里的 bits += 0x200000000,將對應的33-63轉換為10進制阶界,為
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}
例如以t
的refCounts
為例(其中62-33位是strongCount
虹钮,每次增加強引用計數(shù)
增加都是在33-62位上增加的,固定的增量為1左移33位
膘融,即0x200000000
)
- 只有
t
時的refCounts
是 0x0000000200000002 -
t + t1
時的refCounts
是 0x0000000400000002 = 0x0000000200000002 + 0x200000000 -
t + t1 + t2
時的refCounts
是 0x0000000600000002 = 0x0000000400000002 + 0x200000000 - 針對上面的例子芙粱,可以通過
CFGetRetainCOunt
獲取引用計數(shù),發(fā)現(xiàn)依次是 2、3氧映、4春畔,默認多了一個1(調用CFGetRetainCOunt
時會增加1)
- 如果將
t、t1岛都、t2
放入函數(shù)中律姨,還會再次retain
一次
為什么是0x200000000
?
因為1左移33位臼疫,其中4位為一組择份,計算成16進制,剩余的33-32位0x10
烫堤,轉換為10進制為2
荣赶。其實際增加引用技術就是1
swift與OC強引用計數(shù)對比
-
OC
中創(chuàng)建實例對象時為0
-
swift
中創(chuàng)建實例對象時默認為1
內存管理 - 弱引用
以下面為例:
class HTTeacher {
var name: String = "teacher"
var age: Int = 18
var stu: HTStudent?
}
class HTStudent {
var age = 18
var teacher: HTTeacher?
}
func test() {
var t = HTTeacher()
weak var t1 = t
print("end")
}
- 查看
t
的引用計數(shù)變化
- 弱引用聲明的變量是一個
可選值
,因為在程序運行過程中是允許將當前變量設置為nil
的
- 在
weak var t1 = t
處加斷點鸽斟,查看匯編
- 源碼查看
swift_weakInit
函數(shù)拔创,這個函數(shù)是由WeakReference
來調用的,相當于weak
字段在編譯器聲明過程中就自定義了一個WeakReference
的對象富蓄,其目的在于管理弱引用
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
- 進入
nativeInit
void nativeInit(HeapObject *object) {
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
- 進入
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;
}
- 進入
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
地址做了偏移操作并存儲到內存,相當于將sideTable直接存儲到了64位的變量中
所以上面的0xc00000002008d8ae
是HeapObjectSideTableEntry
實例對象的內存地址悠菜,即散列表的地址
(除去63舰攒、62位)
- 查看
HeapObjectSideTableEntry
定義,其中有object
對象悔醋、refCounts
- 進入
SideTableRefCounts
摩窃,同InlineRefCounts
類似,實際做事的是SideTableRefCountBits
芬骄,繼承自RefCountBitsT
(存的是uint64_t類型的64位的信息)猾愿,還有一個uint32_t
的weakBits
,即32位的位域信息- 64位 用于記錄 原有引用計數(shù)
- 32位 用于記錄 弱引用計數(shù)
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
以0xc00000002008f0cc
為例账阻,將62蒂秘、63位清零,變成0x2008F0CC
淘太,然后左移3
位(即InlineRefCountBits
初始化方法)姻僧,變成0x100478660
即HeapObjectSideTableEntry對象地址
,即散列表地址
蒲牧,然后通過x/8g
讀取
問題:如果此時再加一個強引用t2
查看其refCounts
撇贺,t2是執(zhí)行了strong_retain
的
- 源碼查看
_swift_retain_ -> increment -> incrementSlow -> incrementStrong
總結
對于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位
}
}
內存管理 - 循環(huán)引用
主要是研究閉包捕獲外部變量
冰抢,以下面代碼為例
func test() {
var age = 10
let clourse = {
age += 1
}
clourse()
print(age)
}
test()
從輸出結果中可以看出:閉包內部對變量的修改將會改變外部原始變量的值
,主要原因是閉包會捕獲外部變量松嘶,這個與OC中的block
是一致的
- 定義一個類,在
test
函數(shù)作用域消失后晒屎,會執(zhí)行deinit
class HTTeacher {
var age = 10
//反初始化器(當前實例對象被回收時調用)
deinit {
print("HTTeacher deinit")
}
}
func test() {
var t = HTTeacher()
}
test()
- 【修改1】修改例子喘蟆,通過閉包修改其屬性值
class HTTeacher {
var age = 10
//反初始化器(當前實例對象被回收時調用)
deinit {
print("HTTeacher deinit")
}
}
func test() {
var t = HTTeacher()
let clourse = {
t.age += 1
}
clourse()
print(t.age)
}
test()
運行結果發(fā)現(xiàn),閉包對 t
并沒有強引用鼓鲁,deinit
方法會執(zhí)行
- 【修改2】繼續(xù)修改例子為如下,是否有強引用港谊?
class HTTeacher {
var age = 10
var completionBlock: (() ->())?
//反初始化器(當前實例對象被回收時調用)
deinit {
print("HTTeacher deinit")
}
}
func test() {
var t = HTTeacher()
t.completionBlock = {
t.age += 1
}
print(t.age)
}
test()
從運行結果發(fā)現(xiàn)骇吭,沒有執(zhí)行deinit
方法,即沒有打印HTTeacher deinit
歧寺,所以這里有循環(huán)引用
循環(huán)引用解決方法
有兩種方式可以解決swift中的循環(huán)引用
- 【方式一】使用
weak
修飾閉包傳入的參數(shù)燥狰,其中參數(shù)的類型是optional
func test() {
var t = HTTeacher()
t.completionBlock = { [weak t] in
t?.age += 1
}
print(t.age)
}
- 【方式二】使用
無主引用 unowned
修飾閉包參數(shù)棘脐,與weak
的區(qū)別在于unowned
不允許被設置為nil,即總是假定有值的
func test() {
var t = HTTeacher()
t.completionBlock = { [unowned t] in
t.age += 1
}
print(t.age)
}
捕獲列表
-
[weak t] / [unowned t]
在swift中被稱為捕獲列表
- 定義在參數(shù)列表之前
- 【書寫方式】捕獲列表被寫成用逗號括起來的表達式列表龙致,并用方括號括起來
- 如果使用捕獲列表蛀缝,則即使省略參數(shù)名稱、參數(shù)類型和返回類型目代,也必須使用
in
關鍵字 -
[weak t]
就是取t的弱引用對象 類似weakself
請問下面代碼的clourse()調用后屈梁,輸出的結果是什么?
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()
}
test()
// 打印結果 0 1.85
所以從結果中可以得出:對于捕獲列表
中的每個常量
在讶,閉包會利用周圍范圍內具有相同名稱的常量/變量,來初始化捕獲列表中定義的常量霜大。有以下幾點說明:
- 捕獲列表中的常量是
值拷貝
构哺,而不是引用 - 捕獲列表中的常量的相當于復制了變量
age
的值 - 捕獲列表中的常量是只讀的,即不可修改
swift中Runtime探索
- 對于純swift類來說战坤,沒有
動態(tài)特性dynamic
(因為swift
是靜態(tài)語言
)曙强,方法和屬性不加任何修飾符的情況下,已經(jīng)不具備runtime
特性途茫,此時的方法調度碟嘴,依舊是函數(shù)表調度即V_Table調度
- 對于純swift類,方法和屬性添加
@objc
標識的情況下慈省,可以通過runtime API獲取到臀防,但是在OC中是無法進行調度的,原因是因為swift.h
文件中沒有swift類的聲明 - 對于
繼承自NSObject類
來說边败,如果想要動態(tài)的獲取當前屬性+方法袱衷,必須在其聲明前
添加@objc
關鍵字,如果想要使用方法交換
笑窜,還必須在屬性+方法前
添加dynamic
關鍵字致燥,否則當前屬性+方法只是暴露給OC使用,而不具備任何動態(tài)特性
元類型排截、AnyClass嫌蚤、Self
AnyObject
-
AnyObject
:代表任意類的instance、類的類型断傲、僅類遵守的協(xié)議
class HTTeacher: NSObject {
var age: Int = 18
}
var t = HTTeacher()
//此時代表的就是當前HTTeacher的實例對象
var t1: AnyObject = t
//此時代表的是HTTeacher這個類的類型
var t2: AnyObject = HTTeacher.self
//繼承自AnyObject脱吱,表示JSONMap協(xié)議只有類才可以遵守
protocol JSONMap: AnyObject { }
例如如果是結構體遵守協(xié)議,會報錯
需要將struct
修改成class
//繼承自AnyObject认罩,表示JSONMap協(xié)議只有類才可以遵守
protocol JSONMap: AnyObject { }
class HTJSONMap: JSONMap {
}
Any
-
Any
:代表任意類型
箱蝠,包括 function類型 或者Optional類型,可以理解為AnyObject
是Any
的子集
//如果使用AnyObject會報錯,而Any不會
var array: [Any] = [1, "name", "", true]
AnyClass
-
AnyClass
:代表任意實例的類型
,類型是AnyObject.Type
- 查看定義,是
public typealias AnyClass = AnyObject.Type
- 查看定義,是
T.self & T.Type
-
T.self
:- 如果
T
是實例對象
宦搬,返回的就是它本身
- 如果
T
是類
牙瓢,那么返回的是MetaData
- 如果
-
T.Type
:一種類型 -
T.self
是T.Type
類型
//此時的tSelf類型是 HTTeacher.Type
var tSelf = HTTeacher.self
- 查看t1、t2存儲的是什么间校?
class HTTeacher: NSObject {
var age: Int = 18
}
var t = HTTeacher()
//實例對象地址:實例對象.self 返回實例對象本身
var t1 = t.self
//存儲metadata元類型
var t2 = HTTeacher.self
type(of:)
-
type(of:)
:用來獲取一個值的動態(tài)類型
<!--demo1-->
var age = 10 as NSNumber
print(type(of: age))
<!--打印結果-->
__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)
<!--打印結果-->
Int
總結
- 當無弱引用時矾克,
HeapObject
中的refCounts
等于strongCount + unownedCount
- 當有弱引用時,
HeapObject
中的refCounts
等于object + xxx + (strongCount + unownedCount) + weakCount
- 循環(huán)引用用可以通過
weak / unowned
修飾參數(shù)來解決 - swift中閉包的
捕獲列表
是值拷貝
憔足,即深拷貝胁附,是一個只讀的常量 - swift由于是
靜態(tài)語言
,所以屬性四瘫、方法在不加任何修飾符的情況下時是不具備動態(tài)性即Runtime特性
的汉嗽,此時的方法調度是V-Table函數(shù)表
調度 - 如果想要
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