文章目录
OC对象 - Block修改变量
1. 尝试修改
如下代码:
typedef void (^ZSXBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
ZSXBlock block = ^ {
age = 20;
NSLog(@"age is %d", age);
};
block();
}
return 0;
}
尝试修改age
的值,但是编译报错了
1.1 底层实现
将上述代码转成C++
此时会报错,先把age = 20
注释掉
直接修改肯定是改不了的
2. 可行修改方法
2.1 static
typedef void (^ZSXBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int age = 10;
ZSXBlock block = ^ {
age = 20;
NSLog(@"age is %d", age);
};
block();
}
return 0;
}
给age
增加static
修饰符后,可以修改
2.1.1 底层实现
通过指针
的方式访问,自然是可以修改了
2.2 全局变量
全局变量,全局都可以直接访问,不经过结构体指针,就可以直接修改的
typedef void (^ZSXBlock)(void);
int no_ = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int age = 10;
ZSXBlock block = ^ {
age = 20;
no_ = 30;
NSLog(@"age is %d", age);
};
block();
}
return 0;
}
2.3 __block
更多时候,我们希望可以修改局部变量
,如果还使用static
、全局变量
的方式,那变量就一直在内存中了,我们肯定不希望这样。这时候就可以使用__block
修饰符
typedef void (^ZSXBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
ZSXBlock block = ^ {
age = 20;
NSLog(@"age is %d", age);
};
block();
}
return 0;
}
使用__block
修饰符可以正常修改age
,并且age
还能保持局部变量
2.3.1 底层实现
__main_block_impl_0
中,原本是int age
,现在变成一个结构体__Block_byref_age_0
- 使用
__Block_byref_age_0
结构体来包装age
- 结构体初始化
__Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
,传入的参数相当于这样
__forwarding
传的是&age
,相当于把自己
传进去,指针指向自己(__forwarding
的用处后面会再讲到)
3. 总结
- __block可以用于解决block内部无法修改auto变量值的问题
- __block不能修饰全局变量、静态变量(static)
- 编译器会将__block变量包装成一个对象
这个对象对应的是结构体struct __Block_byref_age_0
struct __Block_byref_age_0
结构体中有个__forwarding
指向自身的指针
4. 拓展
4.1 __block修饰对象
我们再声明一个对象
类型的变量
观察底层实现:
- 使用
struct __Block_byref_obj_1
结构体来包装obj
变量 - 因为
obj
是对象类型,牵扯内存管理,所以里面多了copy
、dispose
方法
4.2 block里面操作NSMutableArray
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *mArr = [NSMutableArray array];
ZSXBlock block = ^ {
[mArr addObject:@"1"];
[mArr addObject:@"2"];
};
block();
NSLog(@"%@", mArr);
}
return 0;
}
NSMutableArray
实例对象mArr
并没有使用__block
等方式修饰,而是在 block 里面直接调用addObject
方法,并且添加成功了
这是因为,block
里面只是使用mArr
,并不是修*mArr
的值
这种情况下,就没必要使用
__block
来修饰mArr
,反而使用了使用__block
修饰符,会让底层结构复杂化
4.3 探索内存
前面文章讲到,使用__block
修饰变量后,编译器会将__block
变量包装成一个对象,如下
typedef void (^ZSXBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
ZSXBlock block = ^ {
age = 20;
NSLog(@"age is %d", age);
};
block();
}
return 0;
}
将使用struct __Block_byref_age_0
包装age
变量
那么,怎么确定age = 20;
修改的是__Block_byref_age_0
结构体中的int age
呢
4.3.1 打印比对内存地址
我们将打印age的地址值NSLog(@"%p", &age);
,与block
结构体里面的age
做对比
为了方便调试,我们拷贝底层实现的代码,模拟把block
转成结构体形式
typedef void (^ZSXBlock)(void);
struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
ZSXBlock block = ^ {
age = 20;
NSLog(@"age is %d", age);
};
struct __main_block_impl_0 *blockIml = (__bridge struct __main_block_impl_0 *)block;
NSLog(@"%p", &age);
}
return 0;
}
此时如果直接打印age
发现没有直接打印出内存地址
我们先打印结构体实现的包装类age
,也就是:
接着往下执行,打印age
的地址值
会发现地址值不一样
0x00006000020810c0
((__Block_byref_age_0 *) age)0x6000020810d8
(&age)
仔细观察并计算struct __Block_byref_age_0
的成员
__Block_byref_age_0
里的age
地址值就是0x00006000020810d8
因此,age = 20;
修改的是__Block_byref_age_0
结构体中的int age
苹果这么做,还是为了屏蔽内部实现
,开发者age = 20;
的操作,看起来就是在访问前面定义的int a = 10;
4.3.2 lldb方式打印
@oubijiexi