iOS 中使用引用计数来判断一个对象是否该释放.
void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
{
if (!obj || !ivar || obj->isTaggedPointer()) return;
ptrdiff_t offset;
objc_ivar_memory_management_t memoryManagement;
_class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
if (memoryManagement == objc_ivar_memoryUnknown) {
if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
else memoryManagement = objc_ivar_memoryUnretained;
}
id *location = (id *)((char *)obj + offset);
switch (memoryManagement) {
case objc_ivar_memoryWeak: objc_storeWeak(location, value); break;
case objc_ivar_memoryStrong: objc_storeStrong(location, value); break;
case objc_ivar_memoryUnretained: *location = value; break;
case objc_ivar_memoryUnknown: _objc_fatal("impossible");
}
}
Strong
对象是通过objc_storeStrong
来处理的, 如下代码,
引用计数具体的代码可以下载源码查看, 这里直接说objc_retain(obj);
引用计数会加一.
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
weak
对象是通过 objc_storeWeak
处理的, 如下代码, 具体 weak 处理后面讲, 先说一下 weak 对象最后返回的是 newObj
, 就是你要置为 weak
的对象, *location = (id)newObj;
, 发现*location
指向的也是 newObj
, 如下一小段代码, weakP
和 obj
指向的内存地址是相同的.
但这里没有调用objc_retain
进行引用计数加一.
objc_storeWeak(&weakP, obj);
目前看来和 NSObject *a = obj
的区别就是 weak
修饰后没有引用计数加一.
简单解释一次 objc_storeWeak
参数, location
是新对象的地址, newObj
是想要 weak
的对象.
__weak NSObject *weakP;
objc_storeWeak(&weakP, obj);
static id
storeWeak(id *location, objc_object *newObj)
{
/* 这里把 location 加入到了weak 表中 */
// 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;
}
return (id)newObj;
}
想一个问题, weak
修饰的对象并没有造成引用计数加一, 当所有没有用weak
修饰的对象都置为 nil
的时候, 指向的内存会释放掉, weak
修饰的对象还没有被置为 nil
, ok 造成野指针.
接下来说, 是如何在对象在释放的时候weak
修饰的对象也被置为 nil
.
代码层面, 调用的也是 storeWeak
, 只是赋值为 nil. 如下:
void
objc_destroyWeak(id *location)
{
(void)storeWeak
(location, nil);
}
当调用 storeWeak
的时候, 会调用weak_unregister_no_lock
解绑之前的绑定的对象, 然后调用weak_register_no_lock
, 绑定新的对象,
这里说的绑定是指, 把 location
加入到 weak_table
中, weak_grow_maybe(weak_table)
扩容, 具体可以看源码.
// 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;
}
weak_entry_t
就是一个对象的 weak
信息.
DisguisedPtr
就是对象的指针.
referrers 和 inline_referrers
就是 referent
对应的所有 location
的指针(location 是 weak 指针的地址)
每次查找对象, 就首先通过 referent
, 找到对应的 entry
对象, 然后再通过 location
, 在 inline_referrers
中新增或者删除 weak
对象.
struct weak_entry_t {
DisguisedPtr referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
weak
对象什么时候释放?
当一个对象 dealloc
的时候会调用 weak_clear_no_lock
, 通过对象referent_id
找到 entry, 并释放所有 weak
对象 *referrer = nil;
, 通过 weak_entry_remove
删除 weak_table
中对应的 entry.
/**
* Called by dealloc; nils out all weak pointers that point to the
* provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
关于 NSHashTable
weakObjectsHashTable, 可以弱引用存放 weak
对象, 很棒!
__weak NSObject *weakObj;
NSHashTable *table = [NSHashTable weakObjectsHashTable];
{
NSObject *obj = [NSObject new];
weakObj = obj;
[table addObject:obj];
NSLog(@"all: %@", obj);
}
NSLog(@"all: %@", weakObj);