iOS Block浅谈
一.Block的本质
block本质是一个OC对象,它里面有个isa指针,封装了函数调用环境的OC对象,封装了函数调用上下文的OC对象。
Block底层结构图
查看Block源码:
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;
// 构造函数(类似于OC的init方法),返回结构体对象
main_block_impl_0(void*fp,structmain_block_desc_0 *desc,intflags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 封装了block执行逻辑的函数
static void main_block_func_0(struct main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_0);
}
static struct __main_block_desc_0 {
size_treserved;
size_tBlock_size;
} main_block_desc_0_DATA = { 0, sizeof(struct main_block_impl_0)};
int main(intargc,constchar* argv[]) {
/* @autoreleasepool */{__AtAutoreleasePool__autoreleasepool;
// 定义block变量
void(*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA
);
// 执行block内部的代码
block->FuncPtr(block);
}
return0;
}
说明:FuncPtr:指向调用函数的地址,__main_block_desc_0 :block描述信息,Block_size:block的大小
二.Block变量的捕获
2.1局部变量的捕获
对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量, 不使用则不截获, 因为截获的自动变量会存储于block的结构体内部, 会导致block体积变大。特别要注意的是默认情况下block只能访问不能修改局部变量的值。
int age=10;
void(^Block)(void)=^{
NSLog(@”age:%d”,age);
};
age=20;
Block();
2.2__block 修饰的外部变量
对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。block可以修改__block 修饰的外部变量的值
__block int age=10;
myBlock block=^{
NSLog(@”age = %d”,age);
};
age=18;
block();
输出:18;
auto int age=10;
static int num=25;
void(^Block)(void)=^{
NSLog(@”age:%d,num:%d”,age,num);
};
age=20;
num=11;
Block();
输出结果为:age:10,num:11,auto变量block访问方式是值传递,也就是当block定义的时候,值已经传到block里面了,static变量block访问方式是指针传递,auto自动变量可能会销毁的,内存可能会消失,不采用指针访问;static变量一直保存在内存中,指针访问即可,block不需要对全局变量捕获,都是直接采用取值的,局部变量的捕获是因为考虑作用域的问题,需要跨函数访问,就需要捕获,当出了作用域,局部变量已经被销毁,这时候如果block访问,就会出问题。
2.2.block变量捕获机制
block变量捕获机制
block里访问self,self是当调用block函数的参数,参数是局部变量,self指向调用者,所以它也会捕获self,block里访问成员,成员变量的访问其实是self->xx,先捕获self,再通过self访问里面的成员变量。
3.3Block的类型
block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
NSGlobalBlock ( _NSConcreteGlobalBlock )全局block即数据区
NSStackBlock ( _NSConcreteStackBlock )堆区block
NSMallocBlock ( _NSConcreteMallocBlock )栈区block
说明:堆区,程序员自己控制,程序员自己管理,栈区,系统自动控制,一般我们使用最多的是堆区Block,判断类型的根据是没有访问auto变量的block是__NSGlobalBlock __ ,放在数据段访问了auto变量的block是__NSStackBlock __;[__NSStackBlock __ copy]操作就变成了__NSMallocBlock __,__NSGlobalBlock __ 调用copy操作后,什么也不做__NSStackBlock __ 调用copy操作后,复制效果是:从栈复制到堆;副本存储位置是堆__NSMallocBlock __ 调用copy操作后,复制效果是:引用计数增加;副本存储位置是堆,在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上的几种情况是:
1.block作为函数返回值时
2.将block赋值给__strong指针时
3.block作为Cocoa API中方法名含有usingBlock的方法参数时
4.block作为GCD API的方法参数时
三.对象类型的auto变量
typedefvoid(^XBTBlock)(void);
XBTBlock block;
{
Person*p=[[Person alloc]init];
p.age=10;
block=^{
NSLog(@”======= %d”,p.age);
};}
Person.m
-(void)dealloc{
NSLog(@”Person - dealloc”);
}
说明:block为堆block,block里面有一个Person指针,Person指针指向Person对象。只要block还在,Person就还在。block强引用了Person对象。在MRC下,就会打印,因为堆空间的block会对Person对象retain操作,拥有一次Person对象。无论MRC还是ARC,栈空间上的block,不会持有对象;堆空间的block,会持有对象。
特别说明:block内部访问了对象类型的auto变量时,是否会强引用?
栈block
a) 如果block是在栈上,将不会对auto变量产生强引用
b) 栈上的block随时会被销毁,也没必要去强引用其他对象
堆block
1.如果block被拷贝到堆上:
a) 会调用block内部的copy函数
b) copy函数内部会调用_Block_object_assign函数
c) _Block_object_assign函数会根据auto变量的修饰符(strong、weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
2.如果block从堆上移除
a) 会调用block内部的dispose函数
b) dispose函数内部会调用_Block_object_dispose函数
c) _Block_object_dispose函数会自动释放引用的auto变量(release)
正确答案:
如果block在栈空间,不管外部变量是强引用还是弱引用,block都会弱引用访问对象
如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用
3.2gcd的block中引用 Person对象什么时候销毁?
eg:-(void)touchesBegan:(NSSet )toucheswithEvent:(UIEvent)event{
Person*person = [[Personalloc]init];
person.age=10;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"age:%d",person.age);
});
NSLog(@"touchesBegan");
}
输出:touchesBegan
age:10
Person-dealloc
说明:gcd的block默认会做copy操作,即dispatch_after的block是堆block,block会对Person强引用,block销毁时候Person才会被释放,如果上诉Person用__weak。即添加代码为__weak Person*weakPerson=person;,在Block中变成NSLog(@"age:%p",weakPerson);,它就不输出age,使用__weak修饰过后的对象,堆block会采用弱引用,无法延时Person的寿命,所以在touchesBegan函数结束后,Person就会被释放,gcd就无法捕捉到Person,gcd内部只要有强引用Person,Person就会等待执行完再销毁!如果gcd内部先强引用后弱引用,Person会等待强引用执行完毕后释放,只要强引用执行完,就不会等待后执行的弱引用,会直接释放的
eg:-(void)touchesBegan:(NSSet )toucheswithEvent:(UIEvent)event{
Person*person = [[Personalloc]init];
person.age=10;
__weakPerson*weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2-----age:%p",weakPerson);
});
NSLog(@"1-----age:%p",person);
});
NSLog(@"touchesBegan");
}
四.Block的修饰符
block在修改NSMutableArray,不需要加__block,auto修饰变量,block无法修改,因为block使用的时候是内部创建了变量来保存外部的变量的值,block只有修改内部自己变量的权限,无法修改外部变量的权限。
static修饰变量,block可以修改,因为block把外部static修饰变量的指针存入,block直接修改指针指向变量值,即可修改外部变量值。全局变量值,全局变量无论哪里都可以修改,当然block内部也可以修改。
eg:block int age = 10,系统做了哪些—-》编译器会将block变量包装成一个对象
__block 修饰符作用:
__block可以用于解决block内部无法修改auto变量值的问题
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象
__block修改变量:age->__forwarding->age
__Block_byref_age_0结构体内部地址和外部变量age是同一地址
__block的内存管理---->当block在栈上时,并不会对__block变量产生强引用
block的属性修饰词为什么是copy?
block一旦没有进行copy操作,就不会在堆上
block在堆上,程序员就可以对block做内存管理等操作,可以控制block的生命周期,会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会对__block变量形成强引用(retain)
对于__block 修饰的变量 assign函数对其强引用;对于外部对象 assign函数根据外部如何引用而引用,当block从堆中移除时,会调用block内部的dispose函数dispose函数内部会调用_Block_object_dispose函数_Block_object_dispose函数会自动释放引用的__block变量(release),当block在栈上时,对它们都不会产生强引用,当block拷贝到堆上时,都会通过copy函数来处理它们,对于__block 修饰的变量 assign函数对其强引用;对于外部对象 assign函数根据外部如何引用而引用
block的forwarding指针说明:
栈上__block的__forwarding指向本身
栈上__block复制到堆上后,栈上block的__forwarding指向堆上的block,堆上block的__forwarding指向本身
五. block循环引用
1.ARC下如何解决block循环引用的问题?
三种方式:__weak、__unsafe_unretained、__block
1)第一种方式:__weak
Person*person=[[Person alloc]init];
// __weak Person *weakPerson = person;
__weaktypeof(person)weakPerson=person;
person.block=^{
NSLog(@"age is %d",weakPerson.age);
};
2)第二种方式:__unsafe_unretained
__unsafe_unretained Person*person=[[Person alloc]init];
person.block=^{
NSLog(@"age is %d",weakPerson.age);
};
3)第三种方式:__block
__block Person*person=[[Person alloc]init];
person.block=^{
NSLog(@"age is %d",person.age);
person=nil;
};
person.block();
三种方法比较:weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil,unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变,__block:必须把引用对象置位nil,并且要调用该block
作者:枫紫_6174
链接:https://www.jianshu.com/p/4bde3936b154