block本质上也是一个OC对象,它内部也有一个isa指针
将block内部代码会放到_block_func_0函数中,函数地址保存在FuncPtr中
执行block内部代码时是通过FuncPtr找到函数地址进行调用
block的底层结构如下:
int main(int argc, char * argv[]) {
void (^test)() = ^(){
};
test();
}
//__block_imp: 这个是编译器给我们生成的结构体,每一个block都会用到这个结构体
struct __block_impl {
void *isa; //对于本文可以忽略
int Flags; //对于本文可以忽略
int Reserved; //对于本文可以忽略
void *FuncPtr; //函数指针,这个会指向编译器给我们生成的下面的静态函数__main_block_func_0
};
/*__main_block_impl_0:
是编译器给我们在main函数中定义的block
void (^test)() = ^(){
};
生成的对应的结构体
*/
struct __main_block_impl_0 {
struct __block_impl impl; //__block_impl 变量impl
//__main_block_desc_0 指针,指向编译器给我们生成的结构体变量__main_block_desc_0_DATA
struct __main_block_desc_0* Desc;
//结构体的构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; //说明block是栈blockimpl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;}
};
//__main_block_func_0: 编译器根据block代码生成的全局态函数,会被赋值给impl.FuncPtr
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}
//__main_block_desc_0: 编译器根据block代码生成的block描述,
//主要是记录下__main_block_impl_0结构体大小
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
//这里就生成了__main_block_desc_0的变量__main_block_desc_0_DATA
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
//这里就是main函数了
int main(int argc, char * argv[]) {
//__main_block_impl_0()创建了一个__main_block_impl_0结构体的一个实例
//&取地址
//((void (*)())转成函数
//那么这整句就是说定义一个函数指针指向一个新创建的__main_block_impl_0实例的地址。注意创建这个实例时构选函数传的两个参数,
//正是编译器帮我们生成的静态函数__main_block_func_0及__main_block_desc_0的变量__main_block_desc_0_DATA
void (*test)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test); //下面单独讲
}
当我们声明一个block变量a并为它赋值时,其实就是创建一个函数指针FuncPtr,再根据block a赋值的代码生成一个静态函数,而指针FuncPtr就指向这个静态函数。block a调用时就是使用函数指FuncPtr调用生成的静态函数。
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
基本变量类型
1、block内部访问auto变量时会将auto变量捕获到block内部,block外部修改auto变量的值并不会影响block内部,所以是值捕获 如下 age变量
2、block访问static变量时会将static变量捕获到block内部,block外部修改static变量的值会影响block内部,所以是指针捕获 如下 height变量
/*auto:自动变量,离开作用域就会销毁。
它是默认的关键字,所以通常情况下是省略的。*/
auto int age = 10;
static int height = 10;
void (^block)(void) = ^{
NSLog(@"age is %d, height is %d", age, height);
};
age = 20;
height = 20;
block();
3、block访问全局变量时不会将全局变量捕获到block内部,而是直接访问。
总结:局部变量都会被block捕获,自动变量是值捕获,静态变量为地址捕获。全局变量则不会被block捕获
注意:对象的局部auto变量捕获是指针捕获不是值捕获,所以捕获的内容会受外部变量的影响
Block有三种类型:
都继承于NSBlock
1、NSGlobalBlock:存放在数据段
没有访问auto类型变量,但是可以访问静态变量或者全局变量。它存储在数据区。
NSGlobalBlock调用copy还是NSGlobalBlock
//MRC下 没有访问auto变量
void (^block)(void) = ^ {
NSLog(@"Hello world!");
};
NSLog(@"%@", [block class]); // __NSGlobalBlock__
NSLog(@"%@", [[block class] superclass]); // NSBlock
NSLog(@"%@", [[[block class] superclass] superclass]); // NSObject
2、NSStackBlock:存放在栈区
访问auto类型变量的block类型。它存储在栈区。
PS:栈区的数据在调用之后会自动销毁。
NSStackBlock调用copy,会从栈复制到堆。ARC下NSStackBlock当返回值
//MRC下
// 访问了auto变量
int num = 10;
void (^block)(void) = ^ {
NSLog(@"num = %d", num);
};
NSLog(@"%@", [block class]); // __NSStackBlock__
//ARC下
- (void)viewDidLoad {
[super viewDidLoad];
[self test:^{
NSLog(@"%@",self.object);
}];
}
- (void)test:(void(^)(void))block {
NSLog(@"%@",block); //log: <__NSStackBlock__: 0x7fff5ef49b88>
}
3、NSMallocBlock:存放在堆区
NSMallocBlock调用copy,引用计数增加。对stack类型的block进行copy操作,得出的堆类型的block变量。堆数据需要手动管理内存,需要手动释放。
// NSStackBlock调用了copy
int num = 10;
void (^block)(void) = [^ {
NSLog(@"num = %d", num);
} copy];
NSLog(@"%@", [block class]); // __NSMallocBlock__
4、block的自动copy
在ARC环境下,编译器会根据情况自动将__NSStackBlock转成__NSMallocBlock,比如以下情况:
1、__NSStackBlock作为函数的返回值时
typedef void (^TestBlock)(void);
TestBlock globalBlock(){
return ^{
NSLog(@"Hello, globalBlock!");
};
}
TestBlock mallocBlock(){
int value = 100;
return ^{
NSLog(@"Hello, mallocBlock!%d",value);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestBlock block1 =globalBlock();
TestBlock block2 =mallocBlock();
NSLog(@"%@",block1);//日志:<__NSGlobalBlock__: 0x100004038>
NSLog(@"%@",block2);//日志:<__NSMallocBlock__: 0x100650910>
block1();//日志:Hello, globalBlock!
block2();//日志:Hello, mallocBlock!100
}
return 0;
}
2、将block赋值给__strong指针时
__weak:弱引用变量修饰词,引用计数不会+1,本身可以避免循环引用的问题,但是其会导致外部对象释放了之后,Block内部也访问不到这个对象的问题,我们可以通过在Block内部申明一个__strong的变量来指向weakObj,使外部对象既能在Block内部保持住,又能避免循环引用的问题。
3、block作为Cocoa API中方法名含有usingBlock的方法参数时,例如
NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj,
NSUInteger idx, BOOL * _Nonnull stop) {
}];
4、block作为GCD API的方法参数时,例如:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
ARC环境下:person
为aotu
变量,传入的block
的变量同样为person
,即block
有一个强引用引用person
,所以block
销毁后,peroson才
会销毁。
//ARC环境
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"------block内部%d",person.age);//对象变量捕获是指针捕获
};//栈区自动copy到堆区,强引用
} // 执行完毕,person没有被释放
NSLog(@"--------");
} // person 释放
return 0;
}
//MRC环境下代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"------block内部%d",person.age);
};//栈区
[person release];
} // person被释放
NSLog(@"--------");
}
return 0;
}
1、__block可以用于解决block内部无法修改auto变量值的问题
编译器会将__block修饰的基本变量类型,包装成一个对象。
block对基本变量类型 捕获就变成了对象变量的捕获,就是指针捕获。之后block内就能修改auto变量值了。
2、block中的循环引用
//self强引用block
self.block = ^{
NSLog(@"%ld",self.age);
};//栈区自动copy堆区,blcok强引用self对象。
打破任何一方的强引用,就能解决循环引用
__weak typeof(self) weakSelf = self;
self.quoteBlock = ^{
NSLog(@"%ld",weakSelf.age);
};
在self对象前加了个__weak修饰,block内部就不会对self产生强引用,当block被释放时__weak会自动为nil,不需要block再去释放self了。
1、self释放时通知quoteBlock释放
2、quoteBlock因为没有强引用self,所以无需通知self并等待self先释放,quoteBlock接收到释放通知后就立马释放
3、quoteBlock释放后,self也跟着释放,所以就解决了循环引用的问题