Runtime学习之weak原理
程序员文章站
2022-06-02 12:46:27
...
文章目录
两行代码
Person *person = [[Person alloc] init];
id __weak a = person;
查看汇编得知调用了_objc_initWeak和_objc_destroyWeak函数
查看源码
_objc_initWeak
//location :__weak指针的地址,存储指针的地址,这样便可以在最后将其指向的对象置为nil。
//newObj :所引用的对象。即例子中的obj
id
objc_initWeak(id *location, id newObj)
{
// 如果所引用的对象不存在,将weak的地址置为nil
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
storeWeak
再看返回的storeWeak函数源码
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
// 以前初始化的类置为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:
//如果之前引用过对象,就取出来赋值给oldObj
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
//如果没有引用过对象,oldtable赋值为nil
oldTable = nil;
}
//如果__weak指针需要指向新对象,就取出来放进newObj中
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
//如果没有需要引用新对象,newTable赋值为nil
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.
// +initialize方法,确保弱引用对象具有未初始化的isa
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
// 如果cls还没有初始化,先初始化,再尝试设置weak
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// If this class is finished with +initialize then we're good.
// 如果这个类用+initialize结束,那么我们很好
// 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.
// 如果这个类仍然在这个线程上运行+initialize(即在它自己的一个实例上,+initialize调用storeWeak),那么我们可以继续,但是它看起来正在初始化,而且还没有初始化到检查上面。相反将previouslyInitializedClass设置为在重试时识别它。
previouslyInitializedClass = cls;
goto retry;
}
}
// Clean up old value, if any.
if (haveOld) {
// 如果weak指针之前指向了一个弱引用,则会调用weak_unregister_no_lock方法将旧的weak指针地址移除
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
newObj = (objc_object *)
// 如果weak指针需要指向一个新的引用,则会调用weak_register_no_lock方法将新的weak指针地址添加到弱引用表中
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// 将__weak指针记录到newobject对应的weak_table对应的weak_entry_t中
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
// 更新newObj的isa的weakly_referenced bit标志位
if (newObj && !newObj->isTaggedPointer()) {
// 调用setWeaklyReferenced_nolock方法修改weak新引用的对象的bit标志位
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
// 引用计数不用+1
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
// 解锁
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
weak_register_no_lock
我们看看将新的weak指针地址添加到弱引用中是如何实现的,即weak_register_no_lock方法
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
// referent_id是新的被弱引用对象
objc_object *referent = (objc_object *)referent_id;
// referrer_id是__weak指针的地址
objc_object **referrer = (objc_object **)referrer_id;
// 如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
bool deallocating;
// 确保被引用的对象可用(没有在析构,同时应该支持weak引用)
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
// 正在析构的对象,不能够被弱引用
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
// now remember it and where it is being stored
weak_entry_t *entry;
// 在 weak_table中找到referent对应的weak_entry,并将referrer加入到weak_entry中
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else { // 如果找不到,就新建一个
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
weak_unregister_no_lock
我们再看看怎么将旧的weak指针地址移除,即weak_unregister_no_lock方法,这里和上面的方法差不多,就不再注释了
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if (!referent) return;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
SideTable
我们看看贯穿全文的SideTable的结构
struct SideTable {
spinlock_t slock; // 自旋锁,用于上锁/解锁 SideTable。
RefcountMap refcnts; // 用来存储OC对象的引用计数的 hash表(仅在未开启isa优化或在isa优化情况下isa_t的引用计数溢出时才会用到)
weak_table_t weak_table; // 存储对象弱引用指针的hash表。是OC中weak功能实现的核心数据结构
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() { // 析构函数,用于处理善后
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
weak_table_t
查看对象弱引用指针是怎么存储的,即查看weak_table_t结构
struct weak_table_t {
weak_entry_t *weak_entries; // hash数组,用来存储弱引用对象的相关信息weak_entry_t
// 这里不是指weak_entry_t是哈希表,是一个value是weak_entry_t的哈希数组weak_entries
size_t num_entries; // 表中元素个数
uintptr_t mask;
uintptr_t max_hash_displacement; // 哈希表中可能发生冲突的最大次数
};
得知,每个weak_table_t里都有一个weak_entries来存储弱引用对象的相关信息
weak_entry_t
那我们再看看weak_entry_t
struct weak_entry_t {
DisguisedPtr<objc_object> referent; //被引用的弱对象
// 引用该对象的对象列表,联合。 若个数大于4,用动态数组weak_referrer_t *referrers
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 {
// 引用个数小于4,用inline_referrers数组
// 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;
}
}
};
根据引用个数来判断存储的数组,如果小于4就用定长数组,如果大于4就用动态数组
参考文献
上一篇: php实现的Captcha验证码类实例
下一篇: PHP中常用的输出函数总结