__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 
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];     
}