什么是block
1.block本身也是一个OC对象,它里面也有isa
指针
2.block是封装了函数调用(存储函数调用地址,函数访问变量)和函数调用环境的OC对象
3.block的底层结构如下图所示:
证明:
我们在main中写入一个block:
// 带参数的block
int c = 15;
void (^block)(int , int) = ^(int a ,int b ){
NSLog(@"%d",c);
};
block(10, 10);
走到项目main.m的目录下通过反编译:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o mian.cpp
得到main.cpp
,拉入工程,找到这个block对象的底层结构:
实际上block
在底层对应的就是__main_block_impl_1
,搜索__main_block_impl_1
:
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
int c;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _c, int flags=0) : c(_c) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们发现,里面存储着__block_impl
的结构体impl
,以及__main_block_desc_1
的结构体指针Desc
搜索对象的内容我们可以找到:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
这里可以看到__main_block_desc_1
包含着isa
指针,以及FuncPtr
,FuncPtr
就是block的调用地址,是在声明block的时候初始化传递进来的,以及Block_size
为block的内存大小,还有int c
,也封装到了Block
内部,我们知道OC
对象的特征就是isa
指针,所以,block就是封装了函数调用、以及函数调用环境的OC对象
我们用一张图来总结这个结构:
9AA000FA-1451-4F43-8675-3693D02CD85A.png
Capture (捕获)
对于局部变量:值传递,Block只是把局部变量的值捕获存储在了block的结构体内 存储
int c = 10;
void (^block)(void) = ^{
NSLog(@"%d",c);
};
c = 20;
block(); // 输出10
对于Static :指针传递,Block把static 的变量的指针存储在block的结构体内,所以取值的话就是取对应最后的赋值
static int c = 10;
void (^block)(void) = ^{
NSLog(@"%d",c);
};
c = 20;
block(); // 输出20
全局变量:直接访问
static int a = 20;
int b = 15;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"%d",a);
NSLog(@"%d",b);
};
a = 25;
b = 10;
block(); // 输出 25 10
}
return 0;
}
block 的类型
block分为3中类型,但是最终都是继承自NSObject
__NSGlobalBlock__
: block内部没有访问auto
变量:
// Global - 没有访问auto变量
void (^block1)(void) = ^{
NSLog(@"-----");
};
NSLog(@"%@",[block1 class]);
__NSStackBlock__
: block内部访问了auto变量(存放在栈):
// stack - 访问了auto变量
int c = 2;
void (^block2)(void) = ^{
NSLog(@"-----%d",c);
};
NSLog(@"%@",[block2 class]);
Tip:stack 需要在非arc下才可以看出结果
在
Building Setting
暂时关掉arc
__NSMallocBlock__
: Stack Block调用了copy
// malloc - stack Block 调用了copy
int d = 2;
void (^block3)(void) = [^{
NSLog(@"-----%d",d);
} copy];
NSLog(@"%@",[block3 class]);
Tip:stack block存放在栈内存,如果block存放在函数内,一旦函数作用域结束,则block内容则会被清除,如果存放在堆内存(调用copy),就会变成malloc block,则不会自动清除,这也是为什么block需要用copy
修饰的原因
用一幅图来总结block的类型 :
F92EC499-0EA3-46C1-B421-096ADC7C1A05.png
__block
block内部默认是无法修改auto
变量的,因为在block底部的话执行block、声明age(main函数)的地方分别是2个不同的函数,并没办法从一个函数去修改另一个函数的局部变量,而如果使用static
或者使用全局变量
是可以的,因为block
在底层存储static
变量是存储它的指针地址,全局变量
就全部都可以访问
如果要修改auto
变量的话,则需要使用__block
:
__block int a = 10; // __block 修饰auto变量
void (^block)(void) = ^{
a = 20;
NSLog(@"%d",a);
};
block();
__block
实现原理:当使用__block
修饰auto
变量时,编译器会把a
变成一个__Block_byref_a_0
对象:
我们可以看到__Block_byref_a_0
它包含了__isa
,a
,以及__forwarding
(指向自己的指针),以及其他信息,它就是一个对象,而我们再看main
函数声明block的地方
这里
__block
声明就是声明了一个__Block_byref_a_0
的对象,并把&a
传递给了__forwarding
,10
传递给了a
,最后我们在看执行block的地方:DD0BCCD4-AC01-46BA-A5D4-CCDB1D05DABF.png
这里,执行
block
时,取出了__Block_byref_a_0
所存储的&a
(指向__Block_byref_a_0
的指针地址 __forwarding),再取出a,最后进行修改/使用,用一张图来总结__block
的底层结构:A249145A-816D-4314-AF79-ECA46794B49C.png
循环引用
首先,我们声明一个Person
对象,在Person
上声明一个属性block
:
接着调用:
1EA42152-332A-4814-BCCB-1B85D1A94514.png
此便为循环引用,首先这里我们创建一个
Person
对象,并用*p
指针指向它,而我们在block
内部又使用了p,则在底层block
会捕获p对象,并用强指针指向它,而我们定义的Person
对象,又定义了属性block
,属性block
则会自动生成成员变量_block
,而我们执行p.block = xxx
时,又将这个{}block
赋值给了block
,这样就造成了双方互相强引用,导致内存不会释放:8BBE5395-CAE3-4676-ACB1-0D1C03F70CE6.png
解决循环引用: 使用__weak
或者__unsafe_unretained
修饰变量(block内的person指针指向Person对象时为弱引用)
区别:
__weak
,在Person
对象回收掉后,block
内指向Person
对象的指针person
则会自动置为nil
,即:person = nil
__unsafe_unretained
,在Person
对象回收掉后,block
内指向Person
对象的指针person
还会指向原来Person
对象的内存地址,即野指针
另外还有一种解决方案作为了解:
使用__block
配合person = nil
以及必须调用block
网友评论