对于学习来说,最大的成本不是金钱,而是时间。低质量低效率的学习不仅是对金钱的浪费,更是对时间、生命的浪费。
先来看一系列大厂必问的iOS的高阶面试题:
- 什么是ARC&MRC?底层是如何实现的?
- 对象调用alloc和init方法之后,引用计数是0还是1?为什么?
- weak实现原理以及weak指针是怎样移除的?何时移除?
然后带着这三个问题开始本篇干货:
- iOS内存布局及优化技巧
- 内存管理机制ARC&MRC
- 内存管理之引用计数retain/release底层实现
- 内存管理之dealloc底层实现
- 内存管理之weak底层实现
一、内存布局及优化
三图看懂内存布局及优化
1.内存布局及存储类型
2.内存布局方向的优化技巧
1.3内存优化.png二、内存管理机制ARC&MRC
1、内存管理机制:
引用计数机制,创建时引用计数为1,被持有会对引用计数+1,对象不再使用或者手动release会对引用计数-1,当引用计数为0的时候由系统进行销毁。(注意释放的条件,不要和release混淆,release只是引用计数-1,而不是释放)
引用计数管理机制:谁创建,谁释放;谁引用,谁管理。
2、MRC和ARC的异同:
MRC(全称Manual Reference Counting 手动引用计数)和ARC(全称Automatic Reference Counting,自动引用计数,iOS5推出)底层都是引用计数机制。
ARC是编译属性,是编译器和runtime结合(对象的持有和释放)实现的结果。
三、内存管理之引用计数retain/release底层实现
打开Objc源码,在objc-object.h
中找到这俩的实现
retain的实现:
// Equivalent to calling [this retain], with shortcuts if there is no override
inline id
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return sidetable_retain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
release的实现部分:
// Equivalent to calling [this release], with shortcuts if there is no override
inline void
objc_object::release()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
sidetable_release();
return;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}
明显看出底层分别调用了sidetable_retain()
和sidetable_release()
。
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
//根据对象的地址,从一大堆散列表中,获取当前对象引用计数的散列表
SideTable& table = SideTables()[this];
//自旋锁,加锁
table.lock();
//获取引用计数
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
//引用计数增加
refcntStorage += SIDE_TABLE_RC_ONE;
}
//自旋锁,解锁
table.unlock();
return (id)this;
}
// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
//根据对象的地址,从大散列表中,获取当前对象引用计数的散列表
SideTable& table = SideTables()[this];
//定义局部变量,是否需要dealloc
bool do_dealloc = false;
//自旋锁加锁
table.lock();
//获取当前对象的引用计数
RefcountMap::iterator it = table.refcnts.find(this);
//判断是否需要dealloc
if (it == table.refcnts.end()) {
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
//如果不需要dealloc,则引用计数减1
it->second -= SIDE_TABLE_RC_ONE;
}
//自旋锁解锁
table.unlock();
//如果需要dealloc则发送消息,调用SEL_dealloc
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
存储SideTable
的全局哈希映射表StripedMap
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
散列表SideTable
的结构:
struct SideTable {
spinlock_t slock;//内核自旋锁spinlock_t
RefcountMap refcnts;//引用计数字典map
weak_table_t weak_table;//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(); }
//提供给weak操作的地址顺序锁
// 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);
};
思考:为什么Runtime会映射维护多张散列表SideTable?而不是维护一张散列表?
回答:由于散列表中的操作会加锁(比如引用计数增加、减少都会加自旋锁),如果只有一个表的话,假如我有两个类Person,Student,那么我对Person对象进行操作的时候,再想对Student对象进行操作,就只能等待Person的操作完成解锁后才能操作,这样效率会大大下降。并且多次对同一张表进行操作,提高了使用频率,可能会造成稍微有修改就需要动整张表的不够合理的操作。
而使用了多张表,就避免了以上的几种问题,哪个类需要处理就去改对应的表,而且避开了锁的问题,效率上也提升了很高,用空间换时间。
分析源码总结retain和release的实现逻辑如下:
Runtime
维护了一个全局哈希映射表StripedMap
,根据对象可以在全局映射表中可以获取该对象对应的散列表SideTable
,该SideTable
拥有三个成员变量,一个自旋锁spinlock_t
,一个引用计数表RefcountMap
,以及一个weak表weak_table_t
。引用计数表RefcountMap
以对象的地址作为key,以引用计数作为value。
retain
是objc_object
在底层调用了sidetable_retain()
,查找引用计数表,做了引用计数refcntStorage+=SIDE_TABLE_RC_ONE;
的操作;
release
是objc_object
在底层调用了sidetable_release()
,查找引用计数表,做了引用计数refcntStorage-=SIDE_TABLE_RC_ONE;
的操作;如果引用计数小于阀值SIDE_TABLE_DEALLOCATING
,就调用SEL_dealloc
图文总结如下:
retain&release底层实现.png由于SideTable
结构体中包含一个自旋锁,笔者此处拓展一下自旋锁相关知识:互斥锁的作用,以及和自旋锁的区别
自旋锁是互斥锁的一种实现,互斥锁的作用就是确保同一时间只有一个线程访问数据,对资源加锁后,会等待资源解锁,在这期间会阻塞线程,直到解锁;而自旋锁则是
忙等
,在加锁后,会不断地去询问判断是否解锁。两者的区别主要是:在等待期间互斥锁会放弃CPU,而自旋锁会不断的循环测试锁的状态,会一直占用CPU。
总结之后,来看一个retainCount经典的面试题:
对象进行alloc和init之后的引用计数值为多少?到底是0还是1?为什么?
带着问题,我们源码中全局搜索retainCount {
找到实现,并进入rootRetainCount
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
rootRetainCount
源码,注意代码逻辑
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
//散列表SideTable提供的自旋锁,加锁
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
//判断bits.nonpointer
if (bits.nonpointer) {
//这里先直接+1
uintptr_t rc = 1 + bits.extra_rc;
//判断有没有使用sideTable存储引用计数,如果有,就加上
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
//散列表自旋锁解锁
sidetable_unlock();
//没有就直接返回,
return rc;
}
//散列表SideTable提供的自旋锁,解锁
sidetable_unlock();
//直接返回sidetable_retainCount
return sidetable_retainCount();
}
下面是sidetable_retainCount的实现
uintptr_t
objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this];
//设置初始值1
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
// this is valid for SIDE_TABLE_RC_PINNED too
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
打上断点,对象创建走的是bits.nonpointer
为1的逻辑,此时bits.extra_rc
存储的引用计数为0,但是引用计数rc = 1 + bits.extra_rc
,所以返回了1
从这个源码中我们又看出一件事,引用计数不一定存储在哈希表中,还有可能存储在类isa的
bits.extra_rc
中,找到isa的结构isa_t
以及isa_t
的结构中找到宏定义ISA_BITFIELD
笔者把宏定义整理了一下,注意其中的
has_sidetable_rc
和extra_rc
:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
uintptr_t nonpointer : 1; // 0:普通指针,1:优化过,使用位域存储更多信息
uintptr_t has_assoc : 1; // 对象是否含有或曾经含有关联引用
uintptr_t has_cxx_dtor : 1; // 表示是否有C++析构函数或OC的dealloc
uintptr_t shiftcls : 33; // 存放着 Class、Meta-Class 对象的内存地址信息
uintptr_t magic : 6; // 用于在调试时分辨对象是否未完成初始化
uintptr_t weakly_referenced : 1; // 是否被弱引用指向
uintptr_t deallocating : 1; // 对象是否正在释放
uintptr_t has_sidetable_rc : 1; // 是否需要使用 sidetable 来存储引用计数
uintptr_t extra_rc : 19; // 引用计数能够用 19 个二进制位存储时,直接存储在这里
};
#endif
};
经过分析,isa_t
里面的extra_rc
也是用来存储引用计数的,只不过大小有限,如果超过了大小,就会存储到散列表SideTable中。
总结刚才的答案如下:
问题:对象进行alloc和init之后的引用计数值为多少?到底是0还是1?为什么?
回答:对象alloc之后,在引用计数表中的引用计数其实为0,只是在获取retainCount的方法
rootRetainCount
的内部进行了+1的操作
总结对象生命周期引用计数的变化图如下:
retainCount流程.png四、内存管理之dealloc底层实现
直接进入源码搜索dealloc {
,找到底层函数调用流程如下:
-
dealloc
底层调用_objc_rootDealloc()
-
_objc_rootDealloc()
调用objc_object::rootDealloc()
-
objc_object::rootDealloc()
调用object_dispose()
-
object_dispose()
进行了free(obj)
释放对象,同时调用objc_destructInstance()
- 在
objc_destructInstance()
函数中判断是否有析构函数和关联引用,如果有,就要移除,最后调用clearDeallocating()
- 在
clearDeallocating()
中进行引用计数refcnt的清除和weak指针的移除,并调用weak_clear_no_lock()
(这个weak指针移除具体步骤在下面的weak指针清除的时候进行详细分析。)
下面贴出上述步骤的所有源码:
1、dealloc
底层调用_objc_rootDealloc()
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
2、_objc_rootDealloc()
调用objc_object::rootDealloc()
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
3、objc_object::rootDealloc()
调用object_dispose()
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
4、object_dispose()
进行了对象的释放free(obj)
,同时调用objc_destructInstance()
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
//释放obj
free(obj);
return nil;
}
5、在objc_destructInstance()
函数中判断是否有析构函数和关联引用,如果有,就要移除,最后调用clearDeallocating()
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(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());
}
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();
}
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (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();
}
总结一下dealloc主要做了下面这些内容:
- 首先判断并调用对象C++的析构函数,释放对象占用的空间,顺序是:先子类->后父类 -----
object_cxxDestruct()
- 然后清除对象关联引用 -----
_object_remove_assocations()
- 然后从weak表中清空并移除对象对应的所有weak指针 -----
weak_clear_no_lock()
- 然后移除引用计数 -----
table.refcnts.erase(it);
- 最后释放对象 free(obj)
对应流程图总结如下:
dealloc流程图.png五、内存管理之weak底层实现
分析完retain/release
以及dealloc
的底层实现,我们来继续分析weak
的底层实现及weak表的插入和移除操作:
当我们开发时用__weak
修饰变量,其实Runtime会在底层调用objc_initWeak
函数,objc_initWeak()
底层又调用了storeWeak()
。storeWeak()
的作用就是向表中注册弱引用指针,或者更新表,下面是storeWeak()
的实现部分,笔者把注释写在里面:
/**
* 注意注释部分!!!注释已经说的很直白了
* 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<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
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;
}
然后我们来看一下SideTable
中weak表weak_table_t
的源码:
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
//保存了所有指向指定对象的 weak 指针
weak_entry_t *weak_entries;
//存储空间
size_t num_entries;
//引用计数辅助量
uintptr_t mask;
//最大偏移值
uintptr_t max_hash_displacement;
};
从之前引用计数的分析以及weak表的源码中分析得出:Runtime维护了一个全局哈希映射表StripedMap
,不同对象映射着对应的散列表SideTable
,散列表中包含引用计数map以及weak弱引用表weak_table_t
,weak_table_t
中保存了所有指定对象的weak指针,用对象的地址作为key,weak_entry_t
结构体对象作为value;weak_entry_t
负责维护和存储指向一个对象的所有弱引用的散列表。
weak_entry_t
源码:
/**
* The internal structure stored in the weak references table.
* It maintains and stores
* a hash set of weak references pointing to an object.
* If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set
* is instead a small inline array.
*/
typedef DisguisedPtr<objc_object *> weak_referrer_t;
struct weak_entry_t {
DisguisedPtr<objc_object> 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表的存储结构思维导图总结如下:
weak底层存储结构.png针对weak_table_t
还提供了三个主要的方法:向表中添加元素的方法weak_register_no_lock
、从表中移除元素的weak_unregister_no_lock
、以及清空weak指针weak_clear_no_lock
向添加weak_table_t
中添加方法weak_register_no_lock
的源码:
/**
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
*
* @param weak_table The global weak table.
* @param referent The object pointed to by the weak reference.
* @param referrer The weak pointer address.
*/
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
//对象的指针
objc_object *referent = (objc_object *)referent_id;
//weak指针地址
objc_object **referrer = (objc_object **)referrer_id;
......
// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//添加到表中
append_referrer(entry, referrer);
}
else {
//创建新的weak_entry_t实体
//根据对象及弱指针生成一个weak_entry_t结构体对象,并插入表中
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
//插入到表中
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
移除方法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))) {
//移除表中的weak指针
remove_referrer(entry, referrer);
......
if (empty) {
//移除weak_table表中对应的的entry
weak_entry_remove(weak_table, entry);
}
}
}
清空对象的weak指针,调用时机就是对象dealloc的时候:
/**
* 调用时机:
* 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)
{
objc_object *referent = (objc_object *)referent_id;
//获取weak_entry_t
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;
}
//获取weak_entry_t中的weak_referent_t,遍历weak_referrer_t,将其中的weak指针置为nil
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);
}
总结一下:
1、weak
底层实现的流程
Runtime全局维护了一个全局映射表StripedMap,根据对象的地址能够获取对应的散列表SideTable(注意!!!也有可能是多个对象共用一个散列表),散列表SideTable之中包含有weak表
weak_table_t
,weak_table_t
中根据对象的地址能够查到该对象对应的weak_entry_t
实体,weak_entry_t
用来管理对象的所有的weak指针,weak指针存储在weak_referrer_t
中。当我们在用
__weak
修饰对象的时候,运行时Runtime会在底层调用objc_initWeak()
方法
objc_initWeak()
方法会调用storeWeak()
;
storeWeak()
这个函数会先判断对象是否初始化,如果未初始化,则进行对象初始化,然后创建对应的SideTable;如果对象已经有SideTable,那么判断weak指针是否需要更新,更新操作就是删除对应location位置的weak_entry_t对象,创建新的weak_entry_t,然后插入到weak表weak_table_t中。
2、weak指针移除原理
1、移除时机:调用对象的dealloc方法时,中间会调用
clearDeallocating
,其中会调用weak_clear_no_lock
对weak指针进行移除。2、移除原理:
weak_clear_no_lock
底层会获取weak表weak_table_t
中的实体weak_entry_t
,然后拿到其中的weak_referrer_t
,拿到weak_referrer_t
之后,遍历并将其中的所有weak指针置为nil,最后把这个weak_entry_t
从weak_table_t
中移除。3、weak指针本质:从源码中可以看出weak指针的类型为是
objc_object**
,是对象的二维指针,就是指向对象地址的指针。
接下来看一个问题:
如果在dealloc中使用__weak会有什么样的结果?
答案:会crash!
回到源码,在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.
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
以及注册weak指针的方法
/**
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
*
* @param weak_table The global weak table.
* @param referent The object pointed to by the weak reference.
* @param referrer The weak pointer address.
*/
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
......
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;
}
}
......
return referent_id;
}
结合第一部分注释部分以及第二部分的注册函数weak_register_no_lock
进行分析:如果一个对象正在进行dealloc的时候,进行weak指针的更新操作,就会直接crash!并报错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.
注册时这么判断作用就是:在对象正在释放的过程中,或者对象已经释放后,是不允许使用weak来引用实例变量的。这样就是为了防止野指针的出现。
-END-
网友评论