關聯文章
(一)并不很深入地探究 arc中 __strong 修飾符
(二)并不很深入地探究 arc中 __weak 修飾符
(三)并不很深入地探究 arc中 __autoreleassing 修飾符
ARC是由編譯器和運行時系統進行內存管理,本文將從編譯器和runtime系統出發,探究 __weak 修飾符的實現過程。
在此之前我們需要知道如何獲取匯編輸出
對于__weak 修飾符:
- 若附有__weak 修飾符的變量所引用的對象被廢棄,則將nil賦值給該變量
- 若使用__weak 修飾符的變量,是否是使用注冊到autoreleasepool中的對象?
接下來針對以上兩點進行整理 (便于理解,下文匯編輸出將整理成偽代碼)
若附有__weak 修飾符的變量所引用的對象被廢棄,則將nil賦值給該變量
id obj = [[NSObject alloc] init];
id __weak obj1 = obj;
匯編輸出偽代碼
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);
通過objc_initWeak
初始化帶有__weak修飾符的變量,作用域結束時通過objc_destroyWeak
釋放。
objc_initWeak
和objc_destroyWeak
源碼:
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj); //使用到了storeWeak
}
void
objc_destroyWeak(id *location)
{
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);//使用到了storeWeak
}
可見創建weak變量和銷毀變量都用到了storeWeak
方法
storeWeak
是段函數模板
/* storeWeak 部分代碼 Part 1 */
// 更新weak變量
// 如果 HaveOld 為 true,變量已經存在值,因此需要進行清理操作,變量值可以為空
// 如果 HaveNew 為 true,將有個新值被分配到變量中,這個值可以為空
// If CrashIfDeallocating is true, the process is halted if newObj is
// deallocating or newObj's class does not support weak references.
// If CrashIfDeallocating is false, nil is stored instead.
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
然后SideTable為何物?
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
}
slock
是一個自旋鎖,用來對SideTable
的操作加鎖,refcnts
從變量類型看,應該是存儲引用計數的散列表, weak_table
則是存放了弱引用。在weak表中,將對象的地址作為鍵值進行檢索,能夠高速獲取對于__weak修飾符變量的地址。
/* storeWeak 部分代碼 Part 2 */
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
這段主要是獲取oldObj , oldTable, newTable
,并給oldTable, newTable
上鎖
/* storeWeak 部分代碼 Part 3 */
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
goto retry;
}
}
這段主要確保newObj
的 +initialize
被正確調用
/* storeWeak 部分代碼 Part 4 */
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
最后一部分代碼:
- 第一步: 如果
haveOld
為真,調用weak_unregister_no_lock
來刪除oldTable
中的weak表中記錄 - 第二步: 如果
haveNew
為真,調用weak_register_no_lock
在newTable
中的weak表刪除對應記錄,然后再調用setWeaklyReferenced_nolock
將引用計數表中做SIDE_TABLE_WEAKLY_REFERENCED
標記 - 最后SideTable釋放鎖,返回
newObj
然后,我們回到之前的objc_initWeak
和objc_destroyWeak
方法,
- objc_initWeak,調用
storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj)
因為DontHaveOld
為 false ,DoHaveNew
為 True, 所以調用了上面第二步 - objc_destroyWeak調用
storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating> (location, nil)
因為DoHaveOld
為 true,DontHaveNew
為false 所以調用了上面第一步
由上述分析, __weak 通過散列表來實現,如果大量使用會消耗相應的資源(廢棄對象時需要weak表中查找刪除對應記錄,在引用計數表中也要做類似操作),所以只在需要避免循環引用的時候使用__weak修飾符
若使用__weak 修飾符的變量,是否是使用注冊到autoreleasepool中的對象?
id obj = [[NSObject alloc] init];
id __weak obj1 = obj;
NSLog(@"%@", obj1);
匯編輸出偽代碼
id obj1;
objc_initWeak(&obj1, obj);
id temp = objc_loadWeakRetained(&obj1);
NSLog(@"%@", temp);
objc_release(temp);
objc_destroyWeak(&obj1);
objc_loadWeakRetained
部分源碼
if (! cls->hasCustomRR()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
assert(cls->isInitialized());
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
// Slow case. We must check for +initialize and call it outside
// the lock if necessary in order to avoid deadlocks.
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
class_getMethodImplementation(cls, SEL_retainWeakReference);
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
result = nil;
}
}
else {
table->unlock();
_class_initialize(cls);
goto retry;
}
}
-
rootTryRetain
和tryRetain
用來進行對變量進行 retain操作, 所以objc_loadWeakRetained
函數去除賦有__weak修飾符變量引用的對象,并且retain
然而令人詫異的是,objc_loadWeakRetained
值 并沒有加入autoreleasepool,那么它是如何保證使用期間時候obj1存在的呢, 令人費解,本人暫時無法給出解答,希望有大牛來給給予指導。