__weak UIViewController *weakSelf = self;

后来看开源库源码的时候发现了一种比较好的写法

__weak __typeof(self) weakSelf = self;

再后来接触RAC的时候碰到了更牛逼的写法

@weakify(self);@strongify(self);

我们都知道UIKit的动画是不需要声明__weak的,从__weak的实现上也能知道使用__weak是有开销的。那么这次就深究一下到底什么情况下要用__weak,什么情况下可以不声明__weak
ps. 以下所有讨论都在ARC环境下

循环引用

首先我们来看一个典型循环引用的例子

//  HelloBlock.h#import <Foundation/Foundation.h>typedef void (^MyBlock)(NSString *name);@interface HelloBlock : NSObject@property (nonatomic, copy) MyBlock myBlock;@property (nonatomic, copy) NSString *name;- (void)test;@end
//  HelloBlock.m#import "HelloBlock.h"@implementation HelloBlock- (void)test {    self.myBlock = ^(NSString *name){        self.name = name;    };    self.myBlock(@"ypli");    NSLog(@"%@",self.name);}@end

在这是典型的循环引用的例子,其原因简单的说是self强引用了myBlock,其实Block也可以看做是一个对象(万物都是对象?),它也有自己对应的结构体,在Block生成的时候,Block会将在其内部使用的变量进行强引用,这样一来,self强引用的myBlockmyBlock也强引用了self,谁也得不到释放。
其实此时Xcode已经给出了警告。

然后我们用clang -x objective-c -arch x86_64 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator9.3.sdk -rewrite-objc -fobjc-arc -fblocks -mmacosx-version-min=10.11 -fobjc-runtime=macosx-10.11 -O0 HelloBlock.m来查看重写成cpp的源码,注意一下,网上多数给出的是clang -rewrite-objc HelloBlock.m,这个其实重写出来是非ARC版本的。
HelloBlock类定义如下

struct HelloBlock_IMPL {    struct NSObject_IMPL NSObject_IVARS;    __strong MyBlock _myBlock;    NSString *__strong _name;};

block的定义如下

struct __HelloBlock__test_block_impl_0 {  struct __block_impl impl;  struct __HelloBlock__test_block_desc_0* Desc;  HelloBlock *const __strong self;  __HelloBlock__test_block_impl_0(void *fp, struct __HelloBlock__test_block_desc_0 *desc, HelloBlock *const __strong _self, int flags=0) : self(_self) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};

可以很明确的看到block里声明了HelloBlock *const __strong self;指针,别的代码的含义在这里就不解释了,有兴趣可以参考《Objective-C 高级编程 iOS与OS X多线程和内存管理》

weakSelf

下面我们把test方法改写成以下形式:

- (void)test {    __weak __typeof(self) weakSelf = self;    self.myBlock = ^(NSString *name){        weakSelf.name = name;    };    self.myBlock(@"ypli");    NSLog(@"%@",self.name);}

然后再重写成c++,
HelloBlock类定义如下

struct HelloBlock_IMPL {    struct NSObject_IMPL NSObject_IVARS;    __strong MyBlock _myBlock;    NSString *__strong _name;};

block的定义如下

struct __HelloBlock__test_block_impl_0 {  struct __block_impl impl;  struct __HelloBlock__test_block_desc_0* Desc;  HelloBlock *const __weak weakSelf;  __HelloBlock__test_block_impl_0(void *fp, struct __HelloBlock__test_block_desc_0 *desc, HelloBlock *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};

可以看到,block中指向HelloBlock的指针已经变为了__weak,这样就避免了循环引用。
现在我们来换一种形式,新增test2方法

- (void)test2 {    MyBlock tmpBlock = ^(NSString *name) {        self.name = name;    };    tmpBlock(@"ypli2");    NSLog(@"%@",self.name);}

再次重写成c++
很明显,Block类的数据段是不会发生变化了,因为我们并没有产生实例变量

struct HelloBlock_IMPL {    struct NSObject_IMPL NSObject_IVARS;    __strong MyBlock _myBlock;    NSString *__strong _name;};

block的实现和之前第一次看到的情况是一样的,block拥有一个指向self的强指针

struct __HelloBlock__test2_block_impl_0 {  struct __block_impl impl;  struct __HelloBlock__test2_block_desc_0* Desc;  HelloBlock *const __strong self;  __HelloBlock__test2_block_impl_0(void *fp, struct __HelloBlock__test2_block_desc_0 *desc, HelloBlock *const __strong _self, int flags=0) : self(_self) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};

那么这个block存在哪了呢? 我们来看test2的实现,

static void _I_HelloBlock_test2(HelloBlock * self, SEL _cmd) {    MyBlock tmpBlock = ((void (*)(NSString *__strong))&__HelloBlock__test2_block_impl_0((void *)__HelloBlock__test2_block_func_0, &__HelloBlock__test2_block_desc_0_DATA, self, 570425344));    ((void (*)(__block_impl *, NSString *__strong))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock, (NSString *)&__NSConstantStringImpl__var_folders_gh_pgyltm3s4ljg0kyl1rjjpl1r0000gn_T_HelloBlock_0704ac_mi_2);    NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_pgyltm3s4ljg0kyl1rjjpl1r0000gn_T_HelloBlock_0704ac_mi_3,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));}

简化一下

static void _I_HelloBlock_test2(HelloBlock * self, SEL _cmd) {    MyBlock tmpBlock = &__HelloBlock__test2_block_impl_0(__HelloBlock__test2_block_func_0, &__HelloBlock__test2_block_desc_0_DATA, self, 570425344);    tmpBlock->FuncPtr)(tmpBlock, &__NSConstantStringImpl__var_folders_gh_pgyltm3s4ljg0kyl1rjjpl1r0000gn_T_HelloBlock_0704ac_mi_2);    NSLog(&__NSConstantStringImpl__var_folders_gh_pgyltm3s4ljg0kyl1rjjpl1r0000gn_T_HelloBlock_0704ac_mi_3,(objc_msgSend(self, sel_registerName("name")));}

可以看到,tmpBlock仅被test2中的一个临时变量引用。就是说,当test2执行完毕时,tmpBlock将被释放,届时tmpBlock中指向self的强指针自然也被释放。这里即使self没有使用弱指针,也不会产生循环引用。这也就是UIKit的动画不需要声明weak指针的原因。

结论

到此,就结论很明显了

只有当block直接或间接的被一个object持有时,在block内使用就需要weak object来避免循环引用。其中obejct当然可以是self

直接持有就不用多说了,间接持有就是在block中产生了对self成员变量的强引用,此时也有可能造成循环引用,至于具体产生不产生,也取决于这个block是否为self持有,这种形式的循环引用经常不会引起程序员的注意。

用代码表示就是以下形式

Block block = ^{    // 不会产生循环引用    [object doSomething];    _ivar = @"balabala";};block();__weak __typeof(object) weakObject = object;__weak __typeof(_ivar) weakIvar = _ivar;object.block = ^{    // 避免循环引用    [weakObject doSomething];    weakIvar = @"balabala";};

另外有个小track,就是我们其实也可以不声明weak指针,在block的最后,手动将self置为nil,这样在block执行完毕后,强指针被释放了,自然没有循环引用了。但是除非你能保证block一定会被执行,不然是不推荐这种写法的,因为这样情况只有在block被执行之后才会得到释放,假设block始终没有得到执行,那么就会造成内存一直得不到释放了,。

strongSelf

由于weak指针随时都有可能被置为nil,如果当block中使用了多个self,如下

__weak __typeof(self) weakSelf = self;self.block = ^{    [weakSelf doSomething1];    [weakSelf doSomething2];     }

在执行完doSomething1时,weakSelf有可能被置为nil,结果导致doSomething2没有执行,这种执行了一半的情况当然不是我们愿意看到的,所以在block内我们再对weakSelf产生一个强引用,来保证在block结束之前,weakSelf不会被释放。

__weak __typeof(self) weakSelf = self;self.block = ^{    __strong __typeof(weakSelf) strongSelf = weakSelf;    [strongSelf doSomething1];    [strongSelf doSomething2];     }