一:block的基本使用:
1.
^{
NSLog(@"----blcok");
}();
2. block访问变量
int age =30;
void(^blcok)(int,int)= ^(inta,intb){
NSLog(@"*****blcok-----%d",age);//其实是把age变量存储到了block对象里面
};
blcok(10,20);
二:block的基本概念:
1. block本质也是一个OC对象,它内部也有个ISA指针;
2. block是封装了函数调用以及函数调用环境的OC对象;
三:内部解析:
1. 定义block以后,使用xcrun命令编译成C++代码; xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main7-arm64.cpp
编译完成后可以看到block的结构如下:
struct __main_block_impl_0 {
struct __block_impl impl; // 因为impl是第一个成员;所以impl的地址就是__main_block_impl_0结构体的地址;
struct __main_block_desc_0* Desc;
// 结构体里面放的函数;构造函数(类似于init方法);返回__main_block_impl_0这个结构体函数;入参的FP是__main_block_func_0这个结构体;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; // _NSConcreteStackBlock类型
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
从该结构中可以看出:block主要有1个结构指针__main_block_desc_1和一个__block_impl结构体;以及block外面访问的auto变量和自身的构造函数;继续分析一下__main_block_desc_1和__block_impl内部:
__block_impl结构体:
struct __block_impl {
void *isa; //ISA 指向block类型的
int Flags; //标记位
int Reserved; //预留位 一般是0
void *FuncPtr; 函数指针;指向构造函数的__main_block_func_0这个函数;
};
__main_block_func_0该函数就是block的 ^{}这个函数;我们来分析这个函数的具体实现如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kc_zlwckf1s2hzcw2f11qmlq2780000gp_T_main_de43bb_mi_0);
}
可以看出入参是将block对象传入到了这个函数里面,而且也是block实现的具体内容;我们继续分析其他的结构类型;还有__main_block_desc_1;
__main_block_desc_1结构体:
static struct __main_block_desc_1 {
size_t reserved; //预留位一般也是为0
size_t Block_size;// 返回的是block结构的自身内存占用的大小;
}
block内部的结构如果上面所说;接下来我们看一下;调用的流程和具体的声明如下:
定义声明block3:
void (*block3)(void) = & __main_block_impl_0(
__main_block_func_0,参数1
&__main_block_desc_0_DATA));参数2
执行block函数:
block3->FuncPtr(block3);
从声明定义和调用为起点,来串一下整个流程的调用关系:
从声明上可以看出,在定义block的时候,调用构造__main_block_impl_0函数会传入2个参数__main_block_func_0函数指针和__main_block_desc_0_DATA描述的结构体指针;在构造函数中:将__main_block_func_0函数指针、__main_block_desc_0_DATA赋值给了impl.FuncPtr、__main_block_desc_0* Desc;也就是说block内部的FuncPtr 指向了__main_block_func_0函数;__main_block_desc_0* Desc等于__main_block_desc_1这个结构体;当我们调用 block3->FuncPtr(block3),实际就是调用__main_block_func_0并且将block对象传入到__main_block_func_0函数中;来执行;
四、 block的变量捕获机制capture
1、 从基本使用的内容里面看第二个例子,如果block访问了局部变量,block 内部会生成一个变量,来存储auto自动变量称为捕获,称之为捕获;
2、 捕获的规则如下:
局部变量:
auto(默认)变量 :自动变量,离开作用域就自动销毁;auto变量会捕获到block内部;访问方式是: 值传递(将10的值传递过来的);
static 变量:static变量会捕获到block内部里面,访问方式是:指针传递;
全局变量:
blcok不会捕获变量的值。访问方式是:直接获取;blcok内部不会管理全局变量;直接获取全局变量的值;
3、 我们继续看一下如果访问局部变量block内部结构体:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age; //捕获到block内部的变量;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看出block内部确认多了一个int age;
4、如果是static修饰的变量:
auto int age = 10;
static int height = 20;
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *height = __cself->height; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kc_zlwckf1s2hzcw2f11qmlq2780000gp_T_main_6aa546_mi_0,age,(*height));
}
主要看一下这void (*block3)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height))代码;从内部结构的__main_block_func_0函数看也确实访问的&heigt地址;
blcok内部打印的时候是获取的height的地址存储的值;所以是指针传递;并且 height在内存中只有1份;直到应用程序退出的时候,才会销毁;所以不需要传值,传入指针既可!
而age是作用域是函数结束就释放对象,再次调用重新开辟内存空间,所以需要传入值;来保存到block结构体内部函数__main_block_func_0内部可以获取到 age变量的值来使用所以需要传入值;
5、扩展一下Static的基本知识点:
Static 修饰局部变量时,不可以改变其作用域,在程序中永远只有一份内存;改变生命期,直到程序退出才会释放储存单元;
static关键字修饰局部变量:
当static关键字修饰局部变量时,只会初始化一次且在程序中只有一份内存;
关键字static不可以改变局部变量的作用域,但可延长局部变量的生命周期(直到程序结束才销毁)。
static关键字修饰全局变量:
当static关键字修饰全局变量时,作用域仅限于当前文件,外部类是不可以访问到该全局变量的(即使在外部使用extern关键字也无法访问)。
extern关键字:
想要访问全局变量可以使用extern关键字(全局变量定义不能有static修饰)。
全局变量是不安全的,因为它可能会被外部修改,所以在定义全局变量时推荐使用static关键字修饰。
Static和extern的区别:
(1)extern修饰的全局变量默认是有外部链接的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过extern全局变量的声明,就可以使用全局变量。
(2)static修饰的全局静态变量,作用域是声明此变量所在的文件。
五:block的基本类型
1、从上面的内部结构可以看出来ISA指向了_NSConcreteStackBlock这种类型;说明block是有类型的;
2、block 有3种类型:
栈block __NSStackBlock__ :访问auto变量是Stack 放在栈区;(调用copy函数是从栈区复制到了堆区,变成了堆blcok)
全局blcok __NSGlobalBlock__ :没有访问auto变量是global 放在数据段 (调用copy函数是什么没有操作);
堆blcok的 __NSMallocBlock__, : stackBlock调用了copy函数;就是堆block;global调用copy依然是global的类型的lblock;(堆blcok调用copy函数,引用计数加1);
3.block的继承关系
__NSGlobalBlock__ ---->__NSGlobalBlock------>NSBlock------->NSObject 继承树;
4.内存中存储的位置:
堆:动态分配内存,需要程序员申请内存 也需要程序员自己管理内存 堆blcok的 __NSMallocBlock__
栈:系统管理内存 栈block __NSStackBlock__;
数据区:编译期生成 全局blcok __NSGlobalBlock__
代码段:存放代码,编译期生成;
六:ARC情况环境下,编译器会根据情况自动将栈上的block复制到堆上上,比如以下情况:
1.block作为函数返回值时;
2.将block赋值给__strong指针时; block可以使用copy和strong修饰;系统默认都是强指针;
3.blcok 作为cocoa api 中含有usingblock的方法参数时;
4.block作为GCD的方法入参时;
七:block 访问auto对象类型
1.举例说明:
typedefvoid(^MJBlock)(void);
MJBlock block;
{
LZHPerson* person = [[LZHPersonalloc]init];
person.age=10;
// __weak LZHPerson * weakPersion = person;
block = ^{
NSLog(@"%d",person.age);
};
block();
}
NSLog(@"-------------------");
从上面的例子看分析:person对象什么时候释放的;释放的时机是什么?
2.分析block内部结构如下所示:
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};
从上面结构看出来:block结构对象内部的dec结构会多出来2个指针函数*copy和*dispose;
修饰对象的时候:block内部的desc的会自动添加2个函数指针;__main_block_copy_0, __main_block_dispose_0;因为对象类型需要进行内存管理操作;
_Block_object_assign((void*)&dst->person, (void*)src->person, _Block_object_assign 根据外面变量的类型对block内部作用相应的类型引用;
_Block_object_dispose((void*)src->person
弱引用是需要运行时,才可以生成C++代码;所以weak修饰的变量是不可以直接编译的,需要加上MRC的语法;
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main7-arm64.cpp
1.blcok外面的auto变量是什么类型,在捕获的时候block内部也同样生成相应的类型变量;系统默认都是strong;强引用类型
2.当外部的对象使用strong修饰的时候,对象的生命周期和block的生命周期是一样的;如果使用weak修饰,对象的生命就是自己本身的生命周期 ;
对象类型总结:
一:当block内部访问了对象类型的auto变量时
1:如果block是在栈上,将不会对auto变量产生强引用;(比如:没有强指针指向block模块;没有赋值符号);
2:如果block被拷贝到堆上;
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign的函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出等价的引用关系,类似于retain(形成强引用、弱引用);
注:此处的引用是指的捕获变量的强、弱引用;不是指的block的y强弱引用;
3:如果block从堆上移除(block对象释放)
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose 函数会自动释放引用的auto变量,类似release;
4: copy函数 的调用时机是:栈上的block复制到堆时调用
dispose函数调用时机:堆上的block被废弃时调用;
5:如果在堆区使用strong修饰变量,那么该auto变量对象的生命周期跟block的生命周期保持一致;也会使其引用计数器加一;
注:
在block内部调用block的时候;多层block调用,直接找到该对象的强引用的位置,强引用的位置处就是该对象的生命周期;不需要去处理弱引用指针;
因为弱引用为栈block;括号结束就直接释放了。
八: __block修饰局部变量类型;
1. 如果外部对象使用__block修饰;在编译期间会对这个变量属性包装成一个 __Block_byref_ages_0 *ages对象;其内部结构如下:
block内部结构如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_ages_0 *ages; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_ages_0 *_ages, int flags=0) : ages(_ages->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__Block_byref_ages_0对象结构如下:
struct __Block_byref_ages_0 {
void *__isa; //ISA指向了viod* 类型的指针
__Block_byref_ages_0 *__forwarding; //forwarding指针是指向自己本身的指针;
int __flags; //标记位
int __size; //__Block_byref_ages_0的大小
int ages;__blcok // 修饰的属性;真正修改的值;
};
2、调用blcok函数的内部结构;从 (ages->__forwarding->ages) = 20,可以看出来__blcok修饰的变量值可以改变;其实修改的是__Block_byref_ages_0内部的变量的值;
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_ages_0 *ages = __cself->ages; // bound by ref
(ages->__forwarding->ages) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kc_zlwckf1s2hzcw2f11qmlq2780000gp_T_main_7d9569_mi_0,(ages->__forwarding->ages));
}
3.__blcok修饰成员变量总结:
__block可以用于解决block内部无法修改auto变量值的问题
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象
__block修改变量:age->__forwarding->age
九: __block的内存管理
blcok在捕获对象的时候,会自动生成copy * 和dispose* 指针;
__blcok修饰的int类型是block在执行copy操作的时候会对这个对象进行强引用(_strong),而__block在访问外面对象的auto变量的时候,会对这个对象进行等价的操作;(根据外面是强引用或者是弱引用)。
1.当block在栈上时,并不会对__blcok变量产生强引用
2.当block被 copy到堆时:
会调用blcok内部的copy函数;
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会对_blcok变量形成强引用(retain)
3.blcok从堆中移除时
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的__block变量(release)
大总结:对象类型的auto变量和__block变量;
相同点:
1.当block在栈上时候,对它们都不会产生强弱引用;
2.当block拷贝到堆上时,都会通过copy函数来处理它们;
3. __block变量(假设变量名字叫做a)
_Block_object_assign((void*)&dst->ages, (void*)src->ages, 8 BLOCK_FIELD_IS_BYREF*
4.对象类型的auto变量(假设变量名叫做P)
_Block_object_assign((void*)&dst->person, (void*)src->person, 3BLOCK_FIELD_IS_OBJECT
5.当block从堆上移除时,都会t通过dispose函数来释放它们;
__block变量(假设变量名叫做a)
_Block_object_dispose((void*)src->a, 8 BLOCK_FIELD_IS_BYREF
6.对象类型的auto变量(假设变量名叫做p)
_Block_object_dispose((void*)src->p, 3 BLOCK_FIELD_IS_BYREF
7.(ages->__forwarding->ages) = 20; 为什么已经拿到了block中的agers结构体还要使用__forwarding->age去访问age的值;
因为:当栈上的__block对象copy到堆区后,栈上的__block的forwarding指针指向了堆区;来保证修改的20的值,都可以访问到堆区的内存数据;
十: __blcok修饰对象类型
struct __Block_byref_objc_0 {
void *__isa;
__Block_byref_objc_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*); --新增
void (*__Block_byref_id_object_dispose)(void*);-- 新增
NSObject *__strong objc; --新增
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_objc_0 *objc; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_objc_0 *_objc, int flags=0) : objc(_objc->__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_objc_0 *objc = __cself->objc;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kc_zlwckf1s2hzcw2f11qmlq2780000gp_T_main_9baefb_mi_0,(objc->__forwarding->objc));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->objc, (void*)src->objc, 8 BLOCK_FIELD_IS_BYREF);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 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};
注意:
1.当__blcokb变量在栈上时,不会对指向的对象产生强引用
2.当block被 copy到堆时:
会调用blcok内部的copy函数;
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会对_blcok结构体形成强引用(retain);
__blcok对象的结构内部会调用copy函数;
__block在执行copy操作的时候会对这个对象进行等价的操作;(根据外面是强引用或者是弱引用)。(注意MRC不会产生强引用);
3.blcok从堆中移除时
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的__block结构体对象(release)
十一: block的循环引用问题
1.arc
一:使用__weak、__unsafe_unretained解决;当block在捕获对象的时候;block指向对象为弱引用;
1.typeof (person) 类型为person的类型;简写操作,等价LZHPerson * weakPerson;
2. __unsafe_unretained 不会retain;相当于弱引用相当于weak;跟weak的区别是当对象释放的时候,指针指向的内存不会自动置为nil;weak会自动置为nil;不安全的;
(weak置nil后再次访问内存对象;因为是nil不会(野指针)闪退,而__unsafe_unretained会产生野指针会闪退);
二:__block来解决循环引用;
__block LZHPerson * person = [[LZHPerson alloc]init];
person.age = 10;
person.block = ^{
NSLog(@"%d",person.age);
person = nil;
};
person.block();
1.必须要执行blcok;
2.blockn内部需要把__内部的对象指针置为nil;
2.mrc情况下:
1.使用__unsafe_unretained解决;MRC是不支持__weak;
2.__blcok可以直接解决blockl;因为__blcok修饰对象;内部一定是弱引用;