一、修饰符
ARC 环境下,所有的修饰符有以下4种
-
__strong
修饰符 -
__weak
修饰符 -
__unsafe_unretained
修饰符 -
__autoreleasing
修饰符
- 底层都是被函数
objc_ownership(xxx)
修饰,不同的修饰符,入参xxx不同。
__strong
--->objc_ownership(strong)
__weak
--->objc_ownership(weak)
__unsafe_unretained
--->objc_ownership(none)
__autoreleasing
--->objc_ownership(autoreleasing)
1.1、以__weak
为例
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
NSString *str = @"helloworld";
id __weak objc = str;
NSLog(@"%@", objc);
return 0;
}
注意: clang -rewrite-objc main.m
会报错,需要用以下替换。
clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations weak.m
int main(int argc, const char * argv[]) {
NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_jf_zkvr_3r17rl3q1fw6zhz6jr40000gn_T_weak_cfe516_mi_0;
// id __weak objc = str;
id __attribute__((objc_ownership(weak))) objc = str;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_jf_zkvr_3r17rl3q1fw6zhz6jr40000gn_T_weak_cfe516_mi_1, objc);
return 0;
}
二、__weak
和__strong
的原理
2.1、__weak
原理
1、基础知识
- 最大作用是防止循环引用,当属性对象被释放的时候,weak属性会自动置为nil。
2、原理解释
Runtime维护了一个SideTable
的hash表,Key是所指对象的地址,Value是一张weak_table_t
的哈希数组,用来存储weak_entry_t
的弱引用对象;其中weak_entry_t
也是一个hash结构,如果存储的弱引用个数少于一定个数,就用静态数组存储;如果超过则用动态数组存储。
3、源码解析
在以下代码处断点,开启汇编窗口(Debug -> Debug Wokflow -> Always Show Disassembly
):
id __weak obj = str;
发现底层调用了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. // __weak指针 的地址 ,存储指针的地址。
* @param newObj Object ptr. // 所引用的对象,即例子中的obj 。
*/
id objc_initWeak(id *location, id newObj) {
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
- 总结:
__weak
底层函数调用objc_initWeak()
--->objc_storeWeak()
。
objc_storeWeak()
的作用是更新指针指向,然后创建对应的弱引用表。
注意:实现原理在于理解 objc_storeWeak()
函数的实现,详细可见 iOS底层原理:weak的实现原理 。
5、理解weak实现原理,重点理解内部的存储结构:
struct SideTable {
weak_table_t weak_table;
};
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
};
// weak对象实体存储结构体
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];
};
};
// 用来判断采用哪种存储方式(定长数组 或 动态数组)
// 当弱引用该对象的指针数目小于等于WEAK_INLINE_COUNT时,使用定长数组。
// 当超过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_referrer_t类型:DisguisedPtr泛型类,这里面类型是 objc_object
typedef DisguisedPtr<objc_object *> weak_referrer_t;
借用别人整理的图如下:
6、weak 原理概述:可以概括以下三步:
1、初始化
__weak
对象的时候,底层会调用objc_initWeak
函数,objc_initWeak
函数内部又会调用objc_storeWeak()
函数。2、
objc_storeWeak()
函数,会在底层创建一张weak_table_t
结构的hash表,key是所指对象的地址,value是weak指针的地址数组,数组存放的是weak_entry_t
的weak对象。3、对象释放时,调用
clearDeallocating()
函数根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。。
2.2、__strong
其修饰的对象引用计数器会+1,避免在block执行的时候,对象被提前释放了。
2.3、__weak
和 __strong
的应用
block外使用:
__weak typeof(self) weakSelf = self;
block捕获的是weakSelf
,而其会在对象dealoc的时候,自动被释放置为nil,因此打破循环引用。block内使用:
__strong typeof(weakSelf) strongSelf = weakSelf;
1、block 内的代码在__MyObject__test_block_func_0
函数内,当使用strongSelf
时,会先取出__weak
修饰的成员变量self
。
2、然后再生成一个__strong
修饰的局部变量,这时候self
的引用计数 +1。
3、这样的目的是在block
内的代码块执行完之前避免self
被dealloc
掉。当block
执行完毕之后,局部变量strongSelf
被释放,self 的引用计数 -1。
三、关于RAC的奇妙宏@weakify(self) 和 @strongify(self)
宏的使用过程很精妙,有兴趣可以查看
深入研究 Block 用 weakSelf、strongSelf、@weakify、@strongify 解决循环引用
Reactive Cocoa中的@weakify、@strongify是如何装逼
- 两个宏的作用等同于:
@weakify(self) = @autoreleasepool{} __weak __typeof__ (self) self_weak_ = self;
@strongify(self) = @autoreleasepool{} __strong __typeof__(self) self = self_weak_;
5.1、@weakify(self) 和 @strongify(self)
的原理分析
#import <Foundation/Foundation.h>
@interface MyObject : NSObject {
NSString *_age;
}
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) dispatch_block_t actionBlock;
@end
@implementation MyObject
- (void)dealloc {
NSLog(@"MyObject Dealloc");
}
- (void)test {
self.name = @"n";
_age = @"10";
@autoreleasepool{} __weak __typeof__ (self) self_weak_ = self; // @weakify(self)
void(^textBlock)(void) = ^{
@autoreleasepool{} __strong __typeof__(self) self = self_weak_; // @strongify(self)
_age = @"11"; // 会导致循环引用
//self.name = @"a"; // 不会导致循环引用
//self->_age = @"23"; // 不会导致循环引用
};
textBlock();
self.actionBlock = textBlock;
}
@end
-
clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.14 filename
查看底层代码。
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
MyObject *const __weak self_weak_; // 捕获的是self_weak_
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *const __weak _self_weak_, int flags=0) : self_weak_(_self_weak_) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
MyObject *const __weak self_weak_ = __cself->self_weak_; // bound by copy
/* @autoreleasepool */{ __AtAutoreleasePool __autoreleasepool; } __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;
(*(NSString *__strong *)((char *)self + OBJC_IVAR_$_MyObject$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_jf_zkvr_3r17rl3q1fw6zhz6jr40000gn_T_person_a08aad_mi_3;
}
block默认捕获self是强引用(
MyObject *const __strong self;
);而使用上面修饰后@weakify(self)
,block内部捕获的是MyObject *const __weak self_weak_;
,因此打破了循环引用。注意1:block内部如果使用
_name
方式引用,也会导致循环引用;必须通过self.name
或者self->name
才能打破循环引用。
具体原因,网上有人是说对self和self_weak_都进行了捕获,可是clang之后的代码没有发现显示引用,有待研究。注意2:
@weakify
和@strongify
必须都使用。
测试发现如果只使用@weakify
,而不调@ strongify
一样会导致循环引用。原因是这种情况下捕获的还是self
,而不是self_weak_
。
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
MyObject *const __strong self;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *const __strong _self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
MyObject *const __strong self = __cself->self; // bound by copy
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_jf_zkvr_3r17rl3q1fw6zhz6jr40000gn_T_person_2fca58_mi_3);
}