block本质
block是封装了函数调用以及函数调用环境的OC对象(它内部也有个isa指针)
函数调用环境:函数调用需要什么(比如参数、需要外部值)
比如main函数里有block
int main(int argc, char *argv[])
{
NSString *appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
int age = 10;
void (^myBlock)() = ^(){
NSLog(@"This is Block! age = %d",age);
};
myBlock();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
上面block那几句代码在转C++的cpp文件里大致是这样的
int age = 10;
//block定义
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));//这里的括号里面加括号可以简单理解为在强转,主要就是&__main_block_impl_0这个东西,相当于拿到__main_block_impl_0函数地址传给左边的myBlock,搜索__main_block_impl_0函数可以看到是__main_block_impl_0结构体里的构造函数。第一个参数是block里的方法地址;第二个参数是block的大小相关的描述;第三个参数是外界值。因为__main_block_impl_0函数是结构体里的初始化函数,所以就是把结构体的地址赋值给了左边的myBlock。
//block调用
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);//可以看到这个调用很奇怪,去掉强制转换后可以简写为myBlock->FuncPtr(myBlock),可能为很奇怪,明明FuncPtr是在__main_block_impl_0结构体里的,为啥能直接通过myBlock->FuncPt拿呢,首先可以看到myBlock->FuncPt前的强制转换,强制把myBlock转成了(__block_impl *)型,这时候会更好奇了,__block_impl和__main_block_impl_0是不一样的结构体,为啥能转,其实是因为__block_impl是__main_block_impl_0的第一个元素,__main_block_impl_0的地址值就是__block_impl的地址值,所以可以进行强转。强转之后调用FuncPt,其实就是调用__main_block_func_0函数,里面需要传一个block。
第一个参数长下面这样,可以看到就是myBlock里的NSLog函数。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_1a84fd_mi_0,age);
}
//展开__main_block_impl_0结构体可以看到定义如下
//__main是文件里的方法名带过来的,如果是别的方法,__main会被换成别的名字。_0的这个0代表main中的第几个block,如果再有一个block,会变成_1
struct __main_block_impl_0 {
//__block_impl结构体没有用*修饰impl,相当于直接把__block_impl放到这里面(但是如果直接考进来,可能会有内存不对等)
struct __block_impl impl;
//__main是文件里......
struct __main_block_desc_0* Desc;
//外界定义的变量
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa; //ISA指针
int Flags;
int Reserved;
void *FuncPtr; //block内部一些函数方法地址的指针
};
static struct __main_block_desc_0 { //__main是文件里的方法名带过来的,如果是别的方法,__main会被换成别的名字
size_t reserved;
size_t Block_size; //block占用大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; //0赋值给reserved;sizeof(struct __main_block_impl_0)赋值给Block_size
为了便于学习,上面代码可以简洁如下
struct __main_block_impl_0 {
struct __block_impl impl; //__block_impl结构体没有用*修饰impl,相当于直接把__block_impl放到这里面(但是如果直接考进来,可能会有内存不对等)
struct __main_block_desc_0* Desc; //__main是文件里......
int age; //外界定义的变量
};
struct __block_impl {
void *isa; //ISA指针
int Flags;
int Reserved;
void *FuncPtr; //block内部一些函数方法地址的指针
};
static struct __main_block_desc_0 { //__main是文件里的方法名带过来的,如果是别的方法,__main会被换成别的名字
size_t reserved;
size_t Block_size; //block占用大小
}
如果block接收参数呢
int main(int argc, char *argv[])
{
NSString *appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
int age = 10;
void (^myBlock)(int,int) = ^(int a,int b){
NSLog(@"This is Block! age = %d, a = %d, b = %d",age,a,b);
};
myBlock(11,12);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
将上述代码转cpp文件后看到如下
int age = 10;
//block定义
void (*myBlock)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));//相较上面,也就是左边myBlock和传递方法地址时多了(int, int)。
//block调用
((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 11, 12);//强转以及调用多了俩接收参数
//第一个参数长下面这样,可以看到就是myBlock里的NSLog函数。相比无传参的block,多了接收外界值。
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_02897f_mi_0,age,a,b);
}
//__main_block_impl_0结构体并没有变化。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//__block_impl结构体并没有变化。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//__main_block_desc_0结构体并没有变化。
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 age = 10;
void (^block)(void) = ^{
NSLog(@"age is %d", age);
};
age = 20;
block(); //调用block结果是 age is 10
针对这种原因是因为block块代码在生成的时候,已经在内部生成了一个自己的int age ,然后把外界传进来的局部变量age=10赋值进去,所以在调用block()执行的时候是调用了block内部的age属性
看cpp文件就知道
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_1a84fd_mi_0,age);
}
可以看到是int age = __cself->age;,这个__cself就是block,给age赋值时取得是block内部的age
static静态变量
静态变量优点:
1、节省内存。静态变量只存储一处,但供所有对象使用。
2、它的值是可以更新的。
3、可提高时间效率。只要某个对象对静态变量更新一次,所有的对象都能访问更新后的值。
静态变量static的使用
1、修饰局部变量
让局部变量只初始化一次,局部变量在程序中只有一份内存,但是并不会改变局部变量的作用域,仅仅是改变了局部变量的生命周期(只到程序结束,这个局部变量才会销毁)。
2、修饰全局变量
全局变量的作用域仅限于当前文件
block的变量捕获(capture)
变量类型 捕获到block内部 访问方式
局部变量 auto变量 是 值传递
static变量 是 指针传递
全局变量 否 直接访问
auto:自动变量,离开作用域自动销毁。所以一般定义的局部变量都是默认auto。 auto int age 等于 int age; 注意:auto只存在于局部变量里面
定义局部变量
auto int age = 10;
static int num = 20;
看另外一个例子
int age = 10; //此处局部变量
static int height = 10; //此处局部变量
void (^block)(void) = ^{
NSLog(@"age is %d,height is %d", age,height);
};
age = 20;
height = 20;
block(); //调用block结果是 age is 10,height is 20
针对这种原因是因为block块代码在生成的时候,已经在内部生成了一个自己的int age和 int *height,然后把外界传进来的局部变量age=10以及height的指针赋值进去,所以在调用block()执行的时候是调用了block内部的age属性以及height的指针,从而找到height = 20
同样都是局部变量,为什么auto和static会有这么大的差异呢?
auto作用域太小,为了防止需要访问时内存已被销毁,所以需要在block内部自己定义一份。static能够常驻内存不用担心释放,所以直接指针传递就行了。
为了验证上述问题,看下上面代码转成的cpp文件
int age = 10;
static int height = 10;
void (* myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));//可以看到age传的是值,height传的是地址
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
其中__main_block_impl_0如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age; //可以看到age是值
int *height; //可以看到height是地址
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
block内部方法如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; //拿到值
int *height = __cself->height; // 拿到指针
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_42835e_mi_0, age,(*height));//打印时height指针地址里存的值
}
再看全局变量问题
#import
int age_ = 10;
static int height_ = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"age_ is %d, height_ is %d", age_, height_);
};
age_ = 20;
height_ = 20;
block(); //打印都是20
}
return 0;
}
针对这种原因是因为block块代码在生成的时候,不会在block内部捕获,直接访问外界全局变量
为了验证上述问题,看下cpp文件
void (* myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));//可以看到并无传值
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
其中__main_block_impl_0如下
struct __main_block_impl_0 {//__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内部方法如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_a9afb9_mi_0, age_,height_);//方法里是直接拿外界值。
}
为什么局部变量一定会被捕获到block内部,全局变量不用 被捕获?
因为作用域不同,针对局部变量,如果跨域去访问,必须捕获到block内部才能保证正常访问,全局变量则不用担心这个问题。
至于auto需要值传递是因为作用域太小,生命周期太短,不进行值传递跨域访问时会出问题;static局部变量虽然作用域也是很小,但是生命周期因为static的原因能常驻内存,所以没必要像auto一样累赘的值传递,直接地址传递就行了。
比如一个MJPerson.h类,有一个test方法
- (void)test
{
void (^block)(void) = ^{
NSLog(@"-------%p", self);
};
block();
}
那么请问调用的这个self会捕获吗?
会被捕获。
简单看下转成cpp是什么样的
针对test方法在cpp里面是怎么定义的,如下
static void _I_MJPerson_test(MJPerson * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__MJPerson__test_block_impl_0((void *)__MJPerson__test_block_func_0, &__MJPerson__test_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
可以看到test默认接收了俩参数,一个自身 MJPerson * self,一个SEL _cmd,因为参数也被当做是局部变量,所以test里的self会被捕获。
可以看到block初始化时里有四个参数,第三个是self,第四个是SEL _cmd
看下__MJPerson__test_block_impl_0定义如下,捕获了self,至于SEL _cmd则没有要,因为没必要。
struct __MJPerson__test_block_impl_0 {
struct __block_impl impl;
struct __MJPerson__test_block_desc_0* Desc;
MJPerson *self;//MJPerson *原因为self在外部类型就是这样的,并不是指针传递
__MJPerson__test_block_impl_0(void *fp, struct __MJPerson__test_block_desc_0 *desc, MJPerson *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
再看个例子一个MJPerson.h类,有一个test方法,有一个name属性
@property (copy, nonatomic) NSString *name;
那么如下调用,name会被捕获吗?
- (void)test
{
void (^block)(void) = ^{
NSLog(@"-------%d", _name);
};
block();
}
不会单独针对name捕获,会将整个self捕获,因为_name访问方式等同于self->_name,self会被捕获,那么_name也就被包含了。 _name并不能等同于全局变量的。
那么这个例子呢?
- (void)test
{
void (^block)(void) = ^{
NSLog(@"-------%@", [self name]);
};
block();
}
不会单独针对name捕获,会将整个self捕获,虽然通过[self name]调用,但是[self name]本质走的是消息转发流程mbjc_msgSend(self,sel_regsterName("name")),self为主体在且在block内部被用到,所以self作为参数被当做局部变量进行捕获了。
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
__NSGlobalBlock__ (等价于 _NSConcreteGlobalBlock )
__NSMallocBlock__ (等价于 _NSConcreteMallocBlock )
__NSStackBlock__ (等价于 _NSConcreteStackBlock )
既然说block是一个oc对象,那么可以调用class看下这个类的特征,以及父类什么的。
void (^block)(void) = ^{
NSLog(@"Hello");
};
NSLog(@"%@", [block class]); // __NSGlobalBlock__ 这几个打印都是在ARC下打印的
NSLog(@"%@", [[block class] superclass]);//__NSGlobalBlock
NSLog(@"%@", [[[block class] superclass] superclass]);//NSBlock
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);//NSObject ,看到最后继承于NSObject,也可以很好地说明block是OC对象。至于block里的isa指针也是继承NSObjects时带过来的。
看一个例子
void (^block1)(void) = ^{
NSLog(@"Hello");
};
int age = 10;
void (^block2)(void) = ^{
NSLog(@"Hello - %d", age);
};
NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
NSLog(@"%d", age); //这几个打印都是在ARC下打印的
} class]);//打印结果__NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
我们把上述代码转c++代码后看到isa指针指向的都是impl.isa = &_NSConcreteStackBlock;并没有区分三种类别。其实通过clang转的C++并不一定是真正编译运行的c++,只能作为一种参考,一切以运行时为准,运行的时候上述三种block是三种类别。
先讲一下内存分配
四块区域
低地址 ___
| |___| 程序区域.text块 (代码段、函数方法)
| |___| 数据区域.data块 (全局变量) <-存在数据区域 __NSGlobalBlock__
\|/ |___| 堆 (动态分配内存,比如alloc生成的,由开发者申请释放) <-存在堆 __NSMallocBlock__
高地址 |___| 栈(比如局部变量,程序自动管理) <-存在栈 __NSStackBlock__
针对三种block在内存中怎么分配的呢?
__NSGlobalBlock__存在 数据区域.data块
__NSMallocBlock__存在 堆
__NSStackBlock__ 存在 栈
那block怎么区分类型呢?
访问了auto变量的block是 NSStackBlock (有些情况下需要关闭ARC才能看到正确打印,不关闭ARC有时候NSStackBlock调用copy后打印的是NSMallocBlock)
调用了copy的block是 NSMallocBlock (注意:NSGlobalBlock调用copy依旧是NSGlobalBlock,什么都不会变化;NSMallocBlock调用copy依旧是NSMallocBlock,只是引用计数器加1,之所以引用计数器加1是因为NSMallocBlock已经是在堆上了)
没有访问auto变量的block是 NSGlobalBlock (访问static局部变量依旧是GlobalBlock)
新建一个mac命令行程序(因为不用启动模拟器)
MRC环境下:
#import
void (^block)(void);
void test() {
int age = 10;
block = ^{
NSLog(@"age = %d",age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
}
return 0;
}
运行程序可以看到打印
2019-11-29 11:17:41.051430+0800 testss[2475:81438] age = -272632488
为什么会这样呢,因为block访问了auto变量,成了NSStackBlock被存放在了栈区,当调用完test();后,block随着test函数的结束,作用域也结束。所以这时候在访问block();就会看到内存访问混乱。
针对上面问题,访问了auto变量的block是 NSStackBlock 被放在了栈内存中,随时都会被销毁,所以跨域访问会出现各种问题,所以如果把NSStackBlock放在堆里,就不会出现跨域异常问题。即将NSStackBlock copy一下就变成了NSMallocBlock放在堆上了
block = [^{
NSLog(@"age = %d",age);
} copy];
MRC下:
void (^block)(void) = ^{
NSLog(@"-------");
};
int age = 10;
void (^block1)(void) = ^{
NSLog(@"-------,%d",age);
};
void (^block2)(void) = [^{
NSLog(@"-------,%d",age);
} copy];
NSLog(@"%@ %@ %@", [block class], [block1 class], [block2 class] );
2019-11-29 10:50:36.169072+0800 Test[2052:64456] __NSGlobalBlock__ __NSStackBlock__ __NSMallocBlock__
ARC下
void (^block)(void) = ^{
NSLog(@"-------");
};
int age = 10;
__weak void (^block1)(void) = ^{ //如果不加__weak,这里会打印成__NSMallocBlock__
NSLog(@"-------,%d",age);
};
void (^block2)(void) = [^{
NSLog(@"-------,%d",age);
} copy];
NSLog(@"%@ %@ %@", [block class], [block1 class], [block2 class] );
2019-11-29 10:48:22.019517+0800 Test[1997:62412] __NSGlobalBlock__ __NSStackBlock__ __NSMallocBlock__
上面的情况,有一个注意点
__weak int age = 1; //这种写法会有警告,因为__weak只是用来修饰对象的。
void (^block1)(void) = ^{
NSLog(@"-------,%d",age);
};
依旧是__NSMallocBlock__。因为左边有强指针,所以会copy,copy后会根据age是弱指针,导致弱引用age,但是这里age是基础数据类型,所以desc里没有copy和dispose。
如果是下面这种是对象类型,就会copy到堆上后弱引用person
__weak MJPerson *person = [[MJPerson alloc]init];
void (^ block1)(void) = ^{
NSLog(@"-------,%p", person);
};//__NSMallocBlock__
在ARC环境下,编译器会根据情况自动对cblock进行copy操作,比如以下情况
1.block作为函数返回值时
#import
typedef void (^MJBlock)(void); //定义全局block
MJBlock getBlock() { //声明一个c语言函数,返回值是MJBlock类型
return ^{ //用一个block作为返回值
NSLog(@"----");
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJBlock block = getBlock(); //调用这个c语言函数,返回值是MJBlock类型
block(); //然后调用这个block
}
return 0;
}
上述情况,我们在main函数调用block时
// MJBlock block = getBlock();
// block();
系统会自动copy
2.将block赋值给__strong指针时
#import
typedef void (^MJBlock)(void);
int main(int argc, const char *argv[])
{
@autoreleasepool {
int age = 10;
MJBlock block = ^{ //并不一定左边的block要是全局,局部变量的默认也是strong
NSLog(@"---------%d", age);
};
block();
}
return 0;
}
3.block作为Cocoa API中方法名含有usingBlock的方法参数时
例如: NSArray *arr = @[@1,@2,@3,@4];
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
4.block作为GCD API的方法参数时
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
在使用clang转换OC为C++代码时,可能会遇到以下问题
cannot create __weak reference in file using manual reference
解决方案:支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
如上如果指定运行时版本的话,可以看到生成的cpp文件的变量内存属性
来看个例子
MJPerson里有个age
然后如下代码
#import
#import "MJPerson.h"
typedef void (^MJBlock)(void);
int main(int argc, const char *argv[])
{
@autoreleasepool {
MJBlock myBlock;
{
MJPerson *person = [[MJPerson alloc]init];
person.age = 2;
myBlock = ^{
NSLog(@"age = %d", person.age);
};
}
NSLog(@"--------");
}
return 0;
}
如上,ARC下person会在代码执行到NSLog断点处时销毁吗?
不会。
那么MRC下person会在代码执行到NSLog断点处时就销毁吗?
会。
首先看下指定运行时版本转的cpp文件
int main(int argc, const char *argv[])
{
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MJBlock myBlock;
{
MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 2);
myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_2b9a90_mi_1);
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MJPerson *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
如果在ARC下把代码改造成下面样式
#import
#import "MJPerson.h"
typedef void (^MJBlock)(void);
int main(int argc, const char *argv[])
{
@autoreleasepool {
MJBlock myBlock;
{
MJPerson *person = [[MJPerson alloc]init];
person.age = 2;
__weak MJPerson *weakPerson = person;
myBlock = ^{
NSLog(@"age = %d", weakPerson.age);
};
}
NSLog(@"--------");
}
return 0;
}
如上,ARC下person会在代码执行到断点处时销毁吗?
会。
首先看下指定运行时版本转的cpp文件
int main(int argc, const char *argv[])
{
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MJBlock myBlock;
{
MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 2);
__attribute__((objc_ownership(weak))) MJPerson *weakPerson = person;
myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakPerson, 570425344));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_573c8b_mi_1);
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MJPerson *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
NSStackBlock存放在栈区,不会"强"持有block内部访问外界的auto对象(不会"强"持有意思就是会持有,但不是强引用);NSMallocBlock存放在堆区,会持有,会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作。(注意点:只要是存放在栈区NSStackBlock,哪怕捕获到变量是__strong也没用,也不会强持有外界的auto对象)
当block内部访问了对象类型的auto变量时
如果block是在栈上,将不会对auto变量产生强引用
下面看另一种现象 ARC下
#import
typedef void (^MJBlock)(void);
int main(int argc, const char *argv[])
{
@autoreleasepool {
int age = 2;
MJBlock myBlock = ^{
NSLog(@"age = %d", age);
};
}
return 0;
}
转成的cpp文件
int main(int argc, const char *argv[])
{
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 2;
MJBlock myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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)};
那如果访问的不是基础类型而是对象呢,arc下
#import
#import "MJPerson.h"
typedef void (^MJBlock)(void);
int main(int argc, const char *argv[])
{
@autoreleasepool {
MJPerson *person = [[MJPerson alloc]init];
person.age = 2;
MJBlock myBlock = ^{
NSLog(@"age = %d", person.age);
};
}
return 0;
}
转成的cpp文件
int main(int argc, const char *argv[])
{
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 2);
MJBlock myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MJPerson *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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};
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*/);}
可以看到将block拷贝到堆上时,内部如果捕获的是对象时,__main_block_desc_0产生了变化。多了一个copy指针,一个dispose指针。copy指针指向__main_block_copy_0函数;dispose指针指向__main_block_dispose_0函数。copy内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符来自动做出相应的操作,形成强引用或者弱引用。当block被从堆中移除时,会调用dispose指针指向的__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose进行释放。
在下面连接上可以看到开源的_Block_object_assign函数的定义
http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/runtime.c
/*
* When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
* to do the assignment.
*/ //_Block_object_assign三个参数:参数1:目标地址;;参数2:源对象地址;参数3:flags的不同确定后方走什么路
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
//printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
}
else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy(destAddr, object, flags);
}
// (this test must be before next one)
else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
// copying a Block declared variable from the stack Block to the heap
_Block_assign(_Block_copy_internal(object, flags), destAddr);
}
// (this test must be after previous one)
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
//printf("retaining object at %p\n", object);
_Block_retain_object(object);
//printf("done retaining object at %p\n", object);
_Block_assign((void *)object, destAddr);
}
}
其中
The flags parameter of _Block_object_assign and _Block_object_dispose is set to
* BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,
* BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
* BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.
If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16).
如果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)
针对__main_block_desc_0方法,如果block内部访问的是外界基础数据类型,内部会
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; //block占用大小
};
如果block内部访问的是外界对象类型,内部会
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函数直接return0了,根本不给你机会等几秒后
例1
//ARC
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
MJPerson *person = [[MJPerson alloc]init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---%@",person);
});
NSLog(@"开始点击");
}
可以看到打印,person 3秒后 销毁
2019-11-29 17:31:11.593005+0800 Test[6177:250328] 开始点击
2019-11-29 17:31:14.593211+0800 Test[6177:250328] ---
2019-11-29 17:31:14.593349+0800 Test[6177:250328] MJPerson----dealloc
例2
//ARC
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
__weak MJPerson *person = [[MJPerson alloc]init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---%@",person);
});
NSLog(@"开始点击");
}
可以看到打印,点击前MJPerson已经销毁
2019-11-29 17:32:07.765767+0800 Test[6198:251170] MJPerson----dealloc
2019-11-29 17:32:07.765942+0800 Test[6198:251170] 开始点击
2019-11-29 17:32:10.765957+0800 Test[6198:251170] ---(null)
例3
//ARC
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
__weak MJPerson *person = [[MJPerson alloc]init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---%@",person);
});
});
NSLog(@"开始点击");
}
可以看到打印,点击前MJPerson已经销毁
2019-11-29 17:34:08.055245+0800 Test[6236:252828] MJPerson----dealloc
2019-11-29 17:34:08.055468+0800 Test[6236:252828] 开始点击
2019-11-29 17:34:11.055688+0800 Test[6236:252828] ---(null)
例4
//ARC
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
MJPerson *p = [[MJPerson alloc] init];
__weak MJPerson *weakP = p;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1-------%@", weakP);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2-------%@", p);
});
});
} //在这个例子里,gcd被copy到堆。因为延迟3秒的p是强引用,所以MJPerson在三秒后才释放,weakP和p打印结果一样
//ARC
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
MJPerson *p = [[MJPerson alloc] init];
__weak MJPerson *weakP = p;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1-------%@", p);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2-------%@", weakP);
});
});
}//在这个例子里,gcd被copy到堆。因为延迟1秒的p是强引用,所以MJPerson在一秒后才释放,p有值,weakP为null
默认情况下,block不能修改auto局部变量(全局和static静态是可以在block内部直接修改的)
如果想要修改需要
1.将auto局部变量变成static的局部变量或者使用全局变量
2.使用__block来修饰auto局部变量
为什么__block可以做到?下面通过代码来解释。
__block
下面是"普通数据类型"的"auto变量"
int main(int argc, char *argv[])
{
NSString *appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
int age = 10;
void(^myBlobk)(void) = ^{
NSLog(@"age = %d",age);
};
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
将上述代码转cpp文件后看到如下
int main(int argc, char *argv[])
{
NSString *appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
int age = 10;
void(*myBlobk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
其中__main_block_impl_0如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
其中__block_impl如下
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
其中__main_block_desc_0如下
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)};
其中__main_block_func_0如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_71806c_mi_0,age);
}
如果用"__block"来修饰"普通数据类型"的"auto变量"呢?如下
int main(int argc, char *argv[])
{
NSString *appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
__block int age = 10;
void(^myBlobk)(void) = ^{
age = 20; //多了一句修改值
NSLog(@"age = %d",age);
};
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
将上述代码转cpp文件后看到如下
int main(int argc, char *argv[])
{
NSString *appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
//int age = 10;加上__block后 __block int age = 10;在main函数里就变成了下面这句话,右边是在给__Block_byref_age_0结构体初始化。第二个参数是将自己的地址传给了 。
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
void(*myBlobk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344)); //在捕捉时,已经从捕捉age变成捕捉__Block_byref_age_0的指针了。
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
其中__main_block_impl_0如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
其中__block_impl如下
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
其中__main_block_desc_0如下
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*); //相较访问普通类型auto,加了__block的auto普通类型在底层已经是个对象了,所以需要copy和dispose管理内存
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_byref_age_0如下
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding; //指向自身的指针。至于为什么,是因为存在__Block_byref_age_0结构体从栈上拷贝到堆上的问题。当在栈上时,__forwarding指向栈上的自身取值,没有问题,当__Block_byref_age_0要拷贝到堆上时,这时候修改栈上的__forwarding指向堆上的__Block_byref_age_0结构体,这时候访问,栈上的__forwarding和堆上的__forwarding都指向堆自身,这样不管__block怎么复制到堆上,还是在栈上,都可以通过(结构体指针->__forwarding->值)来访问到变量值。__forwarding指针的存在意义是确保能正确的访问__block变量。
int __flags;
int __size;
int age;
};
其中__main_block_func_0如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // 先通过__cself结构体里的age拿到指针
(age->__forwarding->age) = 20;//拿到指针后再拿到指向自身的__forwarding,然后再取出里面的age进行修改值
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_002bd3_mi_0,(age->__forwarding->age));
}
如果用"__block"来修饰"auto对象"呢?如下
int main(int argc, char *argv[])
{
NSString *appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
__block MJPerson *person = [[MJPerson alloc]init];
void(^myBlobk)(void) = ^{
person = nil;
NSLog(@"person = %@",person);
};
myBlobk();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
将上述代码转cpp文件后看到如下
int main(int argc, char *argv[])
{
NSString *appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
//右边是在给__Block_byref_person_0结构体初始化,相较于用"__block"来修饰"普通数据类型"的"auto变量",这里的结构体里多了copy和dispose函数,而在自赋值初始化过程中,__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131函数内部调用的也是_Block_object_assign函数,这不过这个结构体里的copy和dispose用来管理__Block变量,而这整个结构体因为包装成对象所以当被拷贝到堆上时是强引用给block。
__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, ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"))};
void(*myBlobk)(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 *)myBlobk)->FuncPtr)((__block_impl *)myBlobk);
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
其中__main_block_impl_0如下
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;
}
};
其中__block_impl如下
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
其中__main_block_desc_0如下
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_byref_person_0如下
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);//唯一不同点就在这儿,desc里面的copy和dispose管理的是外界捕获的值的内存也就是__Block_byref_person_0结构体的内存。而__Block_byref_person_0结构体里的copy和dispose管理的是__Block_byref_person_0结构体里捕获的真正用到的对象的内存。
//如果是普通类型auto,即便是加了__Block,__Block_byref_person_0结构体里也不会有copy和dispos的,只有desc里会有。
void (*__Block_byref_id_object_dispose)(void*);//唯一不同点就在这儿,desc里面的copy和dispose管理的是外界捕获的值的内存也就是__Block_byref_person_0结构体的内存。而__Block_byref_person_0结构体里的copy和dispose管理的是__Block_byref_person_0结构体里的对象的内存。
MJPerson *__strong person; //可以看到person是强引用
};
其中__main_block_func_0如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_person_0 *person = __cself->person; // bound by ref
(person->__forwarding->person) = __null;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_315fcb_mi_0,(person->__forwarding->person));
}
//结构体里的copy和dispose函数调用方法
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
如果用"__block"来修饰"__weak auto对象"呢?如下
int main(int argc, char *argv[])
{
NSString *appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
__block __weak MJPerson *person = [[MJPerson alloc]init]; //__weak用来修饰person指针是弱的
void(^myBlobk)(void) = ^{
person = nil;
NSLog(@"person = %@",person);
};
myBlobk();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
将上述代码转cpp文件后看到如下
int main(int argc, char *argv[])
{
NSString *appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
//包装成__Block_byref_person_0结构体时标识了一下结构体内部的person是weak
__attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __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, ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"))};
void(*myBlobk)(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 *)myBlobk)->FuncPtr)((__block_impl *)myBlobk);
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
其中__main_block_impl_0如下
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;
}
};
其中__block_impl如下
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
其中__main_block_desc_0如下
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_byref_person_0如下
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
MJPerson *__weak person; //此处是weak。这也是相较于__strong的auto对象最大的不同之处。
};
其中__main_block_func_0如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_person_0 *person = __cself->person; // bound by ref
(person->__forwarding->person) = __null;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_pf6sbvtj611_l7bnqctfq_km0000gn_T_main_e40e2a_mi_0,(person->__forwarding->person));
}
//结构体里的copy和dispose函数调用方法
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
总结就是编译器会将__block变量包装成一个对象,对象里面有指向自己的指针和变量,修改的话通过指针找到自己再找到变量,进行修改
注意:__block不能修饰全局变量、静态变量(static),但可以修饰任何类型的自动变量。
至于上面那么多代码分析,所以__block的内存管理总结如下
__block的内存管理
1.当block在"栈"上时,并不会对包含__block变量的结构体产生"强"引用(不会"强"引用意思就是会引用,但不是强引用)
2.当block被copy到堆时 会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对包含__block变量的那个结构体形成强引用。然后根据__block变量的修饰符,在结构体里根据结构体里自身的copy和dispose函数对__block变量形成不同引用(至于强弱引用,看修饰符,strong强引用(此处是特殊情况,仅限于ARC下__block变量才是强引用。因为在MRC下,即使此处在堆上且strong修饰,__block变量也还是弱引用。需要注意),weak弱引用)
图解就是
堆上Block ———永远强引用———>结构体指针(包含__block变量的那个结构体)
|
| |
| |
| |
| |
| |
| |
强引用强指针修饰的__block变量 弱引用弱指针修饰的__block变量(注意:MRC下都是弱引用__block变量,即使__block变量修饰符是strong)
用代码来体验下强弱引用
//ARC下
int main(int argc, const char *argv[])
{
@autoreleasepool {
MJBlock block;
{
__block MJPerson *person = [[MJPerson alloc]init];
block = ^{
NSLog(@"person-->%@", person);
};
}
block();
}
return 0;
}//执行到断点处person还存在
//ARC下
int main(int argc, const char *argv[])
{
@autoreleasepool {
MJBlock block;
{
__block __weak MJPerson *person = [[MJPerson alloc]init];
block = ^{
NSLog(@"person-->%@", person);
};
}
block();
}
return 0;
}//执行到断点处[MJPerson dealloc]
循环引用
因为对象持有了block,而生成block时,如果用到对象,就会在block内部引用,导致双方都持有,从而循环引用了
比如有个person类
#import
typedef void (^MJBlock) (void);
@interface MJPerson : NSObject
@property (copy, nonatomic) MJBlock myBlock;
@property (assign, nonatomic) int age;
@end
我们在main函数里调用
int main(int argc, const char *argv[])
{
@autoreleasepool {
{
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
person.myBlock = ^{
NSLog(@"age is %d", person.age);
};
}
NSLog(@"----");
}
return 0;
}
结合以前学的知识,MJPerson *person = [[MJPerson alloc] init];会生成person指针指向[MJPerson alloc]内存。MJPerson类强引用自身的MJBlock myBlock;然后我们在main的block里调用了person.age,从而让block内部强引用了person。这样双方都持有,导致都没法释放。
上面几句代码执行后,引用结构为下
person————通过alloc、int指向————————>MJPerson(包含__block成员变量)对象
| /|\
| |
通过属性赋值强引用 通过捕获强引用外界person对象
| |
| |
| |
| |
\|/ |
block(block内部捕获了外界person对象)
如上如,MJPerson对象里的成员变量强引用block,block捕获的person强引用MJPerson对象。当代码走到断点处,person通过alloc、int指向MJPerson对象的指针消失,留下了
MJPerson(包含__block成员变量)对象
| /|\
| |
通过属性赋值强引用 通过捕获强引用外界person对象
| |
| |
| |
| |
\|/ |
block(block内部捕获了外界person对象)
互相强引用着,使得MJPerson对象不能释放。
ARC下如何解决上述问题
让block内部弱引用了person即可解决双向强持有问题
"方案一" 用__weak
__weak MJPerson *weakPerson = person;
person.block = ^{
NSLog(@"age is %d", weakPerson.age);
};
这样让block内部弱引用person,从而避免了双方强持有。
__weak MJPerson *weakPerson = person;可以换成__weak typeof(person) weakPerson = person;
"方案二" 用__unsafe_unretained (含义:不安全,不强引用)
__unsafe_unretained MJPerson *weakPerson = person;
person.block = ^{
NSLog(@"age is %d", weakPerson.age);
};
__unsafe_unretained被称为不安全的原因是__unsafe_unretained修饰的指针地址内存空间如果被释放,这个指针指向依旧有值,不会像__weak修饰的指针地址 被置nil,所以被称为不安全的
使用__unsafe_unretained修饰的对象,捕捉后定义的__main_block_impl_0如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MJPerson *__unsafe_unretained person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__unsafe_unretained _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
"方案三" 用__block
#import
#import "MJPerson.h"
int main(int argc, const char *argv[])
{
@autoreleasepool {
__block MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
person.myBlock = ^{
NSLog(@"age is %d", person.age);
person = nil; //后置空person ,这两步才能保证避免循环引用
};
person.myBlock(); //先调用block 如果使用__block来避免循环引用,却不调用block,依旧会循环引用并引起内存泄漏
}
return 0;
}
之所以能用__block配合解决循环引用,是因为在上述题目中,使用__block修饰后,引用结构为
__block变量
/ /|\
/ \
持有 持有
\|/ \
MJPerson--持有-->myBlock
MJPerson对象引用myBlock,myBlock引用__block变量,__block变量引用MJPerson对象。执行myBlock后置person = nil;实际上就是置包含__block变量的结构体里的__block变量也就是指向MJPerson对象的指针为nil,所以引用变成了MJPerson对象引用myBlock,myBlock引用__block变量,从而解决了循环引用。
使用__block控制循环引用的优点
1.通过__block变量可控制对象的持有期间
2.在不能使用__weak的环境中,不适用__unsafe_unretained
使用__block控制循环引用的缺点
1.为避免循环引用必须执行block,让__block变量置nil
MRC下如何解决上述循环引用问题
// MRC不支持__weak的
所以只剩下__unsafe_unretained和__block解决了
MRC用__block解决循环引用没有那么负责,直接__block修饰即可,因为在MRC环境下,被__block修饰的对象即使此处在堆上且strong修饰,也还是弱引用。
__block MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"age is %d", person.age);
};
block内部调用weakself时,多次执行self方法有些不生效,需要在block内__strong修饰self指针。
有一个例子
NSMutableArray *arr = [NSMutableArray array];
MJBlock block1 = ^{
[arr addObject:@"111"];
[arr addObject:@"222"];
};
不会报错,而且能修改。是因为addObject这两句不是在修改arr这个指针,而是在使用arr这个指针。如果是arr = nil才是修改这个指针。这里是截获OC对象,调用变更该对象的方法。
但是如下如下操作,则报错
NSMutableArray *arr = [NSMutableArray array];
MJBlock block1 = ^{
arr = [NSMutableArray array];
};