1、Block
struct Block_layout {
void *isa;//指向所属类的指针,也就是block的类型 NSStackBlock NSGlobalBlock NSMallocBlock
int flags;//标志变量,在实现block的内部操作时会用到
int reserved;//保留变量
void (*invoke)(void *, ...);//执行时调用的函数指针,block内部的执行代码都在这个函数中
struct Block_descriptor *descriptor;//block的详细描述,包含 copy/dispose 函数,处理block引用外部变量时使用
/* Imported variables. */
//variables: block范围外的变量,如果block没有调用任何外部变量,该变量就不存在
};
struct Block_descriptor {
unsigned long int reserved;//保留变量
unsigned long int size;//block的内存大小
void (*copy)(void *dst, void *src);// 拷贝block中被 __block 修饰的外部变量
void (*dispose)(void *);//和 copy 方法配置应用,用来释放资源
};
2、Block语法
@property(nonatomic, copy) void (^NormalBlock)(void);
typedef void (^NormalBlock)(void);
@property(nonatomic, copy) NormalBlock block;
^【返回值类型】【参数列表】【表达式】
exp. ^int (int count) {return count + 1;}
注意:【返回值类型】和【参数列表】可省略
void (^blockName) (int parameter);
//void代表返回值类型,后面一次是block名,参数
3、Block有哪几种类型
NSStackBlock
存储于栈区
block 内部引用外部变量,retain、release 操作无效,存储于栈区,变量作用域结束时,其被系统自动释放销毁。
MRC 环境下,[[mutableAarry addObject: blockA],(在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在其所在作用域结束也就是函数出栈后,从mutableAarry中取到的blockA已经被回收,变成了野指针。正确的做法是先将blockA copy到堆上,然后加入数组。支持copy,copy之后生成新的NSMallocBlock类型对象。
NSGlobalBlock
存储于程序数据区
block 内部没有引用外部变量的 Block 类型都是 NSGlobalBlock 类型,存储于全局数据区,由系统管理其内存,retain、copy、release操作都无效。引用static也为globalBlock
NSMallocBlock
存储于堆区
如上例中的
_block
,[blockA copy]
操作后变量类型变为NSMallocBlock
,支持retain、release,虽然 retainCount 始终是 1,但内存管理器中仍然会增加、减少计数,当引用计数为零的时候释放(可多次retain、release 操作验证)。copy之后不会生成新的对象,只是增加了一次引用,类似retain,尽量不要对Block使用retain操作。
在ARC环境下,Block也是存在__NSStackBlock的时候的,平时见到最多的是_NSMallocBlock,是因为我们会对Block有赋值操作,所以ARC下,block 类型通过=进行传递时,会导致调用
objc_retainBlock->_Block_copy->_Block_copy_internal
方法链。并导致 NSStackBlock 类型的 block 转换为 NSMallocBlock 类型
4、Block的结构(Clang后的对应)
原代码
-(void)blockDemo{
void (^block)(void) = ^{
};
}
clang后:
struct __block_impl {
void *isa;//指向所属类的指针,也就是block的类型
int Flags; //标志变量,在实现block的内部操作时会用到
int Reserved; //保留变量
void *FuncPtr;//block执行时调用的函数指针
};
//block内部实现 func0
static void __ViewController__blockDemo_block_func_0(struct __ViewController__blockDemo_block_impl_0 *__cself) {
}
static struct __ViewController__blockDemo_block_desc_0 {
size_t reserved;
size_t Block_size;
}
__ViewController__blockDemo_block_desc_0_DATA = { 0, sizeof(struct __ViewController__blockDemo_block_impl_0)};
static void _I_ViewController_blockDemo(ViewController * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__ViewController__blockDemo_block_impl_0((void *)__ViewController__blockDemo_block_func_0, &__ViewController__blockDemo_block_desc_0_DATA));
}
捕获变量 __block 源代码
-(void)blockDemo{
__block int a = 100;
void (^block)(void) = ^{
a++;
};
block();
}
clang后
struct __Block_byref_a_0 {
void *__isa; //指向所属类的指针,被初始化为 (void*)0
__Block_byref_a_0 *__forwarding;//指向对象在堆中的拷贝
int __flags;//标志变量,在实现block的内部操作时会用到
int __size;//对象的内存大小
int a;//原始类型的变量
};
static void __ViewController__blockDemo_block_func_0(struct __ViewController__blockDemo_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a)++;
}
可以看到 多了一个结构体 被
__block
修饰的变量被封装成了一个对象,类型为__Block_byref_a_0
,然后把&a作为参数传给了block。
其中,isa、__flags 和 __size 的含义和之前类似,而__forwarding
是用来指向对象在堆中的拷贝,runtime.c 里有源码说明:
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
...
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
// 堆中拷贝的forwarding指向它自己
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
// 栈中的forwarding指向堆中的拷贝
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
...
}
这样做是为了保证在 block内 或 block 变量后面对变量a的访问,都是直接访问堆内的对象,而不上栈上的变量。同时,在 block 拷贝到堆内时,它所捕获的由 __block 修饰的局部基本类型也会被拷贝到堆内(拷贝的是封装后的对象),从而会有 copy 和 dispose处理函数。
5、 Block copy的过程
Block
经过copy
之后会在desc
里生成的2个函数
-
copy
函数
调用时机 栈上的Block复制到堆时 -
dispose
函数
调用时机 堆上的Block
被废弃时
当Block
内部访问了带有__block
修饰符的对象类型的auto变量时
- 当
block
在栈上时,并不会对__block
变量产生强引用 - 当
block
被copy
到堆时- 会调用
block
内部的copy
函数 -
copy
函数内部会调用_Block_object_assign
函数 -
_Block_object_assign
函数会根据所指向对象的修饰符(__strong, __weak, __unsafe_unretained
)做出相应的操作,形成强引用(retain
)或者弱引用(注意:这里仅限于ARC
时会retain
,MRC
时不会retain
)
- 会调用
6、__block的作用
-
__block
可以用于解决block
内部无法修改auto
变量值的问题 -
__block
不能修饰全局变量、静态变量(static
)
编译器会将__block
变量包装成一个对象 -
__block
修改变量:age->__forwarding->age
-
__Block_byref_age_0
结构体内部地址和外部变量age
是同一地址
7、__block的结构(Clang后的对应)
编译器会将 __block
变量包装成一个对象
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;//age的地址
int __flags;
int __size;
int age;//age 的值
};
8、__forwarding指针的作用
__forwarding
, 它是结构体__Block_byref_abc_0
的组成部分, 且它的类型是__Block_byref_abc_0 *
。
- 栈上
__block
的__forwarding
指向本身- 栈上
__block
复制到堆上后,栈上block
的__forwarding
指向堆上的block
,堆上block
的__forwarding
指向本身
__forwarding
指针存在的意义就是,无论在任何内存位置,都可以顺利地访问同一个__block
变量。
9、Block 释放的过程
当block
从堆中移除时
1、会调用
block
内部的dispose
函数
2、dispose
函数内部会调用_Block_object_dispose
函数
3、_Block_object_dispose
函数会自动释放引用的__block
变量(release)
当block
从堆上移除时,都会通过dispose
函数来释放它们
10、copy_dispose
11、Block循环引用
三种方式:
__weak、__unsafe_unretained、__block
解决循环引用
1、__weak
:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
2、__unsafe_unretained
:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
3、__block
:必须把引用对象置位nil
,并且要调用该block
12、Block捕获(多重嵌套情况)
block内部会专门新增一个成员来外面变量的值,这个操作称之为捕获
13、Block hook的几种实现
Hook Block 交换block的实现
BlockHook,fishhook,BlockHookDemo,YSBlockHook...
- 1、交换
block
的实现 aspect 原理就是在运行期间动态交换两个方法所指向的IMP指针,那么换作Block也是一样的道理。只要将invoke指针指向我们自定义的函数地址,就可以交换block的实现 - 2、
- 3、
14、Block为何会有Private Data
15、如果获取Block参数的个数及其类型
16、关于BLOCK_HAS_EXTENDED_LAYOUT的一些内容
17、Block 常见题
1、
Block
的原理是怎样的?本质是什么?
2、__block
的作用是什么?有什么使用注意点?
3、Block
的属性修饰词为什么是copy
?使用Block
有哪些使用注意?
4、Block
在修改NSMutableArray
,需不需要添加__block
?
注:
clang 用法:clang -fobjc-arc -framework Foundation HelloWord.m -o HelloWord
- -fobjc-arc表示编译器需要支持ARC特性
- -framework Foundation表示引用Foundation框架
- HelloWord.m为需要进行编译的源代码文件
- -o HelloWord表示输出的可执行文件的文件名
- 1、clang 生成C++
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m