【iOS】weak实现细节学习
1. weak & Strong的基本用法和异同比较
weak 和 strong 是ARC才会用到的两个关键词
以weak为例,一般用到的地方有两处:
-
用于修饰属性常见于协议对象,此时weak被称为属性修饰符
@property (nonatomic, weak) UIView *someView;
-
用作变量的修饰符,常用于打破循环引用,避免内存泄漏,此时, ___weak称为“所有权修饰符”,
__weak PINCache *weakSelf = self;
二者的区别和联系:首先应该明白,属性是什么? 一个属性一般包含3个东西:实例变量、getter 、setter。 当声明一个属性时,编译器在为我们生成一个实例变量时,会使用与其相对应的所有权修饰符来修饰该实例变量,例如被weak修饰的属性,它对应实例变量的所有权修饰符为__weak。
weak基本特征:
- 对应
__weak
修饰符 - 引用计数不加1
- 指向的对象销毁,指针会自动置为nil
- 定义了一种“非拥有关系” (nonowning relationship),为weak属性设置新值时,设置方法既不保留新值,也不释放旧值
assign基本特征
-
对应
__unsafe_unretained
所属权修饰符 -
不会让引用计数器+1
-
如果指向对象被销毁,指针不会清空,仍旧指向对象销毁后所处的内存区域,导致程序崩溃,野指针
-
定义了一种“非拥有关系” (nonowning relationship),设置新值时,设置方法既不保留新值,也不释放旧值
关于关于第4点,可以结合在MRC环境下setter方法的实现来理解
// assign -(void)setName:(NSString *)name{ _name = name; } // retain -(void)setName:(NSString *)name{ if (_name != name) { [_name release]; _name = [name retain]; } } // copy -(void)setName:(NSString *)name{ if (_name != name) { [_name release]; _name = [name copy]; } }
strong基本特征:
- 对应__strong修饰符
- 引用计数加1
- 指向的对象销毁,指针会自动置为nil
有人可能没有思考过strong指向的对象销毁后,strong类型指针会不会变为nil,其实这个仔细想一下就明白,平常我们调试的时候,判断一个对象存不存在不就是通过看它的指针是不是nil吗?
2. weak底层实现原理
2.1 weak表的数据结构
-
runtime中维护了一张hash表。key是对象的值(结构体地址) value是指向结构体指针的一组指针变量
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表 给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
从上可知,hash表的基本要素:
-
散列表(存放记录的数组)
-
散列函数(从key到表中位置的映射关系)
-
散列函数的通常要求是防止碰撞,(碰撞的意思是不同的key值通过映射关系后得到的表的位置相同)
-
可以看得出其实散列函数与MD5加密算法是同一类算法(md5可以将长度不等的字符串编码为长度相等且唯一的字符串)我想这也是md5被称为哈希算法的原因。
以上是本人自己的思考,如果要求证请查阅相关资料
-
-
weak表结构如下:
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries; // 用于存储
size_t num_entries; // weak对象的存储空间
uintptr_t mask; //参与判断引用计数辅助量
uintptr_t max_hash_displacement; //hash key 最大偏移值
};
uintptr_t(即 unsigned long) size_t(即 unsigned )
typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line : 1;
uintptr_t num_refs : PTR_MINUS_1;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line=0 is LSB of one of these (don't care which)
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
}
}
DisguisedPtr<T> acts like pointer type T*
根据注释可知,可以将 DisguisedPtr<T>
当成 T*
指针类型即可,指向objc_object的指针,referent
记录了weak对象的地址
weak_referrer_t
是objc_object
的二阶指针,我推测应该是用于指向referent这个指针变量。
共用体表示几个变量共用一个内存位置,在不同的时间保存不同的数据类型和不同长度的变量。在union中,所有的共用体成员共用一个空间,并且同一时间只能储存其中一个成员变量的值
union
内部有两个结构体,但是两个结构体只会存储一个。weak_referrer_t *referrers
和weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]
都可以作为数组来存储weak_referrer_t
类型的数据(也就是指向referent变量的指针变量)
接着看它的注释:
// out_of_line=0 is LSB of one of these (don't care which)
我推测应该是通过out_of_line
这样一个标志位来判断,要么使用第一个结构存储,要么使用第二个结构存储。WEAK_INLINE_COUNT
是一个常量,所以inline_referrers
可以存储的数据是有限的。我认为它的逻辑应该是,当需要存储的数量大于WEAK_INLINE_COUNT
时,使用第一个结构体存储,否则使用第二个结构体存储。
写到这里之后引发了我对于对象、类、结构体、指针的混乱,我觉得自己的脑子已经乱了,然后我又把这些东西整理了一下,没兴趣的看,可以略过。。。
首先第一个 什么是对象?什么是类?
首先摆出几个结构体的定义:
struct objc_object { Class isa OBJC_ISA_AVAILABILITY; };
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;
/// An opaque type that represents an Objective-C class. typedef struct objc_class *Class;
那我们平常用的例如:
NSString *string = @"Hello world";
前面为什么要用用一个*号呢?
回忆一下C语言类型中类似的场景
typedef struct node { char *name; }Node; Node *node = (Node *)malloc(sizeof(Node));
objc是运行时语言,所以NSString *编译期完全可以看成 NSObject类型,NSObject里面可以找到NSObject的定义:
@interface NSObject <NSObject> { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-interface-ivars" Class isa OBJC_ISA_AVAILABILITY; #pragma clang diagnostic pop }
与
obje_object
结构体是相同的。回到刚刚C语言结构体的定义的地方,首先node是一个指向Node结构体的指针变量,在栈空间分配内存,假设它的值是
0x7f7efb48fe20
它指向了一片堆空间内存,这片内存的地址是0x7f7efb48fe20
这片内存存储的结构是什么呢?就是一个Node类型的结构体!再回到
NSString *string = @"Hello world";
那
string
是什么呢? 它也是一个结构体指针,它指向了一片堆空间的内存,这片内存存储的结构是是objc_object!结论: 所以,如果一个对象是一个objc_object结构体,那么我们平常使用的只是指向该对象的结构体指针!
2.2 weak表的初始化过程
2.2.1 weak表的初始化过程
//初始化weak表
/**
* 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;
*
* @param addr Address of __weak ptr.
* @param val Object ptr.
*/
id objc_initWeak(id *addr, id val)
{
*addr = 0;
if (!val) return nil;
return objc_storeWeak(addr, val); // 存储weak对象
}
阅读注释可以知道,该方法触发时机
- 声明一个空的__weak变量,
- 声明一个空的__weak变量然后赋值的时候
2.2.2 存储weak对象的方法
/**
* This function stores a new value into a __weak variable. It would
* be used anywhere a __weak variable is the target of an assignment.
*
* @param location The address of the weak pointer itself
* @param newObj The new object this weak ptr should now point to
*
* @return \e newObj
*/
id
objc_storeWeak(id *location, id newObj)
{
id oldObj;
SideTable *oldTable;
SideTable *newTable;
spinlock_t *lock1;
#if SIDE_TABLE_STRIPE > 1
spinlock_t *lock2;
#endif
// 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 = *location;
oldTable = SideTable::tableForPointer(oldObj);
newTable = SideTable::tableForPointer(newObj);
lock1 = &newTable->slock;
#if SIDE_TABLE_STRIPE > 1
lock2 = &oldTable->slock;
if (lock1 > lock2) {
spinlock_t *temp = lock1;
lock1 = lock2;
lock2 = temp;
}
if (lock1 != lock2) spinlock_lock(lock2);
#endif
spinlock_lock(lock1);
if (*location != oldObj) {
spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE > 1
if (lock1 != lock2) spinlock_unlock(lock2);
#endif
goto retry;
}
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
newObj = weak_register_no_lock(&newTable->weak_table, newObj, location);
// 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 = newObj;
spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE > 1
if (lock1 != lock2) spinlock_unlock(lock2);
#endif
return newObj;
}
objc_initWeak内调用了objc_storeWeak方法
* ```
/**
* This function stores a new value into a __weak variable. It would
* be used anywhere a __weak variable is the target of an assignment.
*
* @param location The address of the weak pointer itself
* @param newObj The new object this weak ptr should now point to
*
* @return \e newObj
*/
id
objc_storeWeak(id *location, id newObj)
{
id oldObj;
SideTable *oldTable;
SideTable *newTable;
spinlock_t *lock1;
#if SIDE_TABLE_STRIPE > 1
spinlock_t *lock2;
#endif
// 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 = *location;
oldTable = SideTable::tableForPointer(oldObj);
newTable = SideTable::tableForPointer(newObj);
lock1 = &newTable->slock;
#if SIDE_TABLE_STRIPE > 1
lock2 = &oldTable->slock;
if (lock1 > lock2) {
spinlock_t *temp = lock1;
lock1 = lock2;
lock2 = temp;
}
if (lock1 != lock2) spinlock_lock(lock2);
#endif
spinlock_lock(lock1);
if (*location != oldObj) {
spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE > 1
if (lock1 != lock2) spinlock_unlock(lock2);
#endif
goto retry;
}
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
newObj = weak_register_no_lock(&newTable->weak_table, newObj, location);
// 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 = newObj;
spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE > 1
if (lock1 != lock2) spinlock_unlock(lock2);
#endif
return newObj;
}
```
网友评论