天天看点

iOS进阶 - Block底层原理iOS进阶 - Block底层原理

iOS进阶 - Block底层原理

一 block的本质

1 block本质上也是一个oc对象,它内部也有一个isa指针

2 block是封装了函数调用以及函数调用环境的oc对象

3 block的底层结构

Block源码转换查看

block在实际编译时无法转换成我们能够理解的源代码,但可以通过clang(LLVM编译器)转换成可读的源代码,步骤如下:

1打开终端,输入cd 把要转换的文件拖到终端,然后回车进入要转换文件的目录;

2输入命令clang -rewrite-objc main.m -o main.cpp

二 block的变量捕获 capture

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

变量类型 捕获到block内部 访问方式

局部变量 auto 会捕获 值传递

static 会捕获 指针传递

全局变量

不用捕获

直接访问

三 block类型

block有三种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

__NSGlobalBlock__

__NSStackBlock__

__NSMallocBlock__

如何判断具体属于什么类型的block呢?

block类型 环境

NSGlobalBlock 没有访问auto变量

NSStackBlock 访问了auto变量

NSMallocBlock NSStackBlock 调用了copy

每一种类型的block调用copy后的

block类型 存储区域 调用copy后的复制效果

NSStackBlock 栈 从栈复制到堆

NSGlobalBlock 程序的数据区域data 什么也不做

NSMallocBlock 堆 引用计数增加

全局block调用copy后仍然是全局globalBlock

注意:

这是因为编译器默认在ARC环境下,应该切换到MRC环境下,看一下真正的block类型

将编译器改为去除ARC,在Build Settings -> automatic Reference Counting 中将ARC改为MRC

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,如:

block作为函数返回值时 如:Masonry

block赋值给__strong指针时

block作为Cocoa API中方法名有usingBlock的方法参数时 如[array enumeratorObjectsUsingBlock]

block作为GCD API的方法参数时

四 对象类型的auto变量

总结:

当block内部访问了对象类型的auto变量时

1 如果block在栈上,将不会堆auto变量产生强引用

2 如果block被拷贝到堆上

1)会调用block内部的copy函数

2)copy函数内部会调用Block_object_assign函数

3)Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,l类似于retain(形成强引用或者弱引用)

3 如果block从堆上移除

1)会调用block内部的dispose函数

2)dispose函数内部会调用Block_object_dispose函数

3)_Block_object_dispose函数会自动释放引用的auto变量。类似于release

函数 调用时机

copy函数 栈上的block复制到堆上时

dispose函数 堆上的block被废弃时

__weak问题解决

使用clang转换oc为c++代码时,可能会遇到以下问题

Cannot create __weak reference in file using manual reference

解决:支持ARC、指定运行时系统版本,如:

xcrun -sdk iphooneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0 main.m

五 __block修饰符

1 __block的底层原理

__block可以用于解决block内部无法修auto变量的问题

__block不能修饰全局变量、静态变量(static)

编译器会将__block变量包装成一个对象

__forwarding指针指向自己

age – __forwarding – age

block内部有个__Block_byref_age_0

类型的指针age,指向结构体__Block_byref_age_0

结构体内部的forwarding指向自己,拿到结构体内部的age

2 __block的内存管理

当block在栈上时,并不会对__block变量产生强引用

当block被copy到堆上时

1会调用block内部的copy函数

2 copy函数内部会调用__Block_object_assign函数

3 _Block_object_assign函数会对__block变量形成强引用(retain)

⚠️注意:区分于auto对象类型,对象类型copy时要根据对应的属性修饰符

当block从堆中移除时

1 会调用block内部的dispose函数

2 dispose函数内部会调用_Bloock_object_dispose函数

3 _Block_object_dispose函数会自动释放引用的_block变量(release)

3 对象类型的auto变量、__block变量

当block在栈上时,对他们都不会产生强引用

当block拷贝到堆上时,都会通过copy函数来处理它们

1 __block变量(假设变量名叫做age)

_Block_object_assign((void*)&dst->age, (void*)src->age, 8/BLOCK_FIELD_IS_BYREF/);

2 对象类型的auto变量(假设变量名叫做obj)

_Block_object_assign((void*)&dst->obj,(void*)src->obj,3/BLOCK_FIELD_IS_OBJECT/);

当block从堆上移除时,都会通过dispose函数来释放它们

1 __block变量(假设变量名叫做a)

_Block_object_dispose((void*)src->age, 8/BLOCK_FIELD_IS_BYREF/);

2 对象类型的auto变量(假设变量名叫做p)

_Block_object_dispose((void*)src->obj, 3/BLOCK_FIELD_IS_OBJECT/);

对象 BLOCK_FIELD_IS_OBJECT

__block变量 BLOCK_FIELD_IS_BYREF

区别:

在堆上时,Block_object_assign

__block变量都是强引用,对象变量要根据其外部定义时的强弱来决定

4 被__block修饰的对象类型

当__block 变量在栈上时,不会对指向的对象产生强引用

当__block变量被copy到堆上时

1 会调用__block变量内部的copy函数

2 copy函数内部会调用_Block_object_assign函数

3 _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)作出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)

如果__block变量从堆上移除

1 会调用__block变量内部的dispose函数

2 dispose函数内部会调用_Block_object_dispose函数

3 _Block_object_dispose函数会自动释放指向的对象(release)

__block MYPerson *p = [MYPerson alloc] init];

区别于上面3中,添加__block修饰后,block内部的person指针,先指向__block包装的结构体,然后结构体中的person指针再指向person对象

MYPerson *p = [[MYPerson alloc] init];

__block __weak MYPerson *weakPerson = p;

MYBlock block = ^{

NSLog(@"---- %@",weakPerson);

};

+40:是__Block_byref_weakPerson_0的地址加40到weakPerson这个指针的地址

六 循环引用

简单举例:以下情况会造成循环引用

person.block = ^{

//循环引用

NSLog(@“age is %d”, person.age);

};

1 解决循环引用问题 - ARC

用__weak __unsafe__unretained 解决

__weak: 不会产生强引用 指向的对象销毁时,会自动让指针置为nil

__unsafe_unretained: 不会产生强引用,不安全,指向的对象销毁时,指针存储的地址不变,不会自动置为nil

用 __block 解决(必须调用block)

2 解决循环引用问题 - MRC

用 _unsafe__unretained 解决 (MRC不支持)

用 __block 解决(必须调用block)

七 相关问题

block的原理是怎样的?本质是什么?

答:block是封装了了函数调用和函数调用环境的OC对象,内部也有一个isa指针

__block的作用是什么?有什么使用注意点?

答:__bloock可以将变量包装成一个对象,解决block内部无法修改auto变量值的问题,通过block内部的__block_byref_变量_0 指针去访问这个变量的结构体对象,达到修改auto变量值的效果

block的属性修饰符词为什么是copy?使用block有哪些注意点?

答:没有copy的时候,block是在栈区的,释放时机不可控,进行copy操作,拷贝到堆上后,可以对其进行内存管理;

使用注意:循环引用问题

解决:__weak __unsafe_unretained __block

__weak typeof(self) weakSelf = self;

注意:在bloock内部,使用weakSelf,有时如延时函数情况下,self在使用时已经释放了,为了保证在block内部任务执行过程中self的存在,需要用__strong

__strong typeof(weakSelf) strongSelf = weakSelf;

其他:在ARC下,strong和copy效果一样,MRC下,copy会拷贝到堆上,strong不会

block在修改NSMutableArray,需不需要添加__block?

答:不需要,只是addObject

注意:如果mutArr = nil,是不行的