block常见的使用方式以下:ios
// 无参无返回值
void(^MyBlockOne)(void) = ^(void) {
NSLog(@"无参数, 无返回值");
};
MyBlockOne();
// 有参无返回值
void (^MyBlockTwo)(int a) = ^(int a) {
NSLog(@"a = %d", a);
};
MyBlockTwo(10);
//有参有返回值
int (^MyBlockThree)(int, int) = ^(int a, int b) {
NSLog(@"return %d", a + b);
return 10;
};
MyBlockThree(10, 20);
// 无参有返回值
int (^MyBlockFour)(void) = ^(void) {
NSLog(@"return 10");
return 10;
};
// 声明为某种类型
typedef int (^MyBlock) (int, int);
@property (nonatomic, copy) MyBlock block;
复制代码
结论: block
的内部存在isa
指针,其本质就是封装了函数调用
和函数调用环境
的OC对象
。objective-c
main
函数中定义一个block
,以下swift
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^(void) {
NSLog(@"this is first block");
};
block();
}
return 0;
}
复制代码
终端进入项目所在目录,经过xcrun
命令将OC代码
转为C++代码
:markdown
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
复制代码
转换结果以下:iphone
// 1. block 的结构体
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;
}
};
// block 内部impl结构体,存储isa指针,block方法的地址。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; // 方法地址
};
// block 的描述信息,如: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)};
// 2. block 的方法实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hc_wwwl26516td3w0ds9cx80c280000gp_T_main_cf18a7_mi_0);
}
// 3. main方法的实现
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
复制代码
将生成的main
方法简化后得:async
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
block->FuncPtr(block);
}
return 0;
}
复制代码
看简化后的代码,你是否是有疑问, 为何block->FuncPtr(block)
这句话能调用成功,明明FuncPtr
是__block_impl
类型里的成员,为何能够直接使用block调用
?函数
缘由其实很简单,由于在block
结构体__main_block_impl_0
内,__block_impl
是第一个成员变量,所以block
的地址和impl
的地址是相同的。二者能够进行强制转换。oop
根据转换结果:测试
OC
中定义的block
底层其实就是一个C++ 的结构体__main_block_impl_0
。结构体有两个成员变量impl
、Desc
,分别是结构体类型 __block_impl
、__main_block_desc_0
。__block_impl
内包含了isa
指针和指向函数实现的指针FuncPtr
。__main_block_desc_0
内 Block_size
成员存储着Block的大小
。由上可知,block
内部有一个isa
指针,所以,block本质其实就是一个OC对象
。ui
若是block
是一个OC对象,那它最终确定继承自NSObject
类(NSProxy除外
),所以咱们能够直接打印出block
继承链看一下就知道了。
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^(void) {
NSLog(@"this is first block");
};
NSLog(@"class = %@", [block class]);
NSLog(@"superclass = %@", [block superclass]);
NSLog(@"superclass superclass = %@", [[block superclass] superclass]);
NSLog(@"superclass superclass superclass = %@", [[[block superclass] superclass] superclass]);
}
return 0;
}
输出结果:
2020-07-28 19:25:24.475317+0800 LearningBlock[39445:591948] class = __NSGlobalBlock__
2020-07-28 19:25:24.475707+0800 LearningBlock[39445:591948] superclass = __NSGlobalBlock
2020-07-28 19:25:24.475762+0800 LearningBlock[39445:591948] superclass superclass= NSBlock
2020-07-28 19:25:24.475808+0800 LearningBlock[39445:591948] superclass superclass superclass= NSObject
复制代码
block
的继承链: __NSGlobalBlock
-> NSBlock
-> NSObject
能够看出block最终继承自NSObject的
。isa指针
其实就是由NSObject
来的。 所以block
本质就是一个OC
对象。
为了保证block
内部可以正常访问外部的值,block
有个变量捕获的机制。下面来一块儿来探索如下block的变量捕获机制
。
代码:
int a = 10; // 全局变量, 程序运行过程一直存在内存。
int main(int argc, const char * argv[]) {
@autoreleasepool {
auto int b = 20; // 局部变量,默认是auto修饰,通常能够不写auto,所在做用域结束后会被销毁。
static int c = 30; // 静态变量,程序运行过程当中一直存在内存。
void(^block)(void) = ^(void) {
NSLog(@"a = %d, b = %d, c = %d", a, b, c);
};
// 观察调用block时,a,b,c 的值是多少呢?
a = 11;
b = 21;
c = 31;
block(); // 调用block
}
return 0;
}
打印输出:
2020-07-28 19:43:41.729849+0800 LearningBlock[39648:603167] a = 11, b = 20, c = 31
复制代码
由打印结果来看,b
没有改变, 而a
和c
的值都发生了变化。 缘由是什么呢?下面一块儿看下
运行下面的转换语句,将当前的OC代码转换C++, 方便咱们看到更本质的东西:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
复制代码
转换后的代码以下:
int a = 10; // 全局变量
struct __main_block_impl_0 { // block的结构体
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int b; // 新生成的成员变量b,用于存放外部局部变量b的值
int *c; // 新生成的成员变量c,指针类型, 用于存储外部静态局部变量c的引用。
// 构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _b, int *_c, int flags=0) : b(_b), c(_c) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int b = __cself->b; // 经过cself进行访问内部的成员变量b
int *c = __cself->c; // 经过cself获取静态局部变量c的引用
// 直接访问全局变量a
NSLog((NSString *)&__NSConstantStringImpl__var_folders_vt_j2sf07q142992_z55yg_170w0000gp_T_main_256a11_mi_0, a , b, (*c));
}
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)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
auto int b = 20;
static int c = 30;
void (*Myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, b, &c));
a = 11;
b = 21;
c = 31;
((void (*)(__block_impl *))((__block_impl *)Myblock)->FuncPtr)((__block_impl *)Myblock);
}
return 0;
}
复制代码
有上能够观察到:
block
结构体__main_block_impl_0
内部生成了新的成员变量b
和*c
, 分别用于存放外部传进来的b
和c的地址
,这就是咱们所说的捕获
。而对于全局变量a
则没有进行捕获,在使用时是直接访问的。
由此可得出:
block
内部对auto
和static
类型的变量进行了捕获
,可是不会捕获全局变量
。auto
和static
变量都进行了捕获,可是不一样的是,auto
变量是值传递,而static
变量则是地址传递。所以当外部的static
变量值发生变化时,block
内部也跟着会改变,而外部的auto
变量值发生变化,block
内部的值不会发生改变。相信你会有这样的疑问,为何block
会捕获auto
和static
类型的局部变量,而不会捕获全局变量呢?(全局变量
表示不服,block
你怎么搞区别对待呢?), 那么block
的变量捕获究竟有什么讲究呢?
实际上是这样的
auto
类型的局部变量,其生命周期过短了,离开了其所在的做用域后,auto
变量的内存就会被系统回收了,而block
的调用时机是不肯定的,若是block
不对它进行捕获,那么当block运行时再访问auto
变量时,由于变量已被系统回收,那么就会出现坏内存访问
或者获得不正确的值
。static
变量,由于其初始化以后,在程序运行过程当中就会一直存在内存中,而不会被系统回收,可是因为由于是局部变量的缘由,其访问的做用域有限,block想访问它就要知道去哪里访问,因此block才须要对其进行捕获,但与auto
变量不一样的是,block
只需捕获static
变量的地址便可。全局变量
,由于其在程序运行过程一直都在,而且其访问做用域也是全局的,因此block
能够直接找到它,而不须要对它进行捕获。因此,block
的变量捕获原则其实很简单,若是block
内部能直接访问到的变量,那就不捕获(捕获也是浪费空间
), 若是block内部不能直接访问到变量,那就须要进行捕获(不捕获就没得用
)。
block有3种类型,能够经过调用class
方法或者isa指针
查看具体类型,最终都是继承自NSBlock
类型.
为了准确分析block
的类型,先把ARC
给关闭,使用MRC
。
int main(int argc, const char * argv[]) {
@autoreleasepool {
auto int age = 10; // 局部变量,默认是auto,通常能够不写auto,所处做用域结束后会被销毁。
static int height = 20; // 静态变量,程序运行过程当中一直存在内存。
void(^block1)(void) = ^(void) {
NSLog(@"1111111111"); // 没有捕获了auto变量
};
void(^block2)(void) = ^(void) {
NSLog(@"age = %d", age); // 捕获了auto变量
};
void(^block3)(void) = ^(void) {
NSLog(@"height = %d", height); // 捕获了static变量
};
NSLog(@"block1 class: %@", [block1 class]); // __NSGlobalBlock__
NSLog(@"block2 class: %@", [block2 class]); // __NSStackBlock__
NSLog(@"block2 copy class: %@", [[block2 copy] class]); //__NSMallocBlock__
NSLog(@"block3 class: %@", [block3 class]); //__NSGlobalBlock__
}
return 0;
}
// 输出结果:
2020-07-28 20:41:43.283331+0800 LearningBlock[40390:637401] block1 class: __NSGlobalBlock__
2020-07-28 20:41:43.283755+0800 LearningBlock[40390:637401] block2 class: __NSStackBlock__
2020-07-28 20:41:43.283877+0800 LearningBlock[40390:637401] block2 copy class: __NSMallocBlock__
2020-07-28 20:41:43.283924+0800 LearningBlock[40390:637401] block3 class: __NSGlobalBlock__
复制代码
由上可知:
block
类型取值以下:
auto
变量,那么block
的为__NSGlobalBlock__
类型。auto
变量,那么block
为 __NSStackBlock__
类型。__NSStackBlock__
类型的block
进行copy
操做,则block
就会变成__NSMallocBlock__
类型.block
这几种类型的主要区别是:在内存中的存放区域不一样。(即生命的周期不一样)
__NSGlobalBlock__
存在数据段。__NSStackBlock__
存放在栈空间。__NSMallocBlock__
存放在堆空间。检验题:
新建一个Person类, 以下:
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)test;
@end
@implementation Person
- (void)test {
void (^block)(void) = ^{
NSLog(@"person name = %@", _name);
};
}
@end
复制代码
问题: 在Person.m
的test方法
中的block
对self
有没有进行捕获呢?
答案是有,block
会捕获self
. 分析以下:
首先将Person.m
经过xcrun
命令转换为C++
, 获得以下内容:
//test 方法内的block方法
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//test 方法
static void _I_Person_test(Person * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hc_wwwl26516td3w0ds9cx80c280000gp_T_Person_14871d_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block, sel_registerName("class")));
}
复制代码
观察转换后的代码能够看到:
(Person *self, SEL _cmd)
, 分别是方法的调用者 self
和 方法选择器 sel
。咱们平常使用的block通常是__NSMallocBlock__
类型的,缘由有以下:
__NSGlobalBlock__
类型的block
, 由于没有捕获auto
变量, 因此正常通常都是直接使用函数实现。__NSStackBlock__
类型的block
, 由于其存放在栈上,其内部使用变量容易被系统回收掉,从而致使一些异常的状况。好比下面:(要先将项目切成MRC,由于ARC下编译器会根据状况作copy操做,会影响分析)typedef void (^MJBlock)(void);
MJBlock block;
void test() {
int a = 10; // test方法结束后,a的内存就被回收了。
block = ^(void) {
NSLog(@"a = %d", a);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block(); // block里打印的是被回收了的a
}
return 0;
}
输出结果:
2020-09-27 10:05:28.616920+0800 Interview01-block的copy[7134:29679] a = -272632776
复制代码
__NSMallocBlock__
类型的block
, 由于它是存储在堆上,因此就不存在__NSStackBlock__
类型block
的问题。上面演示的是在MRC
环境下的, 那么在ARC
环境下又是如何的呢?
在ARC
环境下,编译器会自动根据状况将栈上的block
复制到堆上。好比一下状况:
block
做为函数返回值时。block
赋值给__strong
指针时。block
做为Cocoa API
中方法名含有usingBlock
的方法参数时。block
做为GCD API
方法参数时。typedef void (^MJBlock)(void);
MJBlock myblock()
{
int a = 10;
return ^{
NSLog(@"--------- %d", a); // 1. 做为方法返回值时。会自动copy
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
MJBlock block = ^{ // 2.赋值给strong指针时,会自动copy
NSLog(@"---------%d", age);
};
NSArray *arr = @[@10, @20];
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 3. block做为Cocoa API中方法名含有usingBlock的方法参数时。会自动copy
}];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 4. block做为GCD API方法参数时。会自动copy
});
}
return 0;
}
复制代码
根据上面的状况,在MRC
和ARC
下block属性
的写法能够有差别:
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void); // 赋值时会自动copy到堆上
ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
复制代码
基本数据类型
的auto
变量咱们已经分析了,那么对象类型
的auto
变量是否是和基本数据类型的同样仍是有什么特别之处呢?下面咱们一块儿来分析下:(记得先将工程切回ARC模式)
以下代码:
@interface LCPerson : NSObject
@property (nonatomic, assign) int age;
@end
@implementation LCPerson
- (void)dealloc {
NSLog(@"%s", __func__); // 销毁代码
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"11111111");
{
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
}
NSLog(@"22222222");
}
return 0;
}
// 输出结果:
2020-09-27 10:36:43.856070+0800 LearningBlock[16016:56873] 11111111
2020-09-27 10:36:43.856442+0800 LearningBlock[16016:56873] -[LCPerson dealloc]
2020-09-27 10:36:43.856474+0800 LearningBlock[16016:56873] 22222222
复制代码
咱们定义了一个LCPerson
类,在main.m
中作测试,由输出结果能够看出,person对象的释放是在1111111
和 22222222
之间, 这咱们应该均可以理解。(局部做用域)
咱们继续~
加入Block以后,咱们再观察一下。
typedef void (^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"11111111");
MyBlock block;
{
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
block = ^(void){
NSLog(@"person age = %d", person.age);
};
}
NSLog(@"22222222");
}
NSLog(@"3333333");
return 0;
}
输出结果:
2020-09-27 10:52:27.578241+0800 LearningBlock[20478:70040] 11111111
2020-09-27 10:52:27.578627+0800 LearningBlock[20478:70040] 22222222
2020-09-27 10:52:27.578688+0800 LearningBlock[20478:70040] -[LCPerson dealloc]
2020-09-27 10:52:27.578729+0800 LearningBlock[20478:70040] 3333333
复制代码
根据结果,咱们能够发现加入了block
以后,person
的销毁是在222222
以后发生的,即person
所在的做用域结束后,person
对象没有当即释放。 那么block
究竟对person
干了什么,致使person对象没能及时释放呢? 为了分析,咱们将上面的代码先简化一下。简化以下
int main(int argc, const char * argv[]) {
@autoreleasepool {
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
void (^block)(void) = ^(void){
NSLog(@"person age = %d", person.age);
};
block();
}
return 0;
}
复制代码
将上面OC代码转换为C++代码:(支持ARC、指定运行时系统版本)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
复制代码
转换后的C++代码以下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
LCPerson *__strong person; // strong类型的指针
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, LCPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
LCPerson *__strong person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_vt_j2sf07q142992_z55yg_170w0000gp_T_main_5882d6_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
LCPerson *person = ((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LCPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10);
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
复制代码
经过观察能够发现,block
内部对person
进行了捕获。而且与捕获基本数据类型的auto
变量不一样的是,捕获对象类型时__main_block_desc_0
结构体多了两个函数,分别是copy
和dispose
,这两个函数与被捕获对象的引用计数的处理有关。
block
从栈
上拷贝到堆
上时,copy
函数被调用,接着它会调用_Block_object_assign
函数,处理被捕获对象的引用计数,若是捕获变量时是使用__strong
修饰,那么对象的引用计数就会+1
. 若是捕获时是__weak
修饰,则引用计数不变。(下面会验证)block
被回收,即释放时,dispose
函数被调用,接着它会调用_Block_object_dispose
函数,若是捕获变量时是使用__strong
修饰,那么对象的引用计数就会-1
. 若是捕获变量时是__weak
修饰,则引用计数不变。(下面会验证)咱们知道,在ARC
环境下,将block
赋值给__strong
指针,block
会自动调用copy
函数。因此 person
对象离开了局部做用域后没有释放的缘由就很明确了,是由于block
调用copy
函数时,将person
对象的引用计数增长了1,因此当局部做用域结束时,person对象
的引用计数并不为0,所以不会释放。 而当block
的做用域结束,block
调用dispose
函数,将person
的引用计数减为0,而后person
才会释放。
如上面所说,那若是是在MRC
环境下,person
对象离开局部做用域后就会销毁了, 由于在MRC
环境下,将block
赋值给__strong
指针是不会触发copy
函数的,因此person
对象应该能够正常释放。
验证一: 将工程切换到MRC
模式下,测试刚才的代码,以下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"11111111");
MyBlock block;
{
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
block = ^(void){
NSLog(@"person age = %d", person.age);
};
[person release]; // MRC下须要手动管理内存
}
NSLog(@"22222222");
}
NSLog(@"3333333");
return 0;
}
// 输出结果:
2020-09-27 11:39:05.493388+0800 LearningBlock[33422:105156] 11111111
2020-09-27 11:39:05.493800+0800 LearningBlock[33422:105156] -[LCPerson dealloc]
2020-09-27 11:39:05.493833+0800 LearningBlock[33422:105156] 22222222
2020-09-27 11:39:05.493857+0800 LearningBlock[33422:105156] 3333333
复制代码
观察输出结果,和预料中的同样。person
对象离开局部做用域后正常释放。
验证二: 用weak
修饰的对象类型的auto
变量. (记得切回ARC环境)
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"11111111");
MyBlock block;
{
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
// 弱指针
__weak LCPerson *weakPerson = person;
block = ^(void){
NSLog(@"person age = %d", weakPerson.age);
};
}
NSLog(@"22222222");
}
NSLog(@"3333333");
return 0;
}
// 输出结果:
2020-09-27 12:00:20.461929+0800 LearningBlock[39325:122309] 11111111
2020-09-27 12:00:20.462321+0800 LearningBlock[39325:122309] -[LCPerson dealloc]
2020-09-27 12:00:20.462361+0800 LearningBlock[39325:122309] 22222222
2020-09-27 12:00:20.462391+0800 LearningBlock[39325:122309] 3333333
复制代码
观察输出结果,和预料中的同样。person
对象离开局部做用域后正常释放。
总结:
当block
内部访问了对象类型的auto
变量时
block
是在栈上,将不会对auto
变量产生强引用若是block
被拷贝到堆上
block
内部的copy
函数copy
函数内部会调用_Block_object_assign
函数_Block_object_assign
函数会根据auto
变量的修饰符(__strong
、__weak
、__unsafe_unretained
)作出相应的操做,造成强引用(retain
)或者弱引用
若是block
从堆上移除
block
内部的dispose
函数dispose
函数内部会调用_Block_object_dispose
函数_Block_object_dispose
函数会自动释放引用的auto
变量(release
)__block
能够用于解决block
内部没法修改auto变量
值的问题__block
不能修饰全局变量
、静态变量
(static
)__block
变量包装成一个对象.下面一块儿验证一下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 10;
void (^block)(void) = ^{
a = 20;
NSLog(@"a = %d", a);
};
block();
}
return 0;
}
// 输出结果:
a = 20
复制代码
将上面OC代码转换为C++代码:(支持ARC、指定运行时系统版本)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
复制代码
获得转换后结果:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref 这就捕获到的a的引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 20; // 修改值a的值。
NSLog((NSString *)&__NSConstantStringImpl__var_folders_vt_j2sf07q142992_z55yg_170w0000gp_T_main_ca9eb0_mi_0, (a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10}; // 这就是__block 修饰的a变量。
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); // 传入的是a变量的地址。
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
复制代码
由上面能够看到,OC
代码 __block int a = 10
转换为C++
以后变为了:
__Block_byref_a_0 a = {0, &a, 0, sizeof(__Block_byref_a_0), 10};
复制代码
__Block_byref_a_0
是一个结构体,结构以下:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
}
复制代码
因此在OC中用__block
修饰一个变量, 编译器会自动生成一个全新的OC对象。
__block
在block
中的内存管理和对象类型的auto
变量相似(但也有区别)。
block
在栈上时,并不会对__block
变量产生强引用block
被copy
到堆时
block
内部的copy
函数copy
函数内部会调用_Block_object_assign
函数_Block_object_assign
函数会对__block
变量造成强引用(retain)。(这点就是和对象类型的auto
变量有区别的地方,对于对象类型的auto
变量, _Block_object_assign
函数会根据auto变量的修饰符(__strong
、__weak
、__unsafe_unretained
)作出相应的操做, 而__block
则是直接强引用 )block
从堆中移除时
block
内部的dispose
函数dispose
函数内部会调用_Block_object_dispose
函数_Block_object_dispose
函数会自动释放引用的__block
变量(release
)经过上面咱们知道了用__block
修饰的基本数据类型
的处理。那用__block
修饰的对象类型的处理是否是同样的呢? 下面咱们一块儿看下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
void(^block)(void) = ^(void) {
NSLog(@"person age %d", person.age);
};
block();
}
return 0;
}
复制代码
经过xcrun
命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
复制代码
转换成C++后,获得结果以下:
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*); // 管理person的内存
void (*__Block_byref_id_object_dispose)(void*); // 管理person的内存
LCPerson *__strong person; //arc环境下, copy 和 dispose函数,会根据person的修饰类型(__strong、__weak)来对person作相应的内存管理。
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_person_0 *person; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__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_person_0 *person = __cself->person; // bound by ref // 这里就是强引用
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hc_wwwl26516td3w0ds9cx80c280000gp_T_main_213c56_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LCPerson"), sel_registerName("alloc")), sel_registerName("init"))};
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)(person.__forwarding->person), sel_registerName("setAge:"), 10);
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
复制代码
当block
从栈
拷贝到堆
上时,会调用block
的copy
方法,同时还会调用__Block_byref_person_0
结构体里的__Block_byref_id_object_copy
方法,__Block_byref_id_object_copy
内部会调用_Block_object_assign
方法,处理结构体__Block_byref_person_0
内部的person
指针所指对象的引用计数。
总结以下:
当__block
变量在栈上时,不会对指向的对象产生强引用
当__block
变量被copy
到堆时
__block
变量内部的copy
函数copy
函数内部会调用_Block_object_assign
函数_Block_object_assign
函数会根据所指向对象的修饰符(__strong
、__weak
、__unsafe_unretained
)作出相应的操做,造成强引用(retain
)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain
)若是__block
变量从堆上移除
__block
变量内部的dispose
函数dispose
函数内部会调用_Block_object_dispose
函数_Block_object_dispose
函数会自动释放指向的对象(release
)当block在栈上时,对它们都不会产生强引用
当block拷贝到堆上时,都会经过copy函数来处理它们
__block变量(假设变量名叫作a)
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);
对象类型的auto变量(假设变量名叫作p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
当block从堆上移除时,都会经过dispose函数来释放它们
__block变量(假设变量名叫作a)
_Block_object_dispose((void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);
对象类型的auto变量(假设变量名叫作p)
_Block_object_dispose((void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
在开发过程当中咱们常常会遇到block循环引用的问题, 以下:
typedef void (^MyBlock)(void);
@interface LCPerson : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, copy) MyBlock block;
@end
@implementation LCPerson
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"person age %d", person.age);
};
NSLog(@"211212121122");
}
return 0;
}
// 输出结果:
2020-09-28 20:01:48.358822+0800 LearningBlock[41115:298402] 211212121122
复制代码
由打印结果能够看出,person
并无释放(没有调用person的dealloc方法)。那是什么缘由致使的呢?是循环引用。 下面咱们来分析一下:
@property (nonatomic, copy) MyBlock block;
从这句话能够看出,person
强引用着block
.block
内部访问了person
对象的age
属性,根据上面所学,咱们知道block
会对person
进行捕获,而且在arc
环境下,block
赋值给__strong
指针时会自动调用copy
方法,将block
从栈拷贝到堆上, 这样会致使person
的引用计数加1,即block
强引用着person
。因此person
与block
相互强引用着,出现了循环引用,因此person
对象不会释放。
那么该如何解决呢? 下面说下在ARC
环境和MRC
环境分别如何处理?
在ARC
环境下,咱们能够经过使用关键字__weak
、__unsafe_unretained
来解决。以下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
__weak LCPerson *weakPerson = person;
// 或者 __unsafe_unretained LCPerson *weakPerson = person;
person.block = ^{
NSLog(@"person age %d", weakPerson.age);
};
NSLog(@"211212121122");
}
return 0;
}
// 打印结果:
2020-09-28 20:30:19.659679+0800 LearningBlock[41212:307877] 211212121122
2020-09-28 20:30:19.660256+0800 LearningBlock[41212:307877] -[LCPerson dealloc]
复制代码
示意图以下:
还可使用__block
解决, 以下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"person age %d", person.age);
person = nil;
};
person.block(); // 必须调用
NSLog(@"211212121122");
}
return 0;
}
// 打印结果:
2020-09-28 20:35:32.531704+0800 LearningBlock[41256:310297] person age 10
2020-09-28 20:35:32.532221+0800 LearningBlock[41256:310297] -[LCPerson dealloc]
2020-09-28 20:35:32.532310+0800 LearningBlock[41256:310297] 211212121122
复制代码
使用__block
解决,必须调用block,否则没法将循环引用
打破。
疑问: __weak
和__unsafe_unretained
关键字有什么区别呢?
使用__weak
和__unsafe_unretained
关键字都能达到弱引用的效果。这二者主要的区别在于,使用__weak
关键字修饰的指针,在所指的对象销毁时,指针存储的地址会被清空(即置为nil), 而__unsafe_unretained
则不会。
__weak
关键字的,因此可使用__unsafe_unretained
关键字解决。(与ARC差很少,这里就不演示了)__block
关键字解决。以下:int main(int argc, const char * argv[]) {
@autoreleasepool {
__block LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"person age %d", person.age);
person = nil;
};
[person release]; // MRC须要手动添加内存管理代码
NSLog(@"211212121122");
}
return 0;
}
复制代码
与ARC
不一样的是,MRC
下使用__block
解决循环引用问题,不要求必定要调用block
。缘由上面__block修饰的对象类型
里有说到:
_Block_object_assign
函数会根据所指向对象的修饰符(__strong
、__weak
、__unsafe_unretained
)作出相应的操做,造成强引用(retain
)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain
)
这篇文章有点乱,还有待改进。写博客真的费时间,不过能加深印象,也不错。
轻松一刻: