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