关联文章
(一)并不很深入地探究 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存在的呢, 令人费解,本人暂时无法给出解答,希望有大牛来给给予指导。
网友评论
id temp = objc_loadWeakRetained(&obj1);
objc_autorelease(temp);
NSLog(@"%@", temp);//先autorelease然后在使用。
现在的版本,也是文章上面的:
id temp = objc_loadWeakRetained(&obj1);
NSLog(@"%@", temp);
objc_release(temp);// 先使用temp,使用完了,直接release
我觉得现在版本这个方式比老版本好啊,苹果爹爹内核系统部的大神们,就是在研究这些,怎么改实现怎么改能效率更好。
很多东西经常改的,有些内部原理会被不断修改,也会经常改变我们的认知,苹果爹爹