什么是block
- block 实际上就是 Objective-C 语言对于闭包的实现。
- 闭包:闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称作自由变量)。
- ______________________________________________________________________________________________________________________________
Block的几种形式
- 分为全局Block(_NSConcreteGlobalBlock)、栈Block(_NSConcreteStackBlock)、堆Block(_NSConcreteMallocBlock)三种形式
其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区
1、不使用外部变量的block是全局block
比如:
NSLog(@"%@",[^{ NSLog(@"globalBlock"); } class]);输出:
__NSGlobalBlock__2、使用外部变量并且未进行copy操作的block是栈block
比如:
NSInteger num = 10; NSLog(@"%@",[^{ NSLog(@"stackBlock:%zd",num); } class]);输出:
__NSStackBlock__日常开发常用于这种情况:
[self testWithBlock:^{ NSLog(@"%@",self); }]; - (void)testWithBlock:(dispatch_block_t)block { block(); NSLog(@"%@",[block class]); }3、对栈block进行copy操作,就是堆block,而对全局block进行copy,仍是全局block
- 比如堆1中的全局进行copy操作,即赋值:
void (^globalBlock)(void) = ^{ NSLog(@"globalBlock"); }; NSLog(@"%@",[globalBlock class]);输出:
__NSGlobalBlock__仍是全局block
- 而对2中的栈block进行赋值操作:
NSInteger num = 10; void (^mallocBlock)(void) = ^{ NSLog(@"stackBlock:%zd",num); }; NSLog(@"%@",[mallocBlock class]);输出:
__NSMallocBlock__对栈blockcopy之后,并不代表着栈block就消失了,左边的mallock是堆block,右边被copy的仍是栈block
比如:[self testWithBlock:^{ NSLog(@"%@",self); }]; - (void)testWithBlock:(dispatch_block_t)block { block(); dispatch_block_t tempBlock = block; NSLog(@"%@,%@",[block class],[tempBlock class]); }输出:
__NSStackBlock__,__NSMallocBlock__- 即如果对栈Block进行copy,将会copy到堆区,对堆Block进行copy,将会增加引用计数,对全局Block进行copy,因为是已经初始化的,所以什么也不做。
_NSConcreteGlobalBlock:全局的静态block,不会访问任何外部变量,不会涉及到任何拷贝,比如一个空的block。例如:
int main() { ^{ printf("Hello, World!\n"); } (); return 0; }_NSConcreteStackBlock:保存在栈中的block,当函数返回时被销毁。例如:
_NSConcreteMallocBlock:保存在堆中的block,当引用计数为0时被销毁{ char a = 'A'; ^{ printf("%c\n",a); } (); }//1 block的第一种形式 NSGlobalBlock 全局block 根函数一样 存储在代码区 // void (^myblock)() = ^{ // NSLog(@"hello"); // }; // // NSLog(@"%@",myblock); //block的本质是结构体 //2 block的第二种形式 NSStackBlock 栈block 存储在栈上 // __block int n = 5; // void (^myblock)() = ^{ // n = 6; // NSLog(@"hello"); // NSLog(@"%d",n); // }; // // NSLog(@"%@",myblock); //3 block的第三种形式 NSMallocBlock 堆blcok 存储在堆上 对栈block做一次copy操作 __block int n = 5; void (^myblock)() = ^{ n = 6; NSLog(@"hello"); NSLog(@"%d",n); }; NSLog(@"%@",[myblock copy]); - 分为全局Block(_NSConcreteGlobalBlock)、栈Block(_NSConcreteStackBlock)、堆Block(_NSConcreteMallocBlock)三种形式
block的基本使用
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
- block的底层结构如下图
typedef 定义Block
实际开发中,经常需要把block作为一个属性,我们可以定义一个block
eg:定义一个有参有返回值的block
typedef int (^MyBlock)(int a, int b);定义属性的时候,如下即可持有这个block
@property (nonatomic,copy) MyBlock myBlockOne;
block实现
self.myBlockOne = ^int(int a, int b) {
return a + b;
};
调用
self.myBlockOne(2, 5);
block 结构分析
如下代码
int age = 20;
void (^block)(void) = ^{
NSLog(@"age is %d",age);
};
block();- 打开终端,cd到main.h目录下
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
生成main.cpp
int age = 20;
// block的定义
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
// block的调用
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);上面的代码删除掉一些强制转换的代码就就剩下如下所示
int age = 20;
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
age
);
// block的调用
block->FuncPtr(block);
复制代码看出block的本质就是一个结构体对象,结构体__main_block_impl_0代码如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
//构造函数(类似于OC中的init方法) _age是外面传入的
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
//isa指向_NSConcreteStackBlock 说明这个block就是_NSConcreteStackBlock类型的
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
图片表示
block类型
block也是一个OC对象
在进行分析block类型之前,先明确一个概念,那就是block中有isa指针的,block是一个OC对象,例如下面的代码
void (^block)(void) = ^{
NSLog(@"123");
};
NSLog(@"block.class = %@",[block class]);
NSLog(@"block.class.superclass = %@",[[block class] superclass]);
NSLog(@"block.class.superclass.superclass = %@",[[[block class] superclass] superclass]);
NSLog(@"block.class.superclass.superclass.superclass = %@",[[[[block class] superclass] superclass] superclass]);
复制代码输出结果为
iOS-block[18429:234959] block.class = __NSGlobalBlock__
iOS-block[18429:234959] block.class.superclass = __NSGlobalBlock
iOS-block[18429:234959] block.class.superclass.superclass = NSBlock
iOS-block[18429:234959] block.class.superclass.superclass.superclass = NSObject
复制代码说明了上面代码中的block的类型是__NSGlobalBlock,继承关系可以表示为__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
block有3种类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )__NSStackBlock__ ( _NSConcreteStackBlock )__NSMallocBlock__ ( _NSConcreteMallocBlock )
其中三种不同的类型和环境对应如下
| block类型 | 环境 |
|---|---|
__NSGlobalBlock__ |
没有访问auto变量 |
__NSStackBlock__ |
访问了auto变量 |
__NSMallocBlock__ |
__NSStackBlock__调用了copy |
其在内存中的分配如下对应
每一种block copy后的
_NSConcreteMallocBlock 存储在堆 copy 后 引用计数加1
_NSConcreteGlobalBlock 存储在数据存储data区 copy后什么也不做
这三个
int age = 10;
int main (int argc, const char * argv[]){
@autoreleasepool {
int a = 100;
NSLog(@"数据段(全局区):age %p", &age);
NSLog(@"栈: %p", &a);
NSLog(@"堆: %p", [[NSObject alloc] init]);
NSLog(@"类对象的内存 class在数据段: %p", [Person class]);
}}ARC环境下,编译器会根据如下情况 自动将栈上的Block复制到堆上:
1. block 作为函数返回值时
2. 将block 赋值给strong指针时
3. BLock作为cocoa api 中方法名含有Usingblock的方法参数时
4. block作为GCD api的方法参数时
ARC下 block 属性建议写法
@property(copy, nonatomac) void(^block) (void);