问题: 阐述一下你所了解的block所有相关内容.
-
block的本质
-
block的分类以及copy情况
-
block的变量捕获
-
block中 auto变量的强弱引用问题
-
__block的作用以及实现原理
-
block中的循环引用问题.
问题: 讲一下block的本质.
block的本质是OC对象.它的底层结构中也含有isa指针.
block 是封装了函数调用以及函数调用环境的OC对象.
实例, 在Demo中main函数中直接写一个 block,如下所示.
int main(int argc, const char *argv[]) {
@autoreleasepool{
void(^block)(void) = ^{
NSLog(@"Test");
};
block();
}
return 0;
}
block 会生成一个 __main_block_impl_0 的结构体, 结构体除了构造函数之外 还有两个成员变量.
一个是 __block_impl 类型的 impl. 另外一个是 __main_block_desc_0 类型的 Desc指针.
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
// 构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FunPtr = fp; // 函数结构体指针赋值
Desc = desc;
}
};
其中 Desc中主要包含了block的数据大小.这个是在构建构成中就确认了.
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = {0, sizeof(struct __main_block_impl_0)};
如果block内部访问了外部的auto对象类型,那么还有存在copy函数指针和dispose函数指针.
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_impl 结构体中主要包含了 isa 指针,以及函数结构体的指针FunPtr,结构如下所示.
函数结构体的指针FunPtr 会在 __main_block_impl_0 的构造函数中进行赋值.
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void * FunPtr;
}
调用过程我们发现, __main_block_impl_0类型的结构体 直接强制转换成了 __block_impl 类型的结构体 然后直接调用内部的函数指针 FunPtr. 完成block的调用过程.
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
注: 为啥可以直接强转?
由于 __block_impl 类型的 impl 是 __main_block_impl_0 中得首页成员变量,所以两者的内存地址是一致的,故可以强转.
问题: 讲一下block的变量捕获.
在block中,变量捕获情况如下所示.
- 被auto修饰的局部自动变量是值捕获
- 被static修复的局部变量为指针捕获.
- 全局变量不捕获而是直接访问.
变量类型 | 捕获到block内部 | 访问方式 |
---|---|---|
局部变量 (auto) | ✅ | 值访问 |
局部变量 (static) | ✅ | 指针访问 |
全局变量 | ❌ | 直接访问 |
对于类中方法,self是实例方法的隐式参数,属于局部变量,所以会进行捕获.
问题: 讲一下block的几种类型.
block 主要分为三种类型 __NSGlobalBlock__
、 __NSStackBlock__
、 __NSMallocBlock__
.
三者的形成条件如下所示.
block类型 | 形成条件 |
---|---|
__NSGlobalBlock__ | 没有访问 auto变量 |
__NSStackBlock__ | 访问了 auto 变量 |
__NSMallocBlock__ | __NSStackBlock__ 调用了copy |
三者的调用copy方法的本质如下表格所示.
block类型 | 调用 copy 之后 |
---|---|
__NSGlobalBlock__ | 无任何变化 |
__NSStackBlock__ | 从栈复制到堆 |
__NSMallocBlock__ | 引用计数+1 |
在MRC环境下,如果需要改变Stack类型Block的存储位置,可以手动Copy.
在ARC环境下,编译器会根据情况自动对Block进行一次Copy操作.有以下几种情况.
- block作为函数返回值.
- block被 __storge 指针引用.
- block作为Cocoa API 方法名为usingBlock的参数时.
- block作为GCD的入参时.
问题: 讲一下block引用auto修饰的对象相关内容.
- 当栈上的block(
__NSStackBlock__
)访问了对象类型的auto变量时.
- 将不会对auto变量进行强引用.
- 当堆上的block(
__NSMallocBlock__
)访问对象类型的auto变量时.
- 在ARC环境下,会调用copy函数.
- copy函数的内部会调用__Block_object_assign函数.
- __Block_object_assign函数会根据auto变量的修饰符( __strong 、__weak 、 __unsafe_unretained)做出相应的操作,类似于retain操作(强引用、弱引用).
- 当堆上的block(
__NSMallocBlock__
) 访问对象类型的auto变量并且在释放时.
- 会调用dispose函数.
- dispose函数内部会调用 __Block_object_dispose 函数,
- _Block_object_dispose 函数会自动释放引用的auto变量,相当于release操作.
问题: 讲一下__block修饰符的作用以及具体底层实现.
- 作用1: __block 主要用于修改外部的auto自动变量的一个修饰符.
- 作用2: 在MRC环境下,可用于解决循环引用(__block在MRC下会形成__weak修饰的数据结构体).
__block不能修饰全局变量、静态变量.
__block修饰符内部实际生成了一个带有isa指针的结构体.
auto变量添加了 __block 修饰符之后, 底层实际上 __main_block_impl_0 持有的是一个 __block的结构体指针.
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 * Desc;
__Block_byref_age_0 *age; // __block修饰产生的结构体指针
...
};
__block的底层结构体中,主要包含一下几个重要结构.
- isa指针.
- 指向 "本身" 的__forwarding指针.
- __block结构体的大小 __size.
- auto的成员变量.
struct __Block_byref_age_0 {
void * __isa;
__Block_byref_age_0 * __forwarding;
int __flags;
int __size;
int age;
};
- 如果是被__block修饰的对象类型,还会存在内存管理的 copy函数指针 和 dispose函数指针.
struct __Block_byref_test_0 {
void *__isa;
__Block_byref_test_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *__strong test;
}
__block中__forwarding指针存在的意义:
- 保证当栈上block发生释放时,所有的block(无论在堆上还是栈上的)均能访问到__block变量.如下图所示.
__block内存管理问题:
-
如果是栈上的block,那么它不会对__block修饰的变量产生强引用.
-
如果把block复制到堆上时,会调用copy函数,函数内部会调用 __Block_object_assign 函数对其__block生成的结构体进行一次copy处理,把__block变量结构体拷贝到堆上并产生强引用.并且 __block变量结构体内部也会同时调用内部copy函数,根据指针类型来添加强弱引用(PS: 强引用的话,引用计数会 + 1)
-
复制过程中 栈上的__block修饰的变量
__forwarding指针
指向的是堆上__block修饰的变量. -
如果堆上的block释放时,会调用 dispose函数, dispose函数内部会调用 __Block_object_dispose 函数对其进行__block形成的结构体释放操作(release操作).__block生成的结构体内部也会调用一次dispose函数,如果是强引用,那么就会进行release操作.
问题: 如何解决block中的循环引用问题?
-
在 MRC环境下, 可以使用
__block
解决循环引用问题, 因为在MRC环境下, __block生成的变量结构体所持有的对象并不会有retain操作,引用计数不会变化. -
使用
__weak
修饰对象, __weak修饰的变量会在 block生成的结构体中形成一个 __weak修饰的指针成员变量,指向对象.循环链得以打破,可以解决循环引用. -
使用
__unsafe_unretained
修饰对象,也会产生 block生成的结构体中形成一个 __unsafe_unretained修饰的指针成员变量,指向对象.与 __weak不同的是, __weak的修饰的对象在内存释放时, __weak 修饰的指针会被置为nil
,__unsafe_unretained
则不会置空,所以会有一定的风险.
Comments | 0 条评论