前言
在上篇中,仔细分析了一下Block的实现原理以及__block捕获外部变量的原理。然而实际使用Block过程中,还是会遇到一些问题,比如Retain Circle的问题。
目录
-
1.Retain Circle的由来
-
2.weak、strong的实现原理
-
3.weakSelf、strongSelf的用途
-
4.@weakify、@strongify实现原理
一. Retain Circle的由来
循环引用的问题相信大家都很理解了,这里还是简单的提一下。
当A对象里面强引用了B对象,B对象又强引用了A对象,这样两者的retainCount值一直都无法为0,于是内存始终无法释放,导致内存泄露。所谓的内存泄露就是本应该释放的对象,在其生命周期结束之后依旧存在。
这是2个对象之间的,相应的,这种循环还能存在于3,4……个对象之间,只要相互形成环,就会导致Retain Cicle的问题。
当然也存在自身引用自身的,当一个对象内部的一个obj,强引用的自身,也会导致循环引用的问题出现。常见的就是block里面引用的问题。
二.weak、strong的实现原理
在ARC环境下,id类型和对象类型和C语言其他类型不同,类型前必须加上所有权的修饰符。
所有权修饰符总共有4种:
1.strong修饰符 2.weak修饰符 3.unsafe_unretained修饰符 4.autoreleasing修饰符
一般我们如果不写,默认的修饰符是__strong。
要想弄清楚strong,weak的实现原理,我们就需要研究研究clang(LLVM编译器)和objc4 Objective-C runtime库了。
关于clang有一份关于ARC详细的文档,有兴趣的可以仔细研究一下文档里面的说明和例子,很有帮助。
http://clang.llvm.org/docs/AutomaticReferenceCounting.html
以下的讲解,也会来自于上述文档中的函数说明。
1.__strong的实现原理
(1)对象持有自己
首先我们先来看看生成的对象持有自己的情况,利用alloc/new/copy/mutableCopy生成对象。
当我们声明了一个__strong对象
{
id __strong obj = [[NSObject alloc] init];
}
LLVM编译器会把上述代码转换成下面的样子
id __attribute__((objc_ownership(strong))) obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
相应的会调用
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj,selector(init));
objc_release(obj);
上述这些方法都好理解。在ARC有效的时候就会自动插入release代码,在作用域结束的时候自动释放。
(2)对象不持有自己
生成对象的时候不用alloc/new/copy/mutableCopy等方法。
{
id __strong obj = [NSMutableArray array];
}
LLVM编译器会把上述代码转换成下面的样子
id __attribute__((objc_ownership(strong))) array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
查看LLVM文档,其实是下述的过程
相应的会调用
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
与之前对象会持有自己的情况不同,这里多了一个objc_retainAutoreleasedReturnValue函数。
这里有3个函数需要说明:
1.id objc_retainAutoreleaseReturnValue(id value)
id objc_retainAutoreleaseReturnValue(id value); Precondition: value is null or a pointer to a valid object.
If value is null, this call has no effect. Otherwise, it performs a retain operation followed by the operation described in objc_autoreleaseReturnValue.
Equivalent to the following code: id objc_retainAutoreleaseReturnValue(id value) { return objc_autoreleaseReturnValue(objc_retain(value)); }
Always returns value
2.id objc_retainAutoreleasedReturnValue(id value)
id objc_retainAutoreleasedReturnValue(id value); Precondition: value is null or a pointer to a valid object.
If value is null, this call has no effect. Otherwise, it attempts to accept a hand off of a retain count from a call to objc_autoreleaseReturnValue on value in a recently-called function or something it calls. If that fails, it performs a retain operation exactly like objc_retain.
Always returns value
3.id objc_autoreleaseReturnValue(id value)
id objc_autoreleaseReturnValue(id value); Precondition: value is null or a pointer to a valid object.
If value is null, this call has no effect. Otherwise, it makes a best effort to hand off ownership of a retain count on the object to a call toobjc_retainAutoreleasedReturnValue for the same object in an enclosing call frame. If this is not possible, the object is autoreleased as above.
Always returns value
这3个函数其实都是在描述一件事情。 it makes a best effort to hand off ownership of a retain count on the object to a call to objc_retainAutoreleasedReturnValue for the same object in an enclosing call frame。
这属于LLVM编译器的一个优化。objc_retainAutoreleasedReturnValue函数是用于自己持有(retain)对象的函数,它持有的对象应为返回注册在autoreleasepool中对象的方法或者是函数的返回值。
在ARC中原本对象生成之后是要注册到autoreleasepool中,但是调用了objc_autoreleasedReturnValue 之后,紧接着调用了 objc_retainAutoreleasedReturnValue,objc_autoreleasedReturnValue函数会去检查该函数方法或者函数调用方的执行命令列表,如果里面有objc_retainAutoreleasedReturnValue()方法,那么该对象就直接返回给方法或者函数的调用方。达到了即使对象不注册到autoreleasepool中,也可以返回拿到相应的对象。
2.__weak的实现原理
声明一个__weak对象
{
id __weak obj = strongObj;
}
假设这里的strongObj是一个已经声明好了的对象。
LLVM转换成对应的代码
id __attribute__((objc_ownership(none))) obj1 = strongObj;
相应的会调用
id obj ;
objc_initWeak(&obj,strongObj);
objc_destoryWeak(&obj);
看看文档描述
id objc_initWeak(id *object, id value); Precondition: object is a valid pointer which has not been registered as a __weak object.
value is null or a pointer to a valid object. If value is a null pointer or the object to which it points has begun deallocation, object is zero-initialized. Otherwise, object is registered as a __weak object pointing to value
Equivalent to the following code: id objc_initWeak(id _object, id value) { _object = nil; return objc_storeWeak(object, value); }
Returns the value of object after the call. Does not need to be atomic with respect to calls to objc_storeWeak on object
objc_initWeak的实现其实是这样的
id objc_initWeak(id *object, id value) {
*object = nil;
return objc_storeWeak(object, value);
}
会把传入的object变成0或者nil,然后执行objc_storeWeak函数。
那么objc_destoryWeak函数是干什么的呢?
void objc_destroyWeak(id *object); Precondition: object is a valid pointer which either contains a null pointer or has been registered as a __weak object.
object is unregistered as a weak object, if it ever was. The current value of object is left unspecified; otherwise, equivalent to the following code:
void objc_destroyWeak(id *object) { objc_storeWeak(object, nil); }
Does not need to be atomic with respect to calls to objc_storeWeak on object
objc_destoryWeak函数的实现
void objc_destroyWeak(id *object) {
objc_storeWeak(object, nil);
}
也是会去调用objc_storeWeak函数。objc_initWeak和objc_destroyWeak函数都会去调用objc_storeWeak函数,唯一不同的是调用的入参不同,一个是value,一个是nil。
那么重点就都落在objc_storeWeak函数上了。
id objc_storeWeak(id *object, id value); Precondition: object is a valid pointer which either contains a null pointer or has been registered as a __weak object. value is null or a pointer to a valid object.
If value is a null pointer or the object to which it points has begun deallocation, object is assigned null and unregistered as a weak object. Otherwise, object is registered as a weak object or has its registration updated to point to value
Returns the value of object after the call.
objc_storeWeak函数的用途就很明显了。由于weak表也是用Hash table实现的,所以objc_storeWeak函数就把第一个入参的变量地址注册到weak表中,然后根据第二个入参来决定是否移除。如果第二个参数为0,那么就把__weak变量从weak表中删除记录,并从引用计数表中删除对应的键值记录。
所以如果weak引用的原对象如果被释放了,那么对应的weak对象就会被指为nil。原来就是通过objc_storeWeak函数这些函数来实现的。
以上就是ARC中strong和weak的简单的实现原理,更加详细的还请大家去看看这一章开头提到的那个LLVM文档,里面说明的很详细。
三.weakSelf、strongSelf的用途
在提weakSelf、strongSelf之前,我们先引入一个Retain Cicle的例子。
假设自定义的一个student类
例子1:
#import
typedef void(^Study)();
@interface Student : NSObject
@property (copy , nonatomic) NSString *name;
@property (copy , nonatomic) Study study;
@end
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
student.study = ^{
NSLog(@"my name is = %@",student.name);
};
}
到这里,大家应该看出来了,这里肯定出现了循环引用了。student的study的Block里面强引用了student自身。根据上篇文章的分析,可以知道,_NSConcreteMallocBlock捕获了外部的对象,会在内部持有它。retainCount值会加一。
我们用Instruments来观察一下。添加Leak观察器。
当程序运行起来之后,在Leak Checks观察器里面应该可以看到红色的❌,点击它就会看到内存leak了。有2个泄露的对象。Block和Student相互循环引用了。
打开Cycles & Roots 观察一下循环的环。
这里形成环的原因block里面持有student本身,student本身又持有block。
那再看一个例子2:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
student.study = ^(NSString * name){
NSLog(@"my name is = %@",name);
};
student.study(student.name);
}
我把block新传入一个参数,传入的是student.name。这个时候会引起循环引用么?
答案肯定是不会。
如上图,并不会出现内存泄露。原因是因为,student是作为形参传递进block的,block并不会捕获形参到block内部进行持有。所以肯定不会造成循环引用。
再改一下。看例子3:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@property (copy,nonatomic) NSString *name;
@property (strong, nonatomic) Student *stu;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
self.name = @"halfrost";
self.stu = student;
student.study = ^{
NSLog(@"my name is = %@",self.name);
};
student.study();
}
这样会形成循环引用么?
答案也是否定的。
ViewController虽然强引用着student,但是student里面的blcok强引用的是viewController的name属性,并没有形成环。如果把上述的self.name改成self,也依旧不会产生循环引用。因为他们都没有强引用这个block。
那遇到循环引用我们改如何处理呢??类比平时我们经常写的delegate,可以知道,只要有一边是__weak就可以打破循环。
先说一种做法,利用__block解决循环的做法。例子4:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
__block Student *stu = student;
student.name = @"Hello World";
student.study = ^{
NSLog(@"my name is = %@",stu.name);
stu = nil;
};
}
这样写会循环么?看上去应该不会。但是实际上却是会的。
由于没有执行study这个block,现在student持有该block,block持有block变量,block变量又持有student对象。3者形成了环,导致了循环引用了。 想打破环就需要破坏掉其中一个引用。__block不持有student即可。
只需要执行一下block即可。例子5:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
__block Student *stu = student;
student.study = ^{
NSLog(@"my name is = %@",stu.name);
stu = nil;
};
student.study();
}
这样就不会循环引用了。
使用block解决循环引用虽然可以控制对象持有时间,在block中还能动态的控制是block变量的值,可以赋值nil,也可以赋值其他的值,但是有一个唯一的缺点就是需要执行一次block才行。否则还是会造成循环引用。
值得注意的是,在ARC下__block会导致对象被retain,有可能导致循环引用。而在MRC下,则不会retain这个对象,也不会导致循环引用。
接下来可以正式开始讲讲weakSelf 和 strongSelf的用法了。
1.weakSelf
说道weakSelf,需要先来区分几种写法。 weak typeof(self)weakSelf = self; 这是AFN里面的写法。。
define WEAKSELF typeof(self) __weak weakSelf = self; 这是我们平时的写法。。
先区分typeof() 和 typeof() 由于笔者一直很崇拜AFNetWorking的作者,这个库里面的代码都很整洁,里面各方面的代码都可以当做代码范本来阅读。遇到不懂疑惑的,都要深究,肯定会有收获。这里就是一处,平时我们的写法是不带的,AFN里面用这种写法有什么特殊的用途么?
在SOF上能找到相关的答案:
typeof() and __typeof() are compiler-specific extensions to the C language, because standard C does not include such an operator. Standard C requires compilers to prefix language extensions with a double-underscore (which is also why you should never do so for your own functions, variables, etc.) typeof() is exactly the same, but throws the underscores out the window with the understanding that every modern compiler supports it. (Actually, now that I think about it, Visual C++ might not. It does support decltype() though, which generally provides the same behaviour as typeof().) All three mean the same thing, but none are standard C so a conforming compiler may choose to make any mean something different.
其实两者都是一样的东西,只不过是C里面不同的标准,兼容性不同罢了。
更加详细的官方说明
那么抽象出来就是这2种写法。
define WEAKSELF __weak typeof(self)weakSelf = self;
define WEAKSELF typeof(self) __weak weakSelf = self;
这样子看就清楚了,两种写法就是完全一样的。
我们可以用WEAKSELF来解决循环引用的问题。例子6:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
__weak typeof(student) weakSelf = student;
student.study = ^{
NSLog(@"my name is = %@",weakSelf.name);
};
student.study();
}
这样就解决了循环引用的问题了。
解决循环应用的问题一定要分析清楚哪里出现了循环引用,只需要把其中一环加上weakSelf这类似的宏,就可以解决循环引用。如果分析不清楚,就只能无脑添加weakSelf、strongSelf,这样的做法不可取。
在上面的例子3中,就完全不存在循环引用,要是无脑加weakSelf、strongSelf是不对的。在例子6中,也只需要加一个weakSelf就可以了,也不需要加strongSelf。
曾经在segmentfault也看到过这样一个问题,问:为什么iOS的Masonry中的self不会循环引用?
> 如果我用blocksKit的bk_addEventHandler > 方法, 其中使用strong self, 该viewController就无法dealloc, 我理解是因为,self retain self.view, retain testButton, retain self. 但是如果只用Mansonry的mas_makeConstraints > 方法, 同样使用strong self, 该viewController却能正常dealloc, 请问这是为什么, 为什么Masonry没有导致循环引用? 看到这里,读者应该就应该能回答这个问题了。
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
上述描述有误,感谢@酷酷的哀殿 耐心指点
更正如下:
关于 Masonry ,它捕获了变量 self,然后对其执行了setTranslatesAutoresizingMaskIntoConstraints:方法。但是,因为执行完毕后,block会被销毁,没有形成环。所以,没有引起循环依赖。
2.strongSelf
上面介绍完了weakSelf,既然weakSelf能完美解决Retain Circle的问题了,那为何还需要strongSelf呢?
还是先从AFN经典说起,以下是AFN其中的一段代码:
如果block里面不加strong typeof(weakSelf)strongSelf = weakSelf会如何呢?
输出:
my name is = (null)
为什么输出是这样的呢?
重点就在dispatch_after这个函数里面。在study()的block结束之后,student被自动释放了。又由于dispatch_after里面捕获的weak的student,根据第二章讲过的weak的实现原理,在原对象释放之后,__weak对象就会变成null,防止野指针。所以就输出了null了。
那么我们怎么才能在weakSelf之后,block里面还能继续使用weakSelf之后的对象呢?
究其根本原因就是weakSelf之后,无法控制什么时候会被释放,为了保证在block内不会被释放,需要添加__strong。
在block里面使用的__strong修饰的weakSelf是为了在函数生命周期中防止self提前释放。strongSelf是一个自动变量当block执行完毕就会释放自动变量strongSelf不会对self进行一直进行强引用。
输出
my name is = Hello World
至此,我们就明白了weakSelf、strongSelf的用途了。
weakSelf 是为了block不持有self,避免Retain Circle循环引用。在 Block 内如果需要访问 self 的方法、变量,建议使用 weakSelf。
strongSelf的目的是因为一旦进入block执行,假设不允许self在这个执行过程中释放,就需要加入strongSelf。block执行完后这个strongSelf 会自动释放,没有不会存在循环引用问题。如果在 Block 内需要多次 访问 self,则需要使用 strongSelf。
关于Retain Circle最后总结一下,有3种方式可以解决循环引用。