手把手教你看懂Block

昨天看到一篇文章,那真的是直戳我的心窝啊,那就是要不要写总结,写什么,从什么开始写等。
我想还有很多iOS爬坑的小伙伴们像我一样,每当想总结一个知识点的时候,就会想那么多知名博客都总结的很详细很透彻了,自己再去总结别人写的知识,感觉low、感觉没有创新,所以就默默的选择将知识点保存到书签方便以后去看,而不是说动手去证实某个观点、加深印象。不管你是不是,反正我的内心是这样子的。
改变就从此刻开始,是时候总结表演真正的技术了,那我就从面试的内容开始。

MRC和ARC中block的存放位置

block在内存中的存放位置决定了block的类型
1.数据区或者称为代码区( __NSGlobalBlock__ )
2.栈区( __NSStackBlock__ )
3.堆区 ( __NSMallocBlock__ )

MRC环境

NSGlobalBlock

即在block代码块中没有访问外部变量(包含了局部和全局变量)

// GlobalBlock
void (^globalBlock)(void) = ^(){
        NSLog(@"GlobalBlock");
    };
NSLog(@"%@",globalBlock);
log:<__NSGlobalBlock__: 0x10a338200>

尝试对GlobalBlock进行copy、retain、release

NSLog(@"copy:%@",[globalBlock copy]);
NSLog(@"retain:%@",[globalBlock retain]);
[globalBlock release];
NSLog(@"release:%@",globalBlock);
log:copy:<__NSGlobalBlock__: 0x10a338200>
log:retain:<__NSGlobalBlock__: 0x10a338200>
log:release:<__NSGlobalBlock__: 0x10a338200>

发现对于存放在代码区的block进行操作是没有什么作用的。

NSStackBlock
// StackBlock
int a = 10;
void (^stackBlock)(void) = ^(){
    NSLog(@"%d",a);
};
NSLog(@"%@",stackBlock);
log:<__NSStackBlock__: 0x7ffee084c588>

你会可能想会不会是因为block中访问的是基本数据类型的原因,因为基本数据类型是存放在栈上
我们用NSString *来尝试:

NSString *stackStr = @"stackBlock";
void (^stackBlock)(void) = ^(){
    NSLog(@"%@",stackStr);
};
 NSLog(@"%@",stackBlock);
log:<__NSStackBlock__: 0x7ffee5495588>

不用怀疑,结果还是在栈上,这就说明在mrc环境下,无论block中访问外部的变量是什么数据类型,都不能改变block的存储位置。
那么问题来了,既然stackBlock是存储在栈上的,那他的死活就是有系统来决定,不由我们控制。
好,我们继续来折腾。block也是一个对象嘛,所以有时候我们会把它装进数组中,那我也来试试看它有什么好玩的事情发生

// 继续上面的操作
// arr是全局不可变数组
self.arr = @[stackBlock];
然后在触摸屏幕的时候,打印一下我们数组中唯一的一个StackBlock
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    NSLog(@"%@",self.arr[0]);
}
StackBlock的野指针错误.png

什么鬼啊,怎么就野指针了呢?
其实到这里已经很明白了,数组中存放一个对象的时候只是进行强引用,因此指针还是同一个,当执行触摸事件的时候,栈上面的block其实已经被系统干掉了,我们在读取一个指向已经释放内存的时候就会出现万恶的野指针错误。

NSMallocBlock

OK,我描述的可能不是很好,如果您能理解以上的内容,就继续折腾一下:我们不妨对stackBlock进行一次copy操作。

// 将上面的代码添加一个copy操作
self.arr = @[[stackBlock copy]];
// 这里打印的还是stackBlock
log:<__NSStackBlock__: 0x7ffee39e1578>
stackBlock的copy.png

此时,我们看看数组中的是什么情况,通过以上图片其实不难看出,copy后的stackBlock其实已经被复制到堆上了__NSMallocBlock__
好了,我们继续执行触摸事件,看到打印的结果是

<__NSMallocBlock__: 0x60400025ef30>

所以说,为什么用block的时候推荐使用copy。好了,我们带着猜疑看看rac环境中的block吧!

RAC环境

NSGlobalBlock
// block块内部不访问外部变量
void (^globalBlock)(void) = ^(){
        NSLog(@"GlobalBlock");
};
NSLog(@"%@",globalBlock);
NSLog(@"copy:%@",[globalBlock copy]);
log:<__NSGlobalBlock__: 0x10cf7b200>
log:copy:<__NSGlobalBlock__: 0x10cf7b200>

发现和mrc中没什么区别

NSStackBlock
 NSString *stackStr = @"stackBlock";
 void (^stackBlock)(void) = ^(){
        NSLog(@"%@",stackStr);
};
 NSLog(@"%@",stackBlock);
log:<__NSMallocBlock__: 0x600000443ab0>

有没有发现和mrc中的不同。没错的,在arc中,只要我们定义一个block赋值给一个strong类型的指针,那么系统就会默认的进行copy。
那怎么证实这个结论呢?
简单呀,既然你说是strong类型的指针,那我就用弱的呗

@interface BlockController ()
@property(nonatomic,weak)void(^stackBlock)(void);
@end

NSString *stackStr = @"stackBlock";
self.stackBlock = ^(){
    NSLog(@"%@",stackStr);
};
NSLog(@"%@",_stackBlock);
log:<__NSStackBlock__: 0x7ffee144b590>

是不是就变成了__NSStackBlock__了。其实还有一个更简单的验证方法,直接打印一个block。

NSString *stackStr = @"stackBlock";
NSLog(@"%@", ^(){
    NSLog(@"%@",stackStr);
});
log:<__NSStackBlock__: 0x7ffee862e590>
NSMallocBlock

其实在rac中,我们平时使用的block,绝大部分都是存在堆区,并不是说rac中block就不能存在栈区。
该知识点也是小的在面试的时候回答的不是很好,所有冲动之后记录下来,方便以后纠错。如果老铁发现有bug,请指正。感谢您的阅读。

你可能感兴趣的:(手把手教你看懂Block)