基礎(chǔ)知識
Swift
和Objective-C
都是利用古老且有效的ARC(Automatic Reference Counting)來管理內(nèi)存惦界,當(dāng)實例的引用計數(shù)為0時,實例將會被析構(gòu),實例占有的內(nèi)存和資源都將重新變得可用。
但收擦,當(dāng)兩個實例發(fā)生循環(huán)引用時,那么他們的引用計數(shù)會一直大于0囊卜,那么他們將不會被析構(gòu)。
為了解決這個問題,Swift
和Objective-C
引入了弱引用栅组,弱引用不會被ARC
計算雀瓢。也就是說當(dāng)一個弱引用指向一個引用類型實例時,引用計數(shù)不會增加玉掸。
Swift中的弱引用
這里以閉包為例刃麸,在OC
中,標(biāo)準(zhǔn)的做法是司浪,定義一個弱引用指向閉包外部的實例泊业,然后在閉包內(nèi)部定義強引用指向這個實例,在閉包執(zhí)行期間使用它.
類似于下面代碼:
__weak typeof(self) weakSelf = self;
void (^myBlock)(NSString *) = ^(NSString * name) {
__strong typeof(self) strongSelf = self;
NSString *name = self.name;
};
為了更方便的處理循環(huán)引用啊易,Swift
引入了一個新的概念吁伺,用于簡化和更加明顯的表達在閉包內(nèi)部,外部變量的捕獲:捕獲列表(capture list)认罩。使用捕獲列表,可以在函數(shù)(閉包)的頭部定義和指定那些需要用在內(nèi)部的外部變量续捂,并且指定引用類型(這里是指 unowned
和 weak
)垦垂。
例子:
不使用捕獲列表時,閉包將會創(chuàng)建一個外部變量的強引用牙瓢。
var i1 = 1, i2 = 1
var fStrong = {
i1 += 1
i2 += 2
}
fStrong()
print(i1,i2) //Prints 2 and 3
使用捕獲列表劫拗,閉包內(nèi)部會創(chuàng)建一個新的可用常量。如果沒有指定常量修飾符矾克,閉包將會簡單地拷貝原始值到新的變量中页慷,對于值類型和引用類型都是一樣的。
var fCopy = { [i1] in
print(i1,i2)
}
fStrong()
print(i1,i2) //打印結(jié)果是 2 和 3
fCopy() //打印結(jié)果是 1 和 3
在上面的例子中胁附,在調(diào)用fStrong
之前定義函數(shù) fCopy
,在該函數(shù)定義的時候酒繁,私有常量已經(jīng)被創(chuàng)建了。正如你所看到的控妻,當(dāng)調(diào)用第二個函數(shù)時候州袒,仍然打印 i1
的原始值。
對于外部引用類型的變量弓候,在捕獲列表中指定 weak
或 unowned
郎哭,這個常量將會被初始化為一個弱引用,指向原始值菇存,這種指定的捕獲方式就是用來處理循環(huán)引用的方式夸研。
class aClass{
var value = 1
}
var c1 = aClass()
var c2 = aClass()
var fSpec = { [unowned c1, weak c2] in
c1.value += 1
if let c2 = c2 {
c2.value += 1
}
}
fSpec()
print(c1.value,c2.value) //Prints 2 and 2
兩個 aClass
捕獲實例的不同的定義方式,決定了它們在閉包中不同的使用方式依鸥。
調(diào)用步驟
動作 | unowned | weak |
---|---|---|
預(yù)先調(diào)用 #1 | 對象進行 unowned_retain 操作 | 創(chuàng)建一個容器亥至,并且對象進行 strong_retain 操作。創(chuàng)建一個可選值,存入到容器中抬闯,然后釋放可選值 |
預(yù)先調(diào)用 #2 | strong_retain_unowned井辆,unowned_retain 和 strong_release | strong_retain |
閉包執(zhí)行 | strong_retain_unowned,unowned_release | load_weak, 打開可選值, strong_release |
調(diào)用之后 | unowned_release | strong_release |
- unowned_retain:增加堆對象中的 unowned 引用計數(shù)溶握。
- strong_retain_unowned :斷言對象的強引用計數(shù)大于 0杯缺,然后增加這個引用計數(shù)。
- strong_retain:增加對象的強引用計數(shù)睡榆。
- load_weak:不是真正的 ARC 調(diào)用萍肆,但是它將增加可選值指向?qū)ο蟮膹娨糜嫈?shù)。
- strong_release:減少對象的強引用計數(shù)胀屿。如果釋放操作把對象強引用計數(shù)變?yōu)?塘揣,對象將被銷毀,然后弱引用將被清除宿崭。當(dāng)整個強引用計數(shù)和 unowned 引用計數(shù)都為0時亲铡,對象的內(nèi)存才會被釋放。
- unowned_release:減少對象的 unowned 引用計數(shù)葡兑。當(dāng)整個強引用計數(shù)和 unowned 引用計數(shù)都為 0 時奖蔓,對象的內(nèi)存才會被釋放。
使用場景
- unowned: 引用使用的場景是讹堤,原始實例永遠不會為 nil吆鹤,閉包可以直接使用它,并且直接定義為顯式解包可選值洲守。當(dāng)原始實例被析構(gòu)后疑务,在閉包中使用這個捕獲值將導(dǎo)致崩潰
- 如果捕獲原始實例在使用過程中可能為 nil ,必須將引用聲明為 weak梗醇, 并且在使用之前驗證這個引用的有效性知允。
實現(xiàn)
unomned實現(xiàn)
來源于Swift5.0源碼HeapObject.cpp
文件。
HeapObject *swift::swift_unownedRetain(HeapObject *object) {
SWIFT_RT_TRACK_INVOCATION(object, swift_unownedRetain);
/// 檢測對象是否存在叙谨,不存在直接return
if (!isValidPointerForNativeRetain(object))
return object;
/// 將對象的引用計數(shù)加1
object->refCounts.incrementUnowned(1);
return object;
}
void swift::swift_unownedRelease(HeapObject *object) {
SWIFT_RT_TRACK_INVOCATION(object, swift_unownedRelease);
/// 檢測對象是否存在廊镜,不存在,直接return
if (!isValidPointerForNativeRetain(object))
return;
// Only class objects can be unowned-retained and unowned-released.
/// 檢測是否為類的對象
assert(object->metadata->isClassObject());
assert(static_cast<const ClassMetadata*>(object->metadata)->isTypeMetadata());
/// 檢測Unowned引用計數(shù)是否能減1
if (object->refCounts.decrementUnownedShouldFree(1)) {
auto classMetadata = static_cast<const ClassMetadata*>(object->metadata);
/// 釋放Unowned指針唉俗,并沒有釋放該指針指向的內(nèi)存
swift_slowDealloc(object, classMetadata->getInstanceSize(),
classMetadata->getInstanceAlignMask());
}
}
到此已經(jīng)有了一個對象的 unowned 引用嗤朴,另外一個指令,strong_retain_unowned
用來創(chuàng)建一個強引用:
HeapObject *swift::swift_unownedRetainStrong(HeapObject *object) {
SWIFT_RT_TRACK_INVOCATION(object, swift_unownedRetainStrong);
if (!isValidPointerForNativeRetain(object))
return object;
/// 斷言來驗證對象是否被弱引用虫溜,一旦斷言通過雹姊,將嘗試進行增加強引用計數(shù)的操作.
/// 一旦對象在進程中已經(jīng)被釋放,嘗試將會失敗衡楞。
assert(object->refCounts.getUnownedCount() &&
"object is not currently unowned-retained");
/// 嘗試增加引用計數(shù)
if (! object->refCounts.tryIncrement())
/// 引用計數(shù)添加失敗
swift::swift_abortRetainUnowned(object);
return object;
}
weak
Swift5.0源碼
/// 初始化弱引用
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
///
void nativeInit(HeapObject *object) {
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
WeakReferenceBits(HeapObjectSideTableEntry *newValue) {
setNativeOrNull(newValue);
}
/// 創(chuàng)建一個弱引用表吱雏,成功則增加弱引用計數(shù)敦姻。
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
auto side = allocateSideTable(true);
if (side)
return side->incrementWeak();
else
return nullptr;
}
/// 創(chuàng)建一個對象的散列表,如果該對象釋放了歧杏,則返回空
// Return an object's side table, allocating it if necessary.
// Returns null if the object is deiniting.
// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
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
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
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);
/// 進行 CAS
} while (! refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_release,
std::memory_order_relaxed));
return side;
}
增加引用計數(shù)
// Increment the weak reference count.
void incrementWeak() {
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
RefCountBits newbits;
do {
newbits = oldbits;
assert(newbits.getWeakRefCount() != 0);
newbits.incrementWeakRefCount();
if (newbits.getWeakRefCount() < oldbits.getWeakRefCount())
swift_abortWeakRetainOverflow();
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_relaxed));
}
bool decrementWeakShouldCleanUp() {
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
RefCountBits newbits;
bool performFree;
do {
newbits = oldbits;
performFree = newbits.decrementWeakRefCount();
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_relaxed));
return performFree;
}
SideTable的數(shù)據(jù)結(jié)構(gòu)
class HeapObjectSideTableEntry {
// FIXME: does object need to be atomic?
std::atomic<HeapObject*> object;
SideTableRefCounts refCounts;
public:
HeapObjectSideTableEntry(HeapObject *newObject)
: object(newObject), refCounts()
{ }
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winvalid-offsetof"
static ptrdiff_t refCountsOffset() {
return offsetof(HeapObjectSideTableEntry, refCounts);
}
弱引用的訪問:
HeapObject *nativeLoadStrong() {
auto bits = nativeValue.load(std::memory_order_relaxed);
return nativeLoadStrongFromBits(bits);
}
HeapObject *nativeLoadStrongFromBits(WeakReferenceBits bits) {
auto side = bits.getNativeOrNull();
return side ? side->tryRetain() : nullptr;
}
到這里大家發(fā)現(xiàn)一個問題沒有镰惦,被引用對象釋放了為什么還能直接訪問 Side Table?其實 Swift ABI 中 Side Table 的生命周期與對象是分離的犬绒,當(dāng)強引用計數(shù)為 0 時旺入,只有 HeapObject 被釋放了。
只有所有的 weak 引用者都被釋放了或相關(guān)變量被置 nil 后凯力,Side Table 才能得以釋放茵瘾,詳見:
void decrementWeak() {
// FIXME: assertions
// FIXME: optimize barriers
bool cleanup = refCounts.decrementWeakShouldCleanUp();
if (!cleanup)
return;
// Weak ref count is now zero. Delete the side table entry.
// FREED -> DEAD
assert(refCounts.getUnownedCount() == 0);
delete this;
}
void decrementWeakNonAtomic() {
// FIXME: assertions
// FIXME: optimize barriers
bool cleanup = refCounts.decrementWeakShouldCleanUpNonAtomic();
if (!cleanup)
return;
// Weak ref count is now zero. Delete the side table entry.
// FREED -> DEAD
assert(refCounts.getUnownedCount() == 0);
delete this;
}
所以即便使用了弱引用,也不能保證相關(guān)內(nèi)存全部被釋放咐鹤,因為只要 weak 變量不被顯式置 nil拗秘,Side Table 就會存在。而 ABI 中也有可以提升的地方祈惶,那就是如果訪問弱引用變量時發(fā)現(xiàn)被引用對象已經(jīng)釋放雕旨,就將自己的弱引用銷毀掉,避免之后重復(fù)無意義的 CAS 操作捧请。當(dāng)然 ABI 不做這個優(yōu)化凡涩,我們也可以在 Swift 代碼里做。
以上就是Swift weak 弱引用機制實現(xiàn)方式的一個簡單的分析血久,可見思路與 Objective-C runtime 還是很類似的突照,都采用與對象匹配的 Side Table 來維護引用計數(shù)帮非。不同的地方就是 Objective-C 對象在內(nèi)存布局中沒有 Side Table 指針氧吐,而是通過一個全局的 StripedMap 來維護對象和 Side Table 之間的關(guān)系,效率沒有 Swift 這么高末盔。另外 Objective-C runtime 在對象釋放時會將所有的 __weak 變量都 zero-out筑舅,而 Swift 并沒有
小結(jié)
在這個實現(xiàn)中,獲取一個強引用需要更多復(fù)雜同步操作陨舱,在多線程競爭嚴(yán)重的情況下翠拣,會帶來性能損耗
結(jié)論
- 保守的使用 weak 引用是否明智呢?答案是否定的游盲,無論是從性能的角度還是代碼清晰的角度而言误墓。
- 使用正確的捕獲修飾符類型,明確的表明代碼中的生命周期特性益缎,當(dāng)其他人或者你自己在讀你的代碼時不容易誤解