说到Block,相信大家并不陌生,平时开发用的太多了,本篇章就从基础到深入,全方位的来进行分析。
一:Block初探
1. 什么是Block?
Block就是一个代码块, Block是将函数及其执行上下文封装起来的对象,是一个匿名的函数对象, Block也有 isa。既然Block内部封装了函数,那么它同样也有参数和返回值,本身也可以被作为参数在方法和函数间传递。
2. 基本使用
- 普通用法 无参数无返回值
// 无参数无返回值
void(^MyBlockOne)(void) = ^(void){
NSLog(@"无参数,无返回值");
};
MyBlockOne();//block的调用
有参数无返回值
// 有参数无返回值
void(^MyblockTwo)(int a) = ^(int a){
NSLog(@"@ = %d我就是block,有参数,无返回值",a);
};
MyblockTwo(100);
有参数有返回值
// 有参数有返回值
int(^MyBlockThree)(int,int) = ^(int a,int b){
NSLog(@"%d我就是block,有参数,有返回值",a + b);returna + b;
};
MyBlockThree(12,56);
无参数有返回值
// 无参数有返回值
int(^MyblockFour)(void) = ^{NSLog(@"无参数,有返回值");
return45;
};
MyblockFour();
- 作为属性
//有返回值有参数的属性 @property (nonatomic, copy) int (^MyBlock)(NSString *name); //没返回值没参数的属性 @property (nonatomic, copy) void (^MyBlock1)();
- 作为方法参数
-(void)myBlock:(int(^)(NSString *name))completion;
- 用
typedef定义Block
typedef int (^customBlock)(int , int);
这时,customBlock
在定义类的属性时可以这样:
@property (nonatomic,copy) customBlock firstBlock;
在定义方法时可以这样:
-(void)customBlock:(customBlock)completion;
二:Block内存管理
1. Block分类
Block一共有三种类型:
__NSGlobalBlock__全局Block__NSMallocBlock__堆Block__NSStackBlock__栈Block
MRC下
-
NSGlobalBlockblock内部没有引用外部变量的,Block类型都是NSGlobalBlock类型,存储于全局数据区,由系统管理其内存,retain、copy、release操作都无效。如果访问了外部static或者全局变量也是这种类型。 -
NSStackBlockblock内部引用外部变量,retain、release操作无效,存储于栈区,变量作用域结束时,其被系统自动释放销毁。 -
NSMallocBlock如上例中的
[blockB copy]操作后变量类型变为NSMallocBlock,支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数,当引用计数为零的时候释放。
ARC下
-
NSGlobalBlock此种情况和
MRC一样,block内部没有引用外部变量,或者只使用静态变量和全局变量,Block类型都是NSGlobalBlock类型。 -
NSStackBlock访问了外部变量,但没有强引用指向这个
block,如直接打印出来的block比如这样: -
NSMallocBlockARC环境下只要访问了外部变量,而且有强引用指向该block(或者作为函数返回值)就会自动将__NSStackBlock类型copy到堆上。
2 copy对Block的影响
栈上的Block,如果其所属的变量作用域结束,该Block就被废弃,如果一般的自动变量,当然,Block中的_block变量也被放弃。
为了解决这个问题,需要把Block复制到堆中,延长其生命周期。在ARC下,系统就会自动将Block复制到(copy)堆上。
不同类型的Block使用copy效果如下:
对于copy对不同Block的这一特性,我们来看一段代码。
NSObject *obj = [[NSObject alloc]init];
NSLog(@"==== %ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
void(^firstBlock)(void) = ^{
NSLog(@"==== %ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
firstBlock();
void(^__weak secondBlock)(void) = ^{
NSLog(@"==== %ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
secondBlock();
void(^thirdBlock)(void) = [secondBlock copy];
thirdBlock();
结果是否出乎意料呢?
第一个打印1,肯定是没有疑问的,我们着重看下后面的345
-
firstBlock是一个堆区的block,这里捕获了obj这个外部变量会进行加一,而且栈区的obj拷贝到堆区又加一,所以打印结果为3。 -
打印
4是因为这里secondBlock是栈block,没有进行拷贝,只是捕获,进行加一,所以为4。 -
最后打印
5是因为[secondBlock copy]进行了拷贝操作,加一,再赋值给thirdBlock,所以打印结果为5。
三:Block循环引用
1. 循环引用的原因
循环引用本质在于两者相互持有,比如控制器self,有一个Block属性,Block调用中又引用self,形成self->block->self的闭环,两者都无法释放。
self.jwBlock = ^{
[self doSomething];
};
正常释放
循环引用
2. 循环引用的解决
方式一:利用weak自动释放
__weak typeof(self) weakSelf = self;
__weak typeof(self) weakSelf = self;
self.jwBlock = ^{
[weakSelf doSomething];
};
weakSelf的存在打破了循环引用,两者都可正常释放,平时可能会看到这样的代码
__weak typeof(self) weakSelf = self;
self.block = ^{
//临时变量
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
self.block();
__strong typeof(weakSelf) strongSelf = weakSelf定义了一个临时强指针指向weakSelf,防止其提前释放。
方式二:手动释放
__block ViewController *vc = self;
self.name = @"jw";
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", vc.name);
vc = nil;
});
};
self.block();
方式三:不使用self,传参
self.name = @"jw";
self.block = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", vc.name);
});
};
self.block(self);
3. 其他
其实平时我们同样会使用到系统的一些Block,比如UIView动画[UIView animateWithDuration: animations:],是否会存在循环引用呢?当然我们需要区分对待。
[UIView animateWithDuration: animations:]是类方法,不被self持有(即self持有了view,但view没有实例化)所以不会循环引用。
Masonry中是否存在循环引用?
Monsary使用的block是当做参数传递的,即便block内部持有self,设置布局的view持有block,但是block不持有view,当block执行完后就释放了,self的引用计数-1,所以block也不会持有self,所以不会导致循环引用。
- 使用Facebook的开源框架能检测是否存在循环引用
- (void)checkLeaks {
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:self];
NSSet *retainCycles = [detector findRetainCycles];
NSLog(@"%@", retainCycles);
}
四:Block底层结构
1. 通过clang看Block
1. Block 本质
定义.c的文件,添加如下代码
int main(){
void(^block)(void) = ^{
printf("我来了");
};
return 0;
}
使用clang命令clang -rewrite-objc main.c -o main.cpp将其转化为.cpp文件。
从.cpp,当我们去掉一些类型强转代码,其实就是调用了__main_block_impl_0构造函数,其中传入了两个参数,分是__main_block_func_0、 &__main_block_desc_0_DATA
先看下__main_block_impl_0
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.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
__main_block_func_0 Block 内部实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("我来了");
}
__main_block_impl_0是一个结构体,继承于__block_impl,可以看到__block_impl里面是有isa的,这也说明Block也是对象。__main_block_impl_0结构体内部有一个构造函数__main_block_impl_0,这个构造函数对block结构体中相关属性进行设置。void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)中的第一个参数__main_block_func_0(上面贴了代码),其实就是具体的Block实现,传入到__main_block_impl_0,通过函数式(函数作为参数)的手法将其保存在impl.FuncPtr当中,方便随时调用。- 当
Block调用的时候,实际上调用的是block->FuncPtr,并将block结构体作为参数传入到方法实现中(之所以传入Block作为参数,在__main_block_func_0中有一个参数__cself,在Block内部就持有了Block所有的东西,相当于一个隐藏参数,在内部提供了更多的便利性)。
2. Block 捕获变量
1. 自动捕获变量
还是使用上面的示例代码,添加int a = 10,然后转化为.cpp文件
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("%d == 我来了",a);
}
__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a)多了一个参数a,而在__main_block_impl_0中同样自动生成了一个int a变量,这是在编译时就完成了。- 在
__main_block_impl_0构造函数中,传入了_a,通过C++的语法a(_a)进行了赋值。 - 在
__main_block_func_0实现中,通过__cself->取到a的值并赋值给新定义的a,这两个a并不是同一个a,地址不同,这是值拷贝。
2. __ block修改外部变量
在block中访问的外部变量是写操作不对原变量生效的,使用__block为什么就可以呢?
我们发现多了一个__Block_byref_a_0结构体,
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a)++;
printf("%d == 我来了",(a->__forwarding->a));
}
- 新定义的
__Block_byref_a_0类型的a进行结构体赋值,其中(__Block_byref_a_0 *)&a,是将int a = 10中a的地址复制给结构体中的第二个参数__forwarding。 - 在
__main_block_impl_0中传入的第三个参数&a就是新定义的这个__Block_byref_a_0类型的a的地址。 - 在
__main_block_impl_0结构体中多了__Block_byref_a_0 *a,而*a就是第三个参数传入的&a。 - 而在构造函数中
a(_a->__forwarding)进行赋值,在__main_block_func_0内部调用__Block_byref_a_0 *a = __cself->a中__cself->a取到的a与__cself->a地址相同,是指针拷贝,当调用(a->__forwarding->a)++,相当于外界a进行++。
2. 通过源码看Block
1. Block的copy操作
Block源码是开源的,可以再官网 libclosure 进行下载查看。
在上文通过clang查看代码的时候其实Block存在于Block_private.h中
我们搜索Block_layout
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor; //
// imported variables
};
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
// 可选
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
这与我们在C++中看到的代码基本相同
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
isa:指向父类的指针flags:记录状态的标志位reserved:保留字段invoke:执行代码块函数descriptor:block的附加描述信息
flags
它是以位域的形式存在,主要用来记录block的一切标志信息。
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
- 第1位:释放标记,一般常用
BLOCK_NEEDS_FREE做位与操作,一同传入Flags,告知该block可释放 - 第16位:存储引用计数的值,是一个可选用参数
- 第24位:第16位是否有效的标志,程序根据它来决定是否增加或是减少引用计数位的值
- 第25位:是否拥有拷贝辅助函数
- 第26位:是否拥有block析构函数
- 第27位:标志是否有垃圾回收
- 第28位:标志是否是全局block
- 第30位:与
BLOCK_USE_STRET相对,判断是否当前block拥有一个签名。用于runtime时动态调用
descriptor
会存在三种形式,BLOCK_DESCRIPTOR_1、BLOCK_DESCRIPTOR_2、BLOCK_DESCRIPTOR_3,后两者属于可选,内部包含有不同的函数。
#if 0
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;
}
#endif
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
🌹// 判断是否有 BLOCK_HAS_COPY_DISPOSE 标识
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1); 🌹//平移
return (struct Block_descriptor_2 *)desc;
}
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
🌹// 判断是否有 BLOCK_HAS_SIGNATURE 标识
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);🌹//平移
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
- 有标识码
BLOCK_HAS_COPY_DISPOSE,那么就会有BLOCK_DESCRIPTOR_2 - 有标识码
BLOCK_HAS_SIGNATURE,就会有BLOCK_DESCRIPTOR_3。 Block_descriptor_2和Block_descriptor_3是通过内存平移来获取。
当Block访问自动变量,会自动将Block从栈区拷贝。
下面我们看一段代码
我们通过汇编来看下流程
在上图断点位置,发现其中会调用objc_retainBlock,我们单步走来到objc_retainBlock,在此处读取x0,此时Block,的确如我们所想,是__NSStackBlock__。
过掉objc_retainBlock,我们再读取x0,此时Block已经变成了__NSMallocBlock__
那么我们设想,在objc_retainBlock做了一些操作,通过汇编进入objc_retainBlock内部看下,调用了_Block_copy
_Block_copy源码
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
🌹// 如果需要对引用计数进行处理,那就直接处理,处理完就返回
🌹// block的引用计数是不由runtime下层处理,需要自己处理
🌹// 这个地方处理的是堆区block
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
🌹// 如果是全局block 直接返回
return aBlock;
}
else {
// Its a stack block. Make a copy.
🌹// 栈区block 使用copy
🌹// 先在堆区初始化一块内存空间
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
🌹// 将栈区的数据copy到堆区的空间
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
🌹// 设置标志位
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
🌹// 设置为isa为_NSConcreteMallocBlock
result->isa = _NSConcreteMallocBlock;
return result;
}
}
- 全局Block在执行copy时:不作任何处理,直接返回
- 堆Block在执行copy时:会增加引用计数,然后返回
- 栈Block在执行copy时:
- 先会申请一片相同大小的内存空间,
- 然后将栈区的Block拷贝到堆区,
- 设置标志位的引用计数,
- 并执行
_Block_call_copy_helper对其中的_Block_descriptor_2进行copy操作 - 最后设置Block的isa指向为堆Block
2. Block三层拷贝
__block修饰的变量确实能在Block内部进行修改,原因在于会将这个变量重新封装成结构体,进行相应的指针拷贝,那内部是如何操作外部变量的呢?
在上面clang生产的.cpp文件中
这里的__main_block_copy_0和__main_block_dispose_0其实就是对应Block_descriptor_2中的copy和dispose,底层分别调用了_Block_object_assign和_Block_object_dispose。
_Block_object_assign
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:🌹 // 对象类型
/*******
id object = ...;
[^{ object; } copy];
********/
// objc 指针地址 weakSelf (self)
// arc
_Block_retain_object(object);
// 持有
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:🌹 // block类型
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// block 被一个 block 捕获
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:🌹
case BLOCK_FIELD_IS_BYREF:🌹 // __block类型
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:🌹
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:🌹
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:🌹
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:🌹
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
default:
break;
}
}
在_Block_object_assign会对类型进行判断,然后执行相应的流程
BLOCK_FIELD_IS_OBJECT对象类型 执行_Block_retain_objectBLOCK_FIELD_IS_BLOCKBlock类型 执行_Block_copyBLOCK_FIELD_IS_BYREF使用__block修饰的类型 执行_Block_byref_copy
BLOCK_FIELD_IS_OBJECT
_Block_retain_object
static void _Block_retain_object_default(const void *ptr __unused) { }
_Block_retain_object方法无任何实现,在ARC环境下,是有runtime底层来处理的。但值得注意的多一点,会对传入的objc进行指针拷贝,从而使该Block持有该对象。
BLOCK_FIELD_IS_BLOCK
如果修饰的是block,则直接调用_Block_copy方法,拷贝到堆区。
BLOCK_FIELD_IS_BYREF
如果是__block类型,会调用_Block_byref_copy方法。
static struct Block_byref *_Block_byref_copy(const void *arg) {
🌹//保存结构体对象
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
🌹// 1.申请堆内存空间
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
🌹// 2. 给新申请的空间赋值
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
🌹// 3.copy的对象和源对象都指向堆内存的拷贝地址
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
🌹 // 有copy能力
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
🌹// 4.处理desc2 内存偏移取值
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
🌹// 处理desc2 内存偏移取值
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
🌹// 如果已经在堆,则直接增加引用计数
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
- 保存外界传入的结构体
Block_byref - 申请堆内存空间
- 给新申请的空间赋值
- copy的对象和源对象都指向堆内存的拷贝地址
- 利用desc2和3的内存偏移取值
- 如果已经在堆,则直接增加引用计数。
如果有拷贝能力if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE),会进行进一步操作,最后执行(*src2->byref_keep)(copy, src);。
我们先看下面这段代码
转化为C++之后,我们发现在__Block_byref结构体中多了__Block_byref_id_object_copy和__Block_byref_id_object_dispose
而下层就会识别并调用byref_keep函数,而它就是Block_byref_2结构体的第一个元素,而它与__Block_byref_id_object_copy函数等价。
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep; // 结构体 __block 对象
BlockByrefDestroyFunction byref_destroy;
};
来到C++文件当中
我们发现在这里又调用了_Block_object_assign,代码在上面已经贴出,此时是对__block修饰的对象进行拷贝,为什么说拷贝的是修饰的对象呢?看下代码
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
struct __Block_byref_lg_name_0 {
void *__isa; // 8
__Block_byref_lg_name_0 *__forwarding; // 8
int __flags; // 4
int __size; // 4
void (*__Block_byref_id_object_copy)(void*, void*); // 8
void (*__Block_byref_id_object_dispose)(void*); // 5*8 = 40
NSString *lg_name;
};
在_Block_object_assign函数第一个参数传入(char*)dst + 40,这个dst就是__Block_byref_lg_name_0这个结构体,这里采用内存偏移40的方式正好取到lg_name,因此这里是多修饰的对象属性进行拷贝。
综上所述,对于__blcok修饰的对象,整个过程出现了三次拷贝:
第一层Block自身的拷贝 调用block_copy,从栈内存到堆内存。第二层__Block_byref结构体的拷贝__block修饰的变量会生成一个名为__Block_byref结构体,调用_Block_object_assign对其进行拷贝。第三层__block修饰的如果是对象,调用_Block_object_assign进行第三次拷贝,只不过此时在此方法中会走到case BLOCK_FIELD_IS_OBJECT:{}这个节点。
_Block_object_dispose
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
byref = byref->forwarding;
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);
}
free(byref);
}
}
}
- 如果是释放对象就什么也不做(自动释放)。
- 如果是
__block修饰,就将指向指回原来的区域并使用free释放。
五:总结
-
Block本质也是一个OC对象,封装了函数的调用和调用环境,内部也有isa指针 -
Block的变量捕获- 对于局部变量的捕获实在栈上保存的,ARC下回自动拷贝到堆中使用
- 对于静态变量,会进行指针拷贝,对值进行修改
- 对于全局变量,不会进行捕获,直接访问
-
Block根据存放位置不同,可以分为3种__NSGlobalBlock__( _NSConcreteGlobalBlock ):全局Block,无临时变量访问__NSStackBlock__( _NSConcreteStackBlock ):栈Block,访问临时变量__NSMallocBlock__( _NSConcreteMallocBlock ):堆Block,栈block调用copy
-
Block循环引用问题原因及处理- 原因:由于Block会对对象进行捕获,并根据其类型进行对应的copy到堆的处理,如果是强引用,对象则需要再block释放后才可以释放,如果对象也持有了block,则发生了循环引用
- 解决:当发生copy操作时,采用弱引用,则不会增加引用计数,同时在block内部强引用此时弱引用的对象,防止弱引用对象被释放而造成的数据问题,可以有效避免循环引用
-
Block的Copy操作也会根据Block的类型来进行处理-
全局Block在执行copy时:不作任何处理,直接返回
-
堆Block在执行copy时:会增加引用计数,然后返回
-
栈Block在执行copy时:
- 先会申请一片相同大小的内存空间,
- 然后将栈区的Block拷贝到堆区,
- 设置标志位的引用计数,
- 并执行
_Block_call_copy_helper对其中的_Block_descriptor_2进行copy操作 - 最后设置
Block的isa指向为堆Block
-
-
__block修饰原理:3层拷贝第一层Block自身的拷贝 调用block_copy,从栈内存到堆内存。第二层__Block_byref结构体的拷贝__block修饰的变量会生成一个名为__Block_byref结构体,调用_Block_object_assign对其进行拷贝。第三层__block修饰的如果是对象,调用_Block_object_assign进行第三次拷贝,只不过此时在此方法中会走到case BLOCK_FIELD_IS_OBJECT:{}这个节点。