一、__block 的本质
1. 观察如下代码,会报错吗?如果想在 block 内修改 age 的值,有哪三种方式?
typedef void (^SPBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
SPBlock block = ^ {
age = 20;
NSLog(@"%d",age);
};
block();
NSLog(@"%d",age);
}
return 0;
}
- 会报错,这种情况下 block 内部是对 age 的值捕获,无法修改外部的 age
- 将 age 变成静态局部变量
- 将 age 变成全局变量
- 将 age 加上
__block
修饰
2. __block
修饰符是什么?
-
__block
可以用于解决 block 内部无法修改auto
变量值
的问题 -
__block
不能修饰全局变量、静态变量(static
) - 编译器会将
__block
变量包装成一个对象
3. 下面代码中的 array没有使用
__block` 修饰,能够正常使用吗?
typedef void (^SPBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *array = [[NSMutableArray alloc] init];
SPBlock block = ^ {
[array addObject:@"1"];
};
block();
NSLog(@"%@",array);
}
return 0;
}
- 可以正常运行,不需要 __block 修饰
- block 中的代码块,只是使用
array
,并没有修改array
的值,所以不需要使用__block
修饰 - 如果 block 代码块中加入
array = nil;
,去修改 array 变量的值,那么就会编译报错
4. __block
修饰 auto 的 非OC 对象变量
,本质做了什么事情?
修饰一般变量代码逻辑图
-
我们发现
__block
修饰的age
变量被编译器包装成了__Block_byref_age_0
对象 -
__main_block_desc_0
中有copy
和dispose
说明对"age"
对象做了内存管理 -
有一个有趣的地方,
(age->__forwarding->age) = 20;
这句是拿到age
对象的forwarding
然后再拿到age
,从上面的代码可得知forwarding
其实就是指向&age
。那为什么不直接拿到age
而要绕一圈呢? -
这是因为一开始 age 被包装的这个对象,没有被 block 捕获时,是放在
栈区的
。经过 block 捕获并 copy 后,age对象
也会被拷贝堆区。这时候堆区和栈区都有age 对象
,我们无法确定我们取得age 对象
是堆区的还是栈区的。然后我们巧妙的经过forwarding绕一圈
,保证无论堆区
还是栈
的 forwarding 都指向堆区的 age 对象
。这样我们才能准确无误的拿到我们想要的age 对象
。
5. 在问题 4
中,那么多 age,我们如何确定拿到的 age 是哪个 age 呢?
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_age_0 {
void *__isa; // 8字节
struct __Block_byref_age_0 *__forwarding;// 8字节
int __flags; // 4字节
int __size; // 4字节
int age; // 这个 age 相对于__isa的地址需要加24 字节
};
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*);
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
struct __Block_byref_age_0 *age; // by ref
};
typedef void (^SPBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
SPBlock block = ^ {
age = 20;
};
struct __main_block_impl_0 lsBlock =*(__bridge struct __main_block_impl_0*)block;
NSLog(@"断点处");
}
return 0;
}
- 思路,我们将 block 的结构体拿出来,然后将我们 OC 中的
block
转换成lsBlock
- 然后从结构体
__Block_byref_age_0
计算出 age 相对于__isa
的地址,应该是 24 字节,也就是0x18
-然后我们在控制台,打印如下信息:
(lldb) p/x lsBlock->age
(__Block_byref_age_0 *) $7 = 0x0000000100525260
Fix-it applied, fixed expression was:
lsBlock.age
(lldb) p/x &age
(int *) $8 = 0x0000000100525278
(lldb) p/x 0x0000000100525260 + 0x18
(long) $9 = 0x0000000100525278
- 借此,我们可以确定,我们在代码中的
age地址值
就是__Block_byref_age_0
结构体中的age地址值
6. __block
修饰 oc对象
,比较复杂,暂时先不讨论,后面有需要再补充。
二、block 相关的循环引用
1. 下面代码,会导致循环引用吗?
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 20;
person.block = ^{
NSLog(@"Age is %d", person.age);
};
person.block();
}
return 0;
}
- 会导致循环引用
- 因为
person.block
捕获了person 对象
,并且会强应用person 对象
- 而
person.block
又被person
强引用 - 所以
person
和person.block
之间形成了循环引用
2. 问题 1
你有哪几种方案解决循环引用?
- 方案一使用
__weak
解决:__weak typeof(person) weakPerson = person;
- 方案二使用
__unsafe_unretained
解决:__unsafe_unretained typeof(person) weakPerson = person;
- 方案三使用
__block
比较麻烦,性能比较低,不推荐,但是能解决:
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
person.age = 20;
person.block = ^{
NSLog(@"Age is %d", person.age);
person = nil;
};
person.block();
}
return 0;
}
- 方案三需要注意:①必须调用
person.block();
②必须手动person = nil;
3. __weak
和 __unsafe_unretained
都能解决循环引用,那么有什么区别?
-
__weak
:不会产生强引用,安全,指向的对象销毁时,会自动让指针置为 nil -
__unsafe_unretained
:不会产生强引用,不安全,执行的对象销毁时,指针存储的地址值不变
4. 下面的这种写法,经常见到,__strong
有什么作用?
- (void)test {
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"Age is %d", strongSelf->_age);
};
}
- 这样写
主要目的
是为了,让test
函数调用期间保证self
不会被突然释放,保证了代码的安全性。 - 其次,编译器不支持
weakSelf->_age
这样访问
网友评论