Block 内存管理
block 为什么用 copy 修饰
内存栈区
由编译器自动分配释放,存放函数的参数值,局部变量的值等,不需要程序员来操心。其操作方式类似于数据结构中的栈。
内存堆区
一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。尽管后边苹果引入了ARC机制,但是ARC的机制其实仅仅是系统帮助程序员添加了retain,release,autorelease代码,并不是说系统就可以自动管理了。他的系统管理的原理还是MRC,并没有本质区别。注意内存堆区与数据结构中的堆是两回事,分配方式倒是类似于链表。
block的作用域
block是一个对象,所以block理论上是可以retain/release的。但是block在创建的时候它的内存是默认是分配在栈(stack)上,而不是堆(heap)上的。所以它的作用域仅限创建时候的当前上下文(函数, 方法...),当你在该作用域外调用该block时,block占用的内存已经释放,无法进行访问,程序就会崩溃,出现野指针错误。
函数返回
在一个函数的内部,return的时候返回的都是一个拷贝,不管是变量、对象还是指针都是返回拷贝,但是这个拷贝是浅拷贝。
对于返回一些非动态分配(new/malloc)得到的指针就可能出现问题,因为尽管你返回了这个指针地址。但是这个指针可能指向的栈内存,栈内存在函数执行完毕后就自动销毁了。如果销毁之后你再去访问,就会访问坏内存会导致程序崩溃。
在MRC下,如果一个block作为参数,没有经过copy就返回。后果是什么呢?由于return的时候返回的是浅拷贝,也就是说返回的是对象的地址,因为在返回后这个block对应的栈内存就销毁了。如果你多次调用这个block就会发现,程序会崩溃。崩溃原因就是上边所说,block占用的空间已经释放了,你不可以进行访问了。
解决方案:就是在返回的时候,把block进行拷贝作为参数进行返回。这样做的好处是返回的那个block存储空间是在堆内,堆内的空间需要程序员自己去释放,系统不会自动回收,也就不会出现访问已释放内存导致的崩溃了。也就是我们在MRC下需要使用copy修饰符的原因。
什么时候block会造成循环引用
首先,并不是在 block 内部使用self 都会出现循环引用问题.
只有block 直接或间接被 self 储存才会造成循环引用.
在 block 中使用 self或 self 属性或者"_"语法都会使 block 对 self 进行强引用,因为只要block中用到了对象的属性或者函数,block就会持有该对象而不是该对象中的某个属性或者函数。
实际开发中常见block的循环引用:
使用通知(NSNotifation),调用系统自带的Block,在Block中使用self会发生循环引用。
block 循环引用的解决办法:
方法一
通过__weak的修饰,先把self弱引用(默认是强引用,实际上self是有个隐藏的__strong修饰的),然后在block回调里用weakSelf,这样就会打破保留环,从而避免了循环引用
__weak typeof(self) weakSelf = self;
提醒:
__block与__weak都可以用来解决循环引用,
__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
__block对象可以在block中被重新赋值,__weak不可以。
方法二
@weakify
@weakify(self)
self.myBlock = ^() {
NSString *localString = self.blockString;
};
缺陷
以上两个方法都可以解决 block-self的循环引用问题,但是如果我想在Block中延时来运行某段代码,这里就会出现一个问题,看这段代码:
- (void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakPerson test];
});
};
person.mitBlock();
}
直接运行这段代码会发现[weakPerson test];并没有执行,打印一下会发现,weakPerson已经是 Nil 了,这是由于当我们的viewDidLoad方法运行结束,由于是局部变量,无论是MitPerson和weakPerson都会被释放掉,那么这个时候在Block中就无法拿到正真的person内容了。
解决办法 strong-weak-dance
- (void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};
person.mitBlock();
}
原理
堆里面的block(被copy过的block)有以下现象:
1.block内部如果通过外面声明的强引用来使用,那么block内部会自动产生一个强引用指向所使用的对象。
2.block内部如果通过外面声明的弱引用来使用,那么block内部会自动产生一个弱引用指向所使用的对象。