1、block的本质
block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
2、block的变量捕获(capture)
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
block的变量捕捉.png
结论:
局部变量(block内部会捕捉变量)
1、auto
使用auto修饰的局部变量,变量的作用域就在局部 离开作用域就会被销毁
block内部 捕捉auto修饰的局部变量 在block创建的时候直接把值给传递进去了
2、static
使用auto修饰的局部变量,只要程序没有退出就一直存在在内存中不被销毁
block内部 捕捉static 修饰的局部变量 在block创建的时候直接把地址给传递进去了
全局变量 (block内部不会捕捉变量)
直接访问全局变量的值,不会捕获变量
也就是说 block 内部访问局部变量时 block在创建的时候就会在内部自动生成对应的变量 然后将值或指针传递进去
Block 内部访问全局变量的时候 不会做任何处理 直接访问全局变量的值
原因是:在垮函数调用block的时候 局部变量随时可能会被销毁 所以为了保证block内部访问的值不被销毁,block在创建的时候就直接自动生成对应的变量来存储内部访问的变量。全局变量无论什么地方访问他都会存在 所以不用关心被销毁因此block内部不会去捕捉
3、block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,
最终都是继承自NSBlock类型(NSBlock 继承 NSObject)
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
三种block 内存分配
三种block 内存分配.png上图注解:
上图 从上至下内存地址排列 是 从低地址到高地址
程序区域:平时写的OC代码 都在 程序区域也就是.text区
数据区域:一般全局变量都放在数据区域也就是.data区
堆:平时alloc 出来的对象都会放在 堆区
(堆区:动态分配内存,也就是开发者自己去申请内存,且开发者自己管理内存)
栈:存放局部变量,程序会自己管理内存
如何区分这三种block
区分这三种block.png上图注解:
globalBlock
没有访问auto变量的block 都是 globalBlock
如:
void (^block1)(void) = ^{
NSLog(@"Hello");
};
或:
//a 是局部变量
static int a = 10;
void (^block1)(void) = ^{
NSLog(@"Hello----%d",a);
};
或:
//a 是全局部变量
int a = 10;
void (^block1)(void) = ^{
NSLog(@"Hello----%d",a);
};
只记住一点:只要没有访问auto变量的block 都是globalBlock
stackBlock
如果访问了 auto变量 就是 stackBlock
在ARC环境下,程序在运行的时候会自动对stackBlock进行copy处理 所以在ARC环境下打印出来的block类型就是 mallocBlock
原因:stackBlock 是在栈区,栈区会自动销毁内存,如果垮函数去调用block
此时block可能已经被释放了 如果再去调用这个block 可能访问的就是一块垃圾内存,
为了解决这个问题,
mallocBlock
stackBlock 调用copy 就会变成mallocBlock
在ARC环境下,程序在运行的时候会自动对stackBlock进行copy处理
在开发过程中我们使用的block一般都是 mallocBlock
每一种类型的block调用copy后的结果如下所示
block调用copy后的结果.png4、block的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
block作为函数返回值时
将block赋值给__strong指针时
block作为Cocoa API中方法名含有usingBlock的方法参数时
block作为GCD API的方法参数时
5、__block修饰符
__block可以用于解决block内部无法修改auto变量值的问题
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象(如下图最后一张)
使用__block修饰的变量底层代码结构图.png
图解:
编译器在编译的时候会讲__block变量包装成一个对象
block内部会有一个指针 __Block_byref_age_0 *age
(如:第二个图)指向生成的对象(底层为一个结构体如最后一个图:__block变量生成的对象底层代码)
__block变量生成的对象 里面又有一个指针存放age的值(第三个图中 val)
同时内部又有一个指针指向他自己(第三个图 __forwarding)
修改方式
先看底层C++代码
block底层c++代码.png
图解:
修改age值的步骤:
1、先通过block内部的 __Block_byref_age_0 *age指针找到
__block变量生成对象(底层为结构体)的__forwarding指针(这个指针就是指向他自己)。
2、然后在结构体中找这个age。
3、最后再对其进行赋值操作。
block内部如何修改变量的值
这个问题一般问的是修改局部变量的值,因为全局变量直接可以访问到,不存在修改不了的情况
如何修改局部变量的值:
第一种:直接使用 static修饰
第二种:局部变量改为全局变量
第三种:使用__block修饰变量
最好的方式就是第三种,使用__block修饰变量 因为前两种 都是将变量永久存放在内存中。
注意:只要不对变量/对象 的值进行修改 就不要去使用__block
如下面的代码就不需要使用__block 因为他们只是使用了变量/对象而没有对其进行修改
#import <Foundation/Foundation.h>
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
//第一种:
int age = 10;
Block block1 = ^{
NSLog(@"age is %d", age);
};
block1();
//第二种
NSMutableArray *arr = [[NSMutableArray alloc]init];
Block block2 = ^{
[arr addObject:@"444"];
[arr addObject:@"555"];
[arr addObject:@"666"];
NSLog(@"arr is %@", arr);
};
block2();
}
return 0;
}
6、block循环引用
有三种方式:__weak 、 __unsafe_unretained 和 __block
区别:
__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
__unsafe_unretained:不会产生强引用,不安全,
指向的对象销毁时,指针存储的地址值不变
所以:经常使用__weak来解决循环引用问题
使用__block解决block内部循环引用问题.png
网友评论