小白篇——基本用法
block声明——格式
返回类型(^名字)(参数列表)
void(^blockName)(int a,bool b,NSString *c)
block表达式——格式
^返回类型(参数列表)
^(int)(int a)
block作变量
void (^block)(int a) = ^(int b) {
NSLog(@"%d---变量",b);
};
block(123);
执行完block(123)后打印123---变量
block作函数参数
//参数block的声明
- (void)function:(int(^)(NSString *str))paramBlock {
int i = paramBlock(@"function");
NSLog(@"%d",i);
}
//调用
[self function:^int(NSString *str) {
NSLog(@"%@---参数",str);
return 123;
}];
打印顺序:function---参数,123
使用typedef声明block
typedef int(^typeBlock)(int c);
//函数声明
- (void)function2:(typeBlock)paramBlock {
int i = paramBlock(1);
NSLog(@"%d",i);
}
//函数调用
[self function2:^int(int c) {
NSLog(@"%d---typedef参数",c);
return c++;
}];
打印顺序 1---typedef参数,2
block作为函数返回值
//函数声明
(int(^)(int a))function3 {
return ^(int count) {
NSLog(@"%d",i);
return count = 0;
};
}
//调用
int(^retBlock)(int a) = [self function3];
//用作fuc2的参数
[self function2:retBlock];
打印顺序 1,0
block与外部变量
在使用block中很重要的一点就是可以通过block获取上文。即block的外部变量可以放在block内部使用。
临时变量
int num = 10;
void(^block1)() = ^() {
NSLog(@"%d",num);
};
num = 20;
block1();
打印结果为10而不是20。从这里我们可以看出block在声明时是将num
拷贝了一份到block内。外面num
无论怎么变化也不会影响到内部。
全局变量和__block
__block int num = 10;
void(^block1)() = ^() {
NSLog(@"%d",num);
};
num = 20;
block1();
int num;
- (void)function {
num = 10;
void(^block)() = ^() {
NSLog(@"%d",num);
};
num = 20;
block();
}
打印结果均为20。那么为什么全局变量拷贝到block内依然可以修改呢?
我们可以猜想,局部变量是存储在栈上,而全局变量是存储在堆上。block拷贝变量是将num
拷贝到堆上。所以对于本来就在堆上的num
,block可以直接使用。
先不说对不对,好像这个猜想挺靠谱的。和我们经常听到的
栈上的block会自动copy到堆上
block截取的_block变量也会同时copy到堆上
说法貌似是一致的。
深入了解block
一个最简单的block
typedef int (^Block)(void);
int main(int argc, char * argv[]) {
// block实现
Block block = ^{
return 0;
};
// block调用
block();
return 0;
}
使用clang工具翻译代码后,这个是整体的,拆分在下面
// block内部结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
typedef int (*Block)(void);
// block结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block方法实现
static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
return 0;
}
// block内部结构体
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
// main函数
int main(int argc, char * argv[]) {
// block实现
Block block = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// block调用
((int (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
block内部结构
impl结构体
struct __block_impl {
void *isa; // 存储位置,_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock
int Flags; // 按位承载 block 的附加信息
int Reserved; // 保留变量
void *FuncPtr; // 函数指针,指向 Block 要执行的函数,即__main_block_func_0
};
1.isa 指针,所有对象都有该指针,用于实现对象相关的功能。
这里block有3种类型。具体有什么不同下面再说。
impl.isa = &_NSConcreteStackBlock 全局的静态 block,不会访问任何外部变量。;
impl.isa = &_NSConcreteMallocBlock 保存在栈中的 block,当函数返回时会被销毁;
impl.isa = &_NSConcreteGlobalBlock 保存在堆中的 block,当引用计数为 0 时会被销毁;
2.flags,用于按 bit 位表示一些 block 的附加信息
3.reserved,保留变量。
4. funcPtr,函数指针,指向具体的 block 实现的函数调用地址。
block结构体
struct __main_block_impl_0 {
// impl结构体,即block的主结构体
struct __block_impl impl;
// Desc结构体,即block的描述结构体
struct __main_block_desc_0* Desc;
// block结构体的构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
impl :block 实现的结构体变量,该结构体就是前面说的那个;
desc : 描述 block 的结构体变量,主要是 size 大小,以及 copy 和 dispose 函数的指针
__main_block_impl_0 :结构体的构造函数,初始化结构体变量 impl、Desc;
从这里就看出block至少是将变量,函数地址通过构造函数都保存到了结构体内部了。
回头再看block截获变量
我们截获变量的类型基本是以下几种
1.全局变量 2.全局静态变量 3.局部静态变量 4.局部变量 5._block修饰的变量
其中1.2属于一类,作用域是全局,block可以直接使用它们。而第3.4种的作用域在block外面。block想使用它就需要进行拷贝。然后通过clang工具翻译后。
typedef int (^Block)(void);
int a = 0;
static int b = 0;
int main(int argc, char * argv[]) {
static int c = 0;
int i = 0;
NSMutableArray *arr = [NSMutableArray array];
Block block = ^{
a = 1;
b = 1;
c = 1;
[arr addObject:@"1"];
return i;
};
block();
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *c;
NSMutableArray *arr;
int i;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_c, NSMutableArray *_arr, int _i, int flags=0) : c(_c), arr(_arr), i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block实现的方法
static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *c = __cself->c; // bound by copy
NSMutableArray *arr = __cself->arr; // bound by copy
int i = __cself->i; // bound by copy
a = 1;
b = 1;
(*c) = 1;
((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)arr, sel_registerName("addObject:"), (id)(NSString *)&__NSConstantStringImpl__var_folders_k__6b9p9yt96y9dq8ds8_kvf3kh0000gn_T_main_3c9752_mi_0);
return i;
}
我们发现静态变量传入的是指针。可以直接修改。
而局部变量拷贝进去是const类型的,无法被修改。
于是如果我们希望在block内部改变外部变量就需要用到__block这个关键字。
__block
block源码
int main()
{
__block int intValue = 0;
void (^blk)(void) = ^{
intValue = 1;
};
return 0;
}
clang后
struct __block_impl
{
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_intValue_0
{
void *__isa;
__Block_byref_intValue_0 *__forwarding;
int __flags;
int __size;
int intValue;
};
struct __main_block_impl_0
{
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_intValue_0 *intValue; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_intValue_0 *_intValue, int flags=0) : intValue(_intValue->__forwarding)
{
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
__Block_byref_intValue_0 *intValue = __cself->intValue; // bound by ref
(intValue->__forwarding->intValue) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src)
{
_Block_object_assign((void*)&dst->intValue, (void*)src->intValue, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0 *src)
{
_Block_object_dispose((void*)src->intValue, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __main_block_desc_0
{
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
int main()
{
__attribute__((__blocks__(byref))) __Block_byref_intValue_0 \
intValue =
{
(void*)0,
(__Block_byref_intValue_0 *)&intValue,
0,
sizeof(__Block_byref_intValue_0),
0
};
void (*blk)(void) = (void (*)()) &__main_block_impl_0 \
(
(void *)__main_block_func_0, \
&__main_block_desc_0_DATA, \
(__Block_byref_intValue_0 *)&intValue, \
570425344 \
);
return 0;
}
当block截获的变量为__block 修饰的变量时,多发生了下面几件事。
__Block_byref_intValue_0 结构体:用于封装 __block 修饰的外部变量。 _Block_object_assign 函数:当 block 从栈拷贝到堆时,调用此函数。 _Block_object_dispose 函数:当 block 从堆内存释放时,调用此函数。
__block修饰的变量被拷贝到了堆上并且变成通过指针传入block内部的。
然后就是block结构体中多出了一个_forwarding指针。并且_forwarding指针指向外部变量自己。
于是block结构体中的__Block_byref_intValue_0 *intValue和变量结构体中的__Block_byref_intValue_0 *__forwarding都指向了外部变量。
这特么是不是冲突了????为了说明这个有啥用,现在开始讲block的内存管理。
block内存管理
吐槽##学到这真的好累啊。。。平时用惯了的block背后还有那么多事儿。不查不知道,一查吓一跳
在前面,提到过了block的类型有三种。NSConcreteGlobalBlock
、_NSConcreteStackBlock
、_NSConcreteMallocBlock
这三种block在内存中的分布如下:
_NSConcreteGlobalBlock
1、当 block 字面量写在全局作用域时,即为 global block;
2、当 block 字面量不获取任何外部变量时,即为 global block
除了这两种方式以外,其他形式创建的block均为stack block
创建一个简单的_NSConcreteGlobalBlock
int main()
{
^{ printf("Hello, World!\n"); } ();
return 0;
}
clang翻译后
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello, World!\n");
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };
int main()
{
(void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA) ();
return 0;
}
由于这个block没有引入外部变量,所以它的类型为_NSConcreteGlobalBlock。
但clang后显示为_NSConcreteStackBlock,这里查资料原因是clang改写的实现方式和LLVM不一样,在LLVM中,开启ARC时,block应该是_NSConcreteGlobalBlock
_NSConcreteStackBlock
_NSConcreteStackBlock
类型的 block 处于内存的栈区,如果其变量作用域结束,这个 block 就被废弃,block 上的 __block 变量也同样会被废弃。
所以,为了解决这个问题,block提供了copy功能,将 block 和 __block 变量从栈拷贝到堆。此时block类型变为_NSConcreteMallocBlock
此时impl.isa = &_NSConcreteMallocBlock;
再看这个构造函数!
block访问的永远是__forwarding
指向的那块内存。所以进行 copy 操作时,会将栈上的 __forwarding
指针指向了堆上的 block 结构体实例
小结:当block执行了copy以后,block的类型变为NSConcreteMallocBlock
参考代码:
int main(int argc, const char * argv[])
{
@autoreleasepool {
int i = 1024;
void (^block1)(void) = ^{
printf("%d\n", i);
};
block1();
NSLog(@"%@", block1);
}
return 0;
}
会输出 <__NSMallocBlock__: 0x100109960>
block的自动拷贝与手动拷贝
在ARC情况下,基本都会讲创建在栈上的block拷贝到堆上。除了以下这种情况:
block 作为方法或函数的参数传递时,编译器不会自动调用 copy 方法;
例子如下:(来自狮子书)
- (id)getBlockArray
{
int val = 10;
return [[NSArray alloc] initWithObjects:
^{NSLog(@"blk0:%d", val);},
^{NSLog(@"blk1:%d", val);}, nil];
}
调用时
id arr = getBlockArray();
Block block = [arr objectAtIndex:1];//此处crash
block();
crash的原因就是block出作用域后已经被回收了,正好验证了作为参数时不进行自动拷贝的说法。