oc runtime之weak
二、weak
声明了弱引用,实际上调用了objc_initWeak函数
/**
* Initialize a fresh weak pointer to some object location.
* It would be used for code like:
*
* (The nil case)
* __weak id weakPtr;
* (The non-nil case)
* NSObject *o = ...;
* __weak id weakPtr = o;
*
* This function IS NOT thread-safe with respect to concurrent
* modifications to the weak variable. (Concurrent weak clear is safe.)
*
* @param location Address of __weak ptr.
* @param newObj Object ptr.
*/
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
我们可以看到它最终调用了storeWeak函数,那我们再看看这个函数
// Update a weak variable.
// If HaveOld is true, the variable has an existing value
// that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be
// assigned into the variable. This value might be nil.
// 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.
template <bool HaveOld, bool HaveNew, bool 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;
// 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;
}
// 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;
}
}
// 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;
}
这里先获得oldTable和newTable,当然如果有的话,然后对它们上锁。下面有两个重试,第一个情况是,如果old value被改了,则解锁重试去取新的sideTable;第二个情况是,为了避免死锁。在弱引用机制和+initialize机制这两个机制下,要保证所有的弱引用对象必须完成了initialize,如果没有完成,会去调用_class_initialize函数来完成,然后设置previouslyInitializedClass条件来标置已完成了,解锁重试。
1、weak_register_no_lock
PRIVATE_EXTERN id weak_register_no_lock(weak_table_t *weak_table, id referent, id *referrer) {
if (referent) {
// ensure that the referenced object is viable
BOOL (*allowsWeakReference)(id, SEL) = (BOOL(*)(id, SEL))
class_getMethodImplementation(object_getClass(referent),
@selector(allowsWeakReference));
if ((IMP)allowsWeakReference != _objc_msgForward) {
if (! (*allowsWeakReference)(referent, @selector(allowsWeakReference))) {
_objc_fatal("cannot form weak reference to instance (%p) of class %s", referent, object_getClassName(referent));
}
}
else {
return NULL;
}
// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer_no_lock(&entry->referrers, referrer);
}
else {
weak_entry_t new_entry;
new_entry.referent = referent;
new_entry.referrers.refs = NULL;
new_entry.referrers.num_refs = 0;
new_entry.referrers.num_allocated = 0;
append_referrer_no_lock(&new_entry.referrers, referrer);
weak_table->num_weak_refs++;
weak_grow_maybe_no_lock(weak_table);
weak_entry_insert_no_lock(weak_table, &new_entry);
}
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent;
}
函数先调用allowsWeakReference来判断是否允许弱引用,下面调用weak_entry_for_referent函数来查询weak_table里是否已有referent的entry,有的话将referrer加入到entry->referrers,没有的话创建new_entry,将其插入weak_table
2、weak_unregister_no_lock
// Unregister an already-registered weak reference.
// This is used when referrer's storage is about to go away, but referent
// isn't dead yet. (Otherwise, zeroing referrer later would be a
// bad memory access.)
// Does nothing if referent/referrer is not a currently active weak reference.
// Does not zero referrer.
// fixme currently requires old referent value to be passed in (lame)
// fixme unregistration should be automatic if referrer is collected
PRIVATE_EXTERN void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer)
{
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer_no_lock(&entry->referrers, referrer);
if (entry->referrers.num_refs == 0) {
weak_entry_remove_no_lock(weak_table, entry);
weak_table->num_weak_refs--;
}
}
// Do not set *referrer = NULL. objc_storeWeak() requires that the
// value not change.
}
// Remove old_referrer from list, if it's present.
// Does not remove duplicates.
// fixme this is slow if old_referrer is not present.
static void remove_referrer_no_lock(weak_referrer_array_t *list, id *old_referrer)
{
size_t index = hash_pointer(old_referrer) % list->num_allocated;
size_t start_index = index, hash_displacement = 0;
while (list->refs[index].referrer != old_referrer) {
index++;
hash_displacement++;
if (index == list->num_allocated)
index = 0;
if (index == start_index || hash_displacement > list->max_hash_displacement) {
malloc_printf("attempted to remove unregistered weak referrer %p\n", old_referrer);
return;
}
}
list->refs[index].referrer = NULL;
list->num_refs--;
}
// Remove entry from the zone's table of weak references, and rehash
// Does not update num_weak_refs.
static void weak_entry_remove_no_lock(weak_table_t *weak_table, weak_entry_t *entry)
{
// remove entry
entry->referent = NULL;
if (entry->referrers.refs) _free_internal(entry->referrers.refs);
entry->referrers.refs = NULL;
entry->referrers.num_refs = 0;
entry->referrers.num_allocated = 0;
// rehash after entry
weak_entry_t *weak_entries = weak_table->weak_entries;
size_t table_size = weak_table->max_weak_refs;
size_t hash_index = entry - weak_entries;
size_t index = hash_index;
if (!weak_entries) return;
do {
index++; if (index == table_size) index = 0;
if (!weak_entries[index].referent) return;
weak_entry_t entry = weak_entries[index];
weak_entries[index].referent = NULL;
weak_entry_insert_no_lock(weak_table, &entry);
} while (index != hash_index);
}
我们可以看到:
首先,通过weak_entry_for_referent从weak_table中查询entry,如果存在,则从entry->referrers中移除此referrer。
然后,如果referrers表为空就调用weak_entry_remove_no_lock函数将此entry从weak_table中移除。在这里可以看到entry->referent = NULL,即把引用者置空。
3、对象释放
通过apple的runtime源码,不难发现NSObject执行dealloc时调用_objc_rootDealloc继而调用object_dispose随后调用objc_destructInstance方法
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARR ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
* Be warned that GC DOES NOT CALL THIS. If you edit this, also edit finalize.
* CoreFoundation and other clients do call this under GC.
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
Class isa_gen = _object_getClass(obj);
class_t *isa = newcls(isa_gen);
// Read all of the flags at once for performance.
bool cxx = hasCxxStructors(isa);
bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
if (!UseGC) objc_clear_deallocating(obj);
}
return obj;
}
这个函数主要干了后面的三件事:
1、object_cxxDestruct来执行对象以及父类的.cxx_destruct(它是clang在编译时候动态插入的)方法,来释放成员变量。
2、_object_remove_assocations释放关联的变量。
3、objc_clear_deallocating清除weak表等。
我们来看objc_clear_deallocating
void
objc_clear_deallocating(id obj)
{
assert(obj);
if (obj->isTaggedPointer()) return;
obj->clearDeallocating();
}
inline void objc_object::clearDeallocating()
{
if (!isa.indexed) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (isa.weakly_referenced || isa.has_sidetable_rc) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
分别看一下这两个clear
1)sidetable_clearDeallocating
void objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
table.refcnts.erase(it);
}
table.unlock();
}
void weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);
}
可见它即是清除此对象关联的weak表,并将表中的引用者置空。
2)clearDeallocating_slow
// Slow path of clearDeallocating()
// for objects with indexed isa
// that were ever weakly referenced
// or whose retain count ever overflowed to the side table.
NEVER_INLINE void objc_object::clearDeallocating_slow()
{
assert(isa.indexed && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
has_sidetable_rc是判断引用计数是否过大,clearDeallocating_slow最终也是调用weak_clear_no_lock
最后贴一张从网上找的图片来结束
上一篇: PHP序列化和反序列化语法差异问题
推荐阅读
-
智能指针之 weak_ptr
-
IOS开发(77)之iOS高级内存管理:比较__unsafe_unretain、__strong、__weak、__autoreleasing
-
IOS开发(87)之Strong与Weak的理解
-
OC学习篇之---循环引用问题
-
iOS开发之OC与swift开发混编教程,代理的相互调用,block的实现。OC调用Swift中的代理, OC调用Swift中的Block 闭包
-
中端显卡也堆料!索泰RTX 3060天启OC评测:GTX 1060终极升级之选
-
关于c++中的Debug以及runtime_error之segment_fault
-
iOS runtime探究(二): 从runtime开始深入理解OC消息转发机制
-
Runtime之方法调用和方法转发
-
OC runtime学习笔记之关联对象