以前的文章有说过 Atomic
原子操做的原理,其做为一个特殊的修饰前缀,影响了存取操做。c++
在属性修饰定义中,还有另外一类修饰前缀,他们分别是 strong
weak
assign
copy
,这些又有什么区别呢?macos
平时喜欢探究的同窗,可能也见过 unsafe_unretained
,这个又是什么呢?数组
让咱们从属性修饰入手,逐步揭开弱引用的面纱。bash
首先咱们先建立一个示例代码文件做为样本。markdown
#import <Foundation/Foundation.h> @interface PropertyObject : NSObject @property (nonatomic, strong) NSObject *pStrongObj; //强引用 @property (nonatomic, copy) NSObject *pCopyObj; //拷贝 @property (nonatomic, weak) NSObject *pWeakObj; //弱引用 @property (nonatomic, assign) NSObject *pAssignObj; //申明 @property (nonatomic, unsafe_unretained) NSObject *pUnretainedObj; //非持有 @end @implementation PropertyObject @end 复制代码
而后经过 clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.14 -fobjc-runtime=macosx-10.14 -Wno-deprecated-declarations main.m
命令将其解释成 c++
代码。(注意这里要指定版本,否则weak属性不能翻译)数据结构
展开的代码比较多,我这里截取关键部分探讨。app
struct PropertyObject_IMPL { NSObject *__strong _pStrongObj; NSObject *__strong _pCopyObj; NSObject *__weak _pWeakObj; NSObject *__unsafe_unretained _pAssignObj; NSObject *__unsafe_unretained _pUnretainedObj; }; {"pStrongObj","T@\"NSObject\",&,N,V_pStrongObj"}, {"pCopyObj","T@\"NSObject\",C,N,V_pCopyObj"}, {"pWeakObj","T@\"NSObject\",W,N,V_pWeakObj"}, {"pAssignObj","T@\"NSObject\",N,V_pAssignObj"}, {"pUnretainedObj","T@\"NSObject\",N,V_pUnretainedObj"} 复制代码
从变量结构体的描述和特性能够看出,strong
和copy
实际都是__strong
修饰,但特性不一样,assign
和unsafe_unretained
则彻底一致,都是__unsafe_unretained
,weak
则单独使用__weak
修饰。ide
下面咱们来看一下方法具体实现。函数
// @implementation PropertyObject //根据偏移取值和赋值 static NSObject * _I_PropertyObject_pStrongObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pStrongObj)); } static void _I_PropertyObject_setPStrongObj_(PropertyObject * self, SEL _cmd, NSObject *pStrongObj) { (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pStrongObj)) = pStrongObj; } static NSObject * _I_PropertyObject_pCopyObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pCopyObj)); } extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool); //只有Copy不一样,setter的实现是objc_setProperty static void _I_PropertyObject_setPCopyObj_(PropertyObject * self, SEL _cmd, NSObject *pCopyObj) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct PropertyObject, _pCopyObj), (id)pCopyObj, 0, 1); } static NSObject * _I_PropertyObject_pWeakObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__weak *)((char *)self + OBJC_IVAR_$_PropertyObject$_pWeakObj)); } static void _I_PropertyObject_setPWeakObj_(PropertyObject * self, SEL _cmd, NSObject *pWeakObj) { (*(NSObject *__weak *)((char *)self + OBJC_IVAR_$_PropertyObject$_pWeakObj)) = pWeakObj; } static NSObject * _I_PropertyObject_pAssignObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pAssignObj)); } static void _I_PropertyObject_setPAssignObj_(PropertyObject * self, SEL _cmd, NSObject *pAssignObj) { (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pAssignObj)) = pAssignObj; } static NSObject * _I_PropertyObject_pUnretainedObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pUnretainedObj)); } static void _I_PropertyObject_setPUnretainedObj_(PropertyObject * self, SEL _cmd, NSObject *pUnretainedObj) { (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pUnretainedObj)) = pUnretainedObj; } // @end 复制代码
在代码中,只有copy
修饰属性的setter
方法使用了objc_setProperty
,其余几种都是根据 self + 偏移量
的方式计算出内存地址直接进行存取。oop
那问题来了,若是真的是那么简单的话,arc
是怎么实现根据不一样修饰从而进行内存管理的呢?
原来经过 clang -rewrite-objc
的代码只是翻译成 c++
语言,在以后的编译过程当中会进一步处理。
接着使用 clang -S -fobjc-arc -emit-llvm main.m -o main.ll
命令生成中间码。
(中间码显示比较杂乱,我根据本身理解整理成简洁版)
//代码整理后 id [PropertyObject pStrongObj] { return *location; } void [PropertyObject setPStrongObj:](self, _cmd, obj) { @llvm.objc.storeStrong(*location, obj) } id [PropertyObject pCopyObj] { return @objc_getProperty(self, _cmd, offset, atomic) } void [PropertyObject setPCopyObj:](self, _cmd, obj) { @objc_setProperty_nonatomic_copy(self, _cmd, obj, offset) } id [PropertyObject pWeakObj] { id obj = @llvm.objc.loadWeakRetained(*location) return @llvm.objc.autoreleaseReturnValue(obj) } void [PropertyObject setPWeakObj:](self, _cmd, obj) { @llvm.objc.storeWeak(*location, obj) } id [PropertyObject pAssignObj] { return *location } void [PropertyObject setPAssignObj:](self, _cmd, obj) { *location = obj } id [PropertyObject pUnretainedObj] { return *location } void [PropertyObject setPUnretainedObj:](self, _cmd, obj) { *location = obj } 复制代码
能够看出分别针对strong
和 weak
都作了处理,而assign
和 unsafe_unretained
则不作内存管理直接返回,这也说明这二者的处理方式是同样的,区别在于 assign
针对。
strong | copy | weak | assign | unsafe_unretained | |
---|---|---|---|---|---|
Ownership | __strong | __strong | __weak | __unsafe_unretained | __unsafe_unretained |
Getter | *location | objc_getProperty | loadWeakRetained | *location | *location |
Setter | storeStrong | objc_setProperty | storeWeak | *location | *location |
对象 | NSObject | NSObject | NSObject | NSObject | Scalar |
本文篇幅有限,暂不介绍 storeStrong
和 objc_setProperty_nonatomic_copy
,主要介绍 weak
相关操做。
打开 objc4-750
开源代码,翻到 NSObject.mm
,咱们来一探究竟。
// 初始化弱引用 id objc_initWeak(id *location, id newObj) { // 不存在则不保存 if (!newObj) { *location = nil; return nil; } return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); } // 销毁弱引用 void objc_destroyWeak(id *location) { (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating> (location, nil); } // 交换原有的值 id objc_storeWeak(id *location, id newObj) { return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object *)newObj); } 复制代码
能够看到 runtime
中调用的都是一个方法,区别在于使用了不一样的模版,那么咱们来看下对一个地址的存取方法。
// 获取操做的具体实现 id objc_loadWeakRetained(id *location) { id obj; id result; Class cls; SideTable *table; retry: // 保证地址有数据且不是伪指针 obj = *location; if (!obj) return nil; if (obj->isTaggedPointer()) return obj; // 根据地址取出对应的表 table = &SideTables()[obj]; // 加锁 table->lock(); // 若是数据被其余线程改变,则重试 if (*location != obj) { table->unlock(); goto retry; } result = obj; cls = obj->ISA(); if (! cls->hasCustomRR()) { // 若是使用的是系统默认的内存管理,则保证了已经初始化 // 因此能够直接rootTryRetain assert(cls->isInitialized()); if (! obj->rootTryRetain()) { result = nil; } } else { // 若是不是默认的,则须要确保在初始化线程上执行自定义retain操做 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; } } //完成后解锁 table->unlock(); return result; } // 保存操做的具体实现 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; // 因为有锁的机制,若是在期间值被改变了,则重试,直到成功 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; } // 检查新对象类有没有初始化完,没有则重试 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)); // 若是正在初始化,则让下一次绕过这个判断继续运行 previouslyInitializedClass = cls; goto retry; } } // 清除以前保存的弱引用数据 if (haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // 保存新的弱引用数据 if (haveNew) { newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating); // 保存成功就记录到对象指针中,这样能够在释放时检查 if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } // 保存到对应位置 *location = (id)newObj; } // 操做成功后解锁 SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // 返回最终数据 return (id)newObj; } 复制代码
除去保护方法,其实 objc_loadWeakRetained
方法就是检查后返回 *location
,也就是变量指向的实际地址。
而 storeWeak
方法则是根据模版,对旧对象执行 weak_unregister_no_lock
,对新对象执行 weak_register_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. // 上面为苹果注释,看这意思应该是objc_storeWeak还须要使用引用地址作后续处理。 } //注册引用 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; //被引用人 objc_object **referrer = (objc_object **)referrer_id; //引用人 // taggedPointer没有引用计数,不须要处理 if (!referent || referent->isTaggedPointer()) return referent_id; // 保证被引用人不在释放中,否则闪退 bool deallocating; 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; } } //获取被引用人的引用数组,没有则建立 weak_entry_t *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; } //释放过程清空引用 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) { //这里应该确定有entry,由于调用前判断了对象的WeaklyReferenced //若是确实没有,苹果认为多是CF/objc缘由 return; } //清空引用数组 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
的变量进行存取操做,其实都是经过被操做的对象地址查找到相应的表,而后增删表的引用数组内容。
关键就在于怎么申明建立表,以及这个表是怎么设计及使用的。
// SideTables 类型申明 // 这里之因此先使用数据的方式申明是由于考虑到加载顺序的问题 alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof(StripedMap<SideTable>)]; // 加载image时执行初始化 static void SideTableInit() { new (SideTableBuf) StripedMap<SideTable>(); } // 数组还原成StripedMap类型 static StripedMap<SideTable>& SideTables() { return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf); } // StripedMap 的结构 enum { CacheLineSize = 64 }; template<typename T> class StripedMap { #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR enum { StripeCount = 8 }; #else enum { StripeCount = 64 }; #endif // 64位对齐 struct PaddedT { T value alignas(CacheLineSize); }; // 手机系统数组个数为8 PaddedT array[StripeCount]; // 把指针地址匹配到数组的序号 static unsigned int indexForPointer(const void *p) { uintptr_t addr = reinterpret_cast<uintptr_t>(p); return ((addr >> 4) ^ (addr >> 9)) % StripeCount; } } 复制代码
在加载镜像的过程当中,经过 SideTableInit
方法建立全局表数组,能够看到手机系统是8个数组。
源码中使用 &SideTables()[obj]
的方式,其实就是把 obj
的指针地址转成序号获取某一个 table
,经过这种方式分散冗余。
接着咱们看 SideTable
类的内部结构。
// 哈希散列表,使用补码的形式把指针地址做为Key,保存引用计数 typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap; // Template parameters. enum HaveOld { DontHaveOld = false, DoHaveOld = true }; enum HaveNew { DontHaveNew = false, DoHaveNew = true }; struct SideTable { spinlock_t slock; // 自旋锁 RefcountMap refcnts; // 引用记数表 weak_table_t weak_table;// 弱引用表 template<HaveOld, HaveNew> static void lockTwo(SideTable *lock1, SideTable *lock2); template<HaveOld, HaveNew> static void unlockTwo(SideTable *lock1, SideTable *lock2); }; struct weak_table_t { weak_entry_t *weak_entries; //弱引用数组 size_t num_entries; //数组个数 uintptr_t mask; //计算辅助量,数值为数组总数-1 uintptr_t max_hash_displacement;//哈希最大偏移量 }; #if __LP64__ #define PTR_MINUS_2 62 #else #define PTR_MINUS_2 30 #endif typedef DisguisedPtr<objc_object *> weak_referrer_t; struct weak_entry_t { // 被引用者 DisguisedPtr<objc_object> referent; union { // 引用者数据结构 struct { // 当数量超过4个时,结构转为指针,每次容量满的时候就扩容两倍 // 须要与数组做区分,因此有out_of_line_ness标记 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 { // 四个数组 weak_referrer_t inline_referrers[4]; }; }; }; 复制代码
SideTable
存储的不只有对象引用计数表,还有咱们关注的弱引用表,其结构顺序以下:
SideTable->weak_table_t->weak_entry_t->weak_referrer_t
为了方便理解,我模拟一下找弱引用对象的步骤:
sideTable = &SideTables()[referent]
把对象内存地址按照8取余后找到表
weakTable = &sideTable->weak_table
取出弱引用表
entry = weak_entry_for_referent(weakTable, referent)
根据被引用人地址,遍历弱引用表找出入口
referrer = entry->referrers[index]
入口有特殊的数组,其中保存了全部弱引用者的对象地址
仔细一点的同窗应该发现了 weak_entry_t
中有一个联合体,这又是怎么操做实现的呢?
// 添加新引用者 static void append_referrer (weak_entry_t *entry, objc_object **new_referrer) { // 没有超过4个,就用内敛数组 if (! entry->out_of_line()) { // 遍历数组,若是有空位置,则插入后返回 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == nil) { entry->inline_referrers[i] = new_referrer; return; } } // 若是超过4个了,就从数组结构转成指针结构 weak_referrer_t *new_referrers = (weak_referrer_t *) calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t)); // 拷贝原数据到指针指向的内容 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { new_referrers[i] = entry->inline_referrers[i]; } entry->referrers = new_referrers; //指针数组 entry->num_refs = WEAK_INLINE_COUNT; //数组元素个数 entry->out_of_line_ness = REFERRERS_OUT_OF_LINE; //是不是指针的标记位 entry->mask = WEAK_INLINE_COUNT-1; //数组最大下标,用于取余 entry->max_hash_displacement = 0; //最大hash移位次数,用于优化循环 // 因为只有4个,会在下个判断后执行grow_refs_and_insert初始化并插入新对象 } // 断言必然是指针结构 assert(entry->out_of_line()); // 若是指针数量超过3/4,就容量翻倍后再插入 if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { return grow_refs_and_insert(entry, new_referrer); } size_t begin = w_hash_pointer(new_referrer) & (entry->mask); size_t index = begin; size_t hash_displacement = 0; //找一个空位置,不够就从头找 while (entry->referrers[index] != nil) { hash_displacement++; index = (index+1) & entry->mask; //下标+1后取余 if (index == begin) bad_weak_table(entry); } if (hash_displacement > entry->max_hash_displacement) { entry->max_hash_displacement = hash_displacement; } //保存 weak_referrer_t &ref = entry->referrers[index]; ref = new_referrer; entry->num_refs++; } 复制代码
至此对于弱引用的总体结构和逻辑都清楚了,对象根据修饰符进行内存管理,若是是弱引用,则找到其引用地址的引用表操做。
反过来说,强对象被引用时在全局引用表中注册一个节点,保存全部引用者的地址,当释放时设置全部地址为空。
被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构能够画出来么?
对象被释放时执行 obj->rootDealloc()
,若是有弱引用标记,则会执行 objc_destructInstance
方法后释放。
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(); //处理isa } 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]; // 删除强引用和弱引用 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(); } 复制代码
能够看到在 sidetable_clearDeallocating
方法中,最后执行了 weak_clear_no_lock
清空了全部引用关系。
SideTable
表结构以下图:
weak原理是绕不开的经典课题,经过阅读开源代码对苹果如何实现有了大体的了解,受益不浅。
阅读过程当中还惊叹于苹果各类花式小技巧,因为文章篇幅有限没来得及介绍,感兴趣能够了解一下,好比 DisguisedPtr
。