所有的Block里面的self必须要weak一下?
很显然答案不都是,有些情况下是可以直接使用self的,比如调用系统的方法:
[UIView animateWithDuration:0.5 animations:^{ NSLog(@"%@", self);
}];
因为这个block存在于静态方法中,虽然block对self强引用着,但是self却不持有这个静态方法,所以完全可以在block内部使用self。
另外,来看一个Masonry代码布局的例子,这里面的self会不会造成循环引用呢?
[self.headView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.otherView.mas_centerY);
}];
并不是 block 就一定会造成循环引用,是不是循环引用要看是不是相互持有强引用。block 里用到了 self,那 block 会保持一个 self 的引用,但是 self 并没有直接或者间接持有 block,所以不会造成循环引用。可以看一下Masonry的源代码:
View+MASAdditions.m
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker); return [constraintMaker install];
}
Block引起的循环引用
一般来说我们总会在设置Block之后,在合适的时间回调Block,而不希望回调Block的时候Block已经被释放了,所以我们需要对Block进行copy,copy到堆中,以便后用。
Block可能会导致循环引用问题,因为block在拷贝到堆上的时候,会retain其引用的外部变量,那么如果block中如果引用了他的宿主对象,那很有可能引起循环引用
注意观察,这个作为方法参数的Block体并没有被任何方持有。因此,我们放心在Masonry中使用self.xxx 不会循环引用的。而且这个block里面用weakSelf还有可能会出问题,因为mas_qeual如果得到一个nil参数的话应该会导致程序崩溃。
因为UIView未强持有block,所以这个block只是个栈block,而且构不成循环引用的条件。栈block有个特性就是它执行完毕之后就出栈,出栈了就会被释放掉。看mas_makexxx的方法实现会发现这个block很快就被调用了,完事儿就出栈销毁,构不成循环引用,所以可以直接放心的使self。另外,这个与网络请求里面使用self道理是一样的。
Block与内存管理
根据Block在内存中的位置分为三种类型:
NSGlobalBlock是位于全局区的block,它是设置在程序的数据区域(.data区)中。
NSStackBlock是位于栈区,超出变量作用域,栈上的Block以及 __block变量都被销毁。
NSMallocBlock是位于堆区,在变量作用域结束时不受影响。
注意:在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。
位于堆内存:MallocBlock
堆中的block无法直接创建,其需要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block需要执行copy之后才能存放到堆中)。由于block的拷贝最终都会调用_Block_copy_internal函数。
截获自动变量值
Block表达式可截获所使用的自动变量的值。截获:保存自动变量的瞬间值。因为是“瞬间值”,所以声明Block之后,即便在Block外修改自动变量的值,也不会对Block内截获的自动变量值产生影响。
inti=10;
void(^blk)(void)=^{
NSLog(@"In block, i = %d",i)
};
i=20;//Block外修改变量i,也不影响Block内的自动变量blk();
//i修改为20后才执行,打印: In block, i = 10NSLog(@"i = %d",i);
//打印:i = 20
__block说明符号
自动变量截获的值为Block声明时刻的瞬间值,保存后就不能改写该值,如需对自动变量进行重新赋值,需要在变量声明前附加__block说明符,这时该变量称为__block变量。
__blockinti=10;//i为__block变量,可在block中重新赋值
void(^blk)(void)=^{
NSLog(@"In block, i = %d",i);
};
i=20;
blk();//打印: In block, i = 20
NSLog(@"i = %d",i);//打印:i = 20
自动变量值为一个对象情况
当自动变量为一个类的对象,且没有使用__block修饰时,虽然不可以在Block内对该变量进行重新赋值,但可以修改该对象的属性。如果该对象是个Mutable的对象,例如NSMutableArray,则还可以在Block内对NSMutableArray进行元素的增删:
NSMutableArray*array=[[NSMutableArrayalloc]initWithObjects:@"1",@"2",nil];
NSLog(@"Array Count:%ld",array.count);//打印Array Count:2
void(^blk)(void)=^{
[arrayremoveObjectAtIndex:0];//Ok
//array = [NSNSMutableArray new];//没有__block修饰,编译失败!
};
blk();
NSLog(@"Array Count:%ld",array.count);//打印Array Count:1
Block也是Objective-C中的对象。
根据Block对象创建时所处数据区不同而进行区别:
_NSConcreteStackBlock:在栈上创建的Block对象
_NSConcreteMallocBlock:在堆上创建的Block对象
_NSConcreteGlobalBlock:全局数据区的Block对象
使用__block发生了什么
Block捕获的自动变量添加__block说明符,就可在Block内读和写该变量,也可以在原来的栈上读写该变量。自动变量的截获保证了栈上的自动变量被销毁后,Block内仍可使用该变量。__block保证了栈上和Block内(通常在堆上)可以访问和修改“同一个变量”,__block是如何实现这一功能的?
__block发挥作用的原理:将栈上用__block修饰的自动变量封装成一个结构体,让其在堆上创建,以方便从栈上或堆上访问和修改同一份数据。
Block循环引用原因:一个对象A有Block类型的属性,从而持有这个Block,如果Block的代码块中使用到这个对象A,或者仅仅是用用到A对象的属性,会使Block也持有A对象,导致两者互相持有,不能在作用域结束后正常释放。
全局块存在于全局内存中, 相当于单例.
栈块存在于栈内存中, 超出其作用域则马上被销毁
堆块存在于堆内存中, 是一个带引用计数的对象, 需要自行管理其内存
简而言之,存储在栈中的Block就是栈块、存储在堆中的就是堆块、既不在栈中也不在堆中的块就是全局块。
Block访问外界变量
MRC 环境下:访问外界变量的 Block 默认存储栈中。
ARC 环境下:访问外界变量的 Block 默认存储在堆中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区),自动释放。
Block的复制操作执行的是copy实例方法。不同类型的Block使用copy方法的效果如下表:
三、Block与外界变量
1、截获自动变量(局部变量)值
(1)默认情况
对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量, 不使用则不截获, 因为截获的自动变量会存储于block的结构体内部, 会导致block体积变大。特别要注意的是默认情况下block只能访问不能修改局部变量的值。
int age = 10;
myBlock block = ^{
NSLog(@"age = %d", age);
};
age = 18;
block();
输出结果
age = 10
__block 修饰的外部变量
对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。block可以修改__block 修饰的外部变量的值。
__block int age = 10;
myBlock block = ^{
NSLog(@"age = %d", age);
};
age = 18;
block();
输出为:
age = 18
为什么要用copy去修饰block呢
个人理解:默认情况下,block会存档在栈中(栈是吃了吐),所以block会在函数调用结束被销毁,在调用会报空指针异常,如果用copy修饰的话,可以使其保存在堆区(堆是吃了拉) ,它的生命周期会随着对象的销毁而结束的。只要对象不销毁,我们就可以调用在堆中的block。
在了解block为什么要用copy之前,我们要先了解block的三种类型
一NSGlobalBlock:全局的静态block 没有访问外部变量 你的block类型就是这种类型(也就是说你的block没有调用其他外部变量)
二NSStackBlock:保存在栈中的block,没有用copy去修饰并且访问了外部变量,你的block类型就是这种类型,会在函数调用结束被销毁 (需要在MRC)
三NSMallocBlock 保存在堆中的block 此类型blcok是用copy修饰出来的block 它会随着对象的销毁而销毁,只要对象不销毁,我们就可以调用的到在堆中的block。
信有很多面试者被问到这样的问题:block使用什么修饰,往往能够答出是copy,很多面试官就会问到:为什么要使用copy,这时候就懵了。
我亲身体验了一把,所以先总结一下。
block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈上的,而不是在堆上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。因为栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.
使用retain也可以,但是block的retain行为默认是用copy的行为实现的,
因为block变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆,所以说为了block属性声明和实际的操作一致,最好声明为copy。
block 会自动捕获block 内部使用的变量,并对其强引用
block是通过添加引用来访问实例变量的,所以self会被retain一次,block也是一个强引用,会引起循环引用。
3、关于 __strong
- (void)viewDidLoad { [super viewDidLoad];
MyOBJ *mm = [[MyOBJ alloc]init]; mm.name = @"Lilei";
__weak typeof(student) weakSelf = mm;
mm.doBlock = ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"my name is = %@",weakSelf.name);
});
};
mm.doBlock();}
//输出:my name is = (null)
在dispatch_after这个函数里面。在doBlock()的block结束之后,mm被自动释放了。 又由于dispatch_after里面捕获的__weak的mm,在原对象释放之后,__weak对象就会变成nil,防止野指针。
那么我们怎么才能在weakSelf之后,block里面还能继续使用weakSelf之后的对象呢?
究其根本原因就是weakSelf之后,无法控制什么时候会被释放,为了保证在block内不会被释放,需要添加__strong。
weakSelf 是为了block不持有self,避免Retain Circle循环引用。
在 Block 内如果需要访问 self 的方法、变量,建议使用 weakSelf。
strongSelf的目的是因为一旦进入block执行,假设不允许self在这个执行过程中释放,就需要加入strongSelf。block执行完后这个strongSelf 会自动释放,不会存在循环引用问题。
如果在 Block 内需要多次 访问 self,则需要使用 strongSelf。
关于 多层嵌套的block
- (void)setUpModel{
XXModel *model = [XXModel new];
__weak typeof(self) weakSelf = self;
model.dodoBlock = ^(NSString *title) {
__strong typeof(self) strongSelf = weakSelf;//第一层
strongSelf.titleLabel.text = title;
__weak typeof(self) weakSelf2 = strongSelf;
strongSelf.model.dodoBlock = ^(NSString *title2) {
__strong typeof(self) strongSelf2 = weakSelf2;//第二层
strongSelf2.titleLabel.text = title2;
};
};
self.model = model;
}
这样就避免的引用循环,不管都多少个block嵌套,都可以按照这样来做。
OS block嵌套block中weakify的使用
结论:嵌套中的block只需要写strongify,不需要再写一次weakify
只要持有block的变量和block中的变量不是同一个变量(可以指向同一个变量),就不会因此循环引用,导致memory leak。
所以,当block嵌套block的时候,内部的block不需要再次增加@weakify(self)。