1.block的copy
在ARC环境下,编译器会在以下情况下自动将栈上的block复制到堆上
- block作为函数的返回值
typedef void (^MJBlock)(void);
MJBlock myblock(int age)
{
return ^{
NSLog(@"---------%d",age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
NSLog(@"%@", [myblock(age) class]);
}
return 0;
}
//打印:__NSMallocBlock__
//当然,如果myblock没有捕获age的话,打印__NSGlobalBlock__
- 将block赋值给__strong指针
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
typedef void (^MJBlock)(void);
MJBlock block = ^{
NSLog(@"---------%d", age);
};
NSLog(@"赋值给__strong指针:%@", [block class]);
NSLog(@"未赋值给__strong指针:%@", [^{
} class]);
}
return 0;
}
打印:赋值给__strong指针:__NSMallocBlock__
未赋值给__strong指针:__NSGlobalBlock__
- block作为Cocoa API中方法名含有usingBlock的方法参数时
比如数组遍历
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
- block作为GCD API的方法参数时
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"-------");
});
此时的打印操作已经是属于堆上的block。
如果MRC环境下需要手动调用copy函数才能将block从栈复制到堆上
如
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
typedef void (^MJBlock)(void);
MJBlock block = [^{
NSLog(@"---------%d", age);
} copy];
NSLog(@"赋值给__strong指针:%@", [block class]);
NSLog(@"未赋值给__strong指针:%@", [^{
} class]);
}
return 0;
}
当然也需要对应的release操作
2.对象类型的auto变量
- 如果在栈上的block内部访问了对象类型的auto变量时,不会对auto变量产生强引用
{
Person *p = [[Person alloc] init];
^{
NSLog(@"-------%@",p);
}();
}
NSLog(@"===================");
打印:
-------<Person: 0x6000028143f0>
Person - dealloc
===================
可以看到栈上的block正常捕获对象类型auto变量,但是Person会在作用域结束后打印@"=============="前销毁
- 如果是堆上的block内部访问了对象类型的auto变量时,会对auto变量产生强引用
typedef void(^Myblock)(void);
Myblock block;
{
Person* p = [[Person alloc] init];
block = ^{
NSLog(@"-------%@",p);
};
block();
}
NSLog(@"=======");
打印:
-------<Person: 0x6000027fc2f0>
=======
MJPerson - dealloc
没有在作用域结束之后立即释放。
原理:
1.如果block被拷贝到堆上,会调用block内部的copy(__main_block_copy_)函数,copy函数会调用_Block_object_assign函数,
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。
2.如果block从堆上移除,会调用block内部的dispose(__main_block_dispose_)函数, dispose函数内部会调用_Block_object_dispose函数 _Block_object_dispose函数会自动释放引用的auto变量(release)
注意:copy函数与dispose函数都是在_main_block_desc_结构体中,当捕获了对象类型的auto变量,无论weak还是strong都会出现这两个函数,添加在size_t reserved 和 size_t Block_size后边
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};
3.__block修饰符底层
- __block可以用于解决block内部无法修改auto变量值得问题
- __block无法修饰全局变量和静态变量(static)
底层原理是:编译器会将__block变量包装成一个对象
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
^{
age = 30;
NSLog(@"age is %d", age);
}();
}
return 0;
}
//这段代码的底层结构(包括main函数)
int main(int argc, const char * argv[]) {
//包装外面的age变量
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
//捕获包装过的age变量
MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
//block底层结构体实现
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, NSObject *_p, __Block_byref_age_0 *_age, int flags=0) : p(_p), age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//__Block_byref_age_0结构体的底层结构
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
可以看到先把即将要捕获的 __block修饰过的age变量包装成一个对象struct __Block_byref_age_0,然后将这个对象捕获到block中,struct __main_block_impl_0中的age变量已经不是未修饰过的时候的 int age类型,而是__Block_byref_age_0 *age类型,也证实确实是捕获的一个对象类型的变量。
变量值的更改:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
}
通过包装过的对象中的forwarding指针修改age(forwarfing指针其实就是指向这个对象自己)。
注意:如果我们在block后边打印age的地址,那么这个age是__Block_byref_age_0的地址呢,还是__Block_byref_age_0结构体里的int age地址呢?
typedef void (^MyBlock) (void);
struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
struct __Block_byref_age_0 *age;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
MyBlock block = ^{
age = 20;
NSLog(@"age is %d", age);
};
//将block转换成结构体
struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
NSLog(@"%p---%p", &age,blockImpl->age);
}
return 0;
}
用命令行p/x blockImpl-->age和 p/x &(blockImpl-->age-->age)分别打印age结构体和结构体里的int age地址,发现与结构体中的int age地址相等。
4.__weak
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
MJPerson *p = [[MJPerson alloc] init];
__weak Person *weakP = p;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1-------%@", weakP);
});
NSLog(@"touchesBegan:withEvent:");
}
打印结果:
2019-12-09 12:34:51.821036+0800 Interview03-测试[6028:400633] touchesBegan:withEvent:
2019-12-09 12:34:51.821209+0800 Interview03-测试[6028:400633] Person - dealloc
2019-12-09 12:34:52.918856+0800 Interview03-测试[6028:400633] 1-------(null)
由于Person是weak修饰的,打印时已经释放了
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
Person *p = [[Person alloc] init];
__weak Person *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);
});
});
NSLog(@"touchesBegan:withEvent:");
}
打印结果:
2019-12-09 12:32:49.854488+0800 Interview03-测试[5975:392337] touchesBegan:withEvent:
2019-12-09 12:32:50.854661+0800 Interview03-测试[5975:392337] 1-------<Person: 0x600001793a40>
2019-12-09 12:32:52.855110+0800 Interview03-测试[5975:392337] 2-------<Person: 0x600001793a40>
2019-12-09 12:32:52.855286+0800 Interview03-测试[5975:392337] Person - dealloc
会发现Person在3秒后才释放。
来源:CSDN
作者:HuberCui
链接:https://blog.csdn.net/HuberCui/article/details/103453017