__weak UIViewController *weakSelf = self;
后来看开源库源码的时候发现了一种比较好的写法
__weak __typeof(self) weakSelf = self;
再后来接触RAC的时候碰到了更牛逼的写法
@weakify(self); @strongify(self);
我们都知道UIKit
的动画是不需要声明__weak
的,从__weak
的实现上也能知道使用__weak
是有开销的。那么这次就深究一下到底什么情况下要用__weak
,什么情况下可以不声明__weak
。
ps. 以下所有讨论都在ARC环境下
循环引用
首先我们来看一个典型循环引用的例子
// HelloBlock.h #importtypedef 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
强引用的myBlock
,myBlock
也强引用了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]; }