block在objectC中是一个匿名函数。
这就是一个block
^{}
有参数列表的block
^(void){}
有参数列表和返回值的block
^void (void){}
block变量的声明
int (^block)(NSString *a, NSString *b);
这样就定义了一个block变量,它的返回值为int类型,入参为两个字符串类型。
可以对它赋值和执行。
block = ^(NSString *a, NSString *b){
return 1;
};
int a = block();
使用typedef会使代码更简洁
typedef int (^Block)(NSString *a,NSString *b);
Block block = ^(NSString *a, NSString *b){
return 1;
};
作为对象属性
@property (nonatomic, copy) void (^block)(NSString *a,NSString *b);
// or
@property (nonatomic, copy) Block block;
作为函数的参数
- (void)func:(int (^)(NSString *a, NSString *b))block;
// or
- (void)func:(Block)block;
自动变量
自动变量是指block对其上下文变量的自动捕获
int n = 1;
Block block1 = ^(NSString *a, NSString *b){
n += 1;
return n;
};
变量n被block自动捕获,并能在block内部被访问。
这里的n就是自动变量。
注意,上面代码中,block内部的变量n是外面声明n的一份值的拷贝。block内部对n进行赋值,不会改变外部n的值。
下面是block对变量的地址捕获
static int n = 1;
Block block2 = ^(NSString *a, NSString *b){
n += 1;
return n;
};
当将变量n修饰为静态变量时,block捕获的是变量的地址。
block内部修改n的值,外部n的值也会发生变化。
捕获的变量类型是对象
Person *p = [Person alloc] init];
p.age = 10;
Block block2 = ^(NSString *a, NSString *b){
p.age = 30;
return p.age;
};
自动变量是对象,也是地址捕获,block内部对对象的属性修改后,外部也会生效。
还有一种全局变量,block内部可以直接访问,且不会捕获。
int number = 30;
int main(){
Block block3 = ^(NSString *a, NSString *b){
number = 40;
return number;
};
}
以上是三种block:
block1 是NSStackBlock,其存储位置在栈上。
block2 是NSMallocBlock,其存储位置在堆上。
block3 是NSGlobalBlock,其存储位置在数据区。
这三种block都继承自NSBlock。
循环引用问题
// Persson.h
@property (nonatomic, assign) int age;
@property (nonatomic, copy) Block block;
// main.m
Person *p = [Person alloc] init];
p.age = 10;
p.block = ^(NSString *a, NSString *b){
p.age = 30;
return p.age;
};
在Person前加__weak修饰符,解除循环引用
__weak Person *p = [Person alloc] init];
__block的原理
上面说到的block1无法修改外部变量n的值。
如果想要修改,就在变量前加 __block修饰符。
__block int n = 1;
Block block1 = ^(NSString *a, NSString *b){
n += 1;
return n;
};
block1();
NSLog(@"n=%d",n); // 2
__block 将变量n包装成一个结构体,结构如下
struct __Block_byref_n_0 {
void *__isa;
__Block_byref_n_0 *__forwarding; // 指向自身的指针
int __flags;
int __size;
int n; // 实际值
};
__forwarding指针指向自身。
block的内部实际是通过__forwarding找到外部的n并修改其值的。
接下来模拟分析上述代码执行时,n变量在内存中的情况
__block int n = 1;
图1.png
变量n被转换成结构体,我们假设结构体变量名字为Block_n
,它的地址为0x00000000。
然后执行到下面一步
block1();
图2.png
变量n被block捕获,此时复制的是结构的值。我们假设名称为Block_n'。
注意,此时Block_n'中的__forwarding指向的是Block_n,也就是外部变量的地址。
最后执行
n += 1;
图 3.png
外部变量n的值就被修改了。
以上内容是我对block的一点理解,如有不对之处,望能不吝斧正。
网友评论