一、__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 对象。
forwarding图解
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这样访问









网友评论