函数内定义的变量叫做局部变量,函数外部定义的变量叫做全局变量。
block封装了函数调用,以及函数调用环境的oc对象
- (void)viewDidLoad {
[super viewDidLoad];
int age = 10;
void (^blockName)(void) = ^{
NSLog(@"%d",age);
};
blockName();
}
源码如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
int age;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zs_r4g726_d3rd7y0hvw0_7ckym0000gn_T_ViewController_3bd104_mi_0,age);
}
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
int age = 10;
void (*blockName)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, age));
((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
}
自动变量截获值不截获指针,是因为自动变量随时可能销毁,如果解惑指针,有可能会在访问自动变量的时候指针已经销毁
代码举例:
void (^block)(void);
- (void)viewDidLoad {
[super viewDidLoad];
test();
block();// 作用域外age已经被销毁,所以如果age是指针传递,则此时age的地址已经被销毁,会造成坏内存访问
}
void test() {
int age = 10;
static int height = 110;
block = ^{
NSLog(@"%d, %d", age, height);
};
age = 10;
height = 120;
}
如何证明self是一个局部变量
void test () {
void (^blockName)(void) = ^{
NSLog(@"%@",self);
};
blockName();
}
编译之后的代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main__block_desc_0;
XSYPerson *self; // 会被捕获,所以说明self是局部变量
}
test的未省略方法为:
void test(XSYPerson *self ,SEL _cmd)
{
void (^blockName)(void) = ^{
NSLog(@"%@",self);
};
blockName();
}
能被block捕获的变量都是局部变量
全局变量不会捕获,局部变量会捕获。
void test () {
void (^blockName)(void) = ^{
NSLog(@"%@",_name );
};
blockName();
}
编译之后的代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main__block_desc_0;
XSYPerson *self; // 依然捕获的是self对象
}
因为
_name 是 self->_name这样来访问的
block的内存布局
isa决定了属于哪个类型的block
为什么说block是对象
- superclass最终是nsobject
- c++编译成功是一个带有isa的结构体,这跟nsobject的结构相同
block的类型
在arc环境下,一下情况会自动调用copy将blockcopy到堆上
- block作为函数返回时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCDAPI的方法参数时
拷贝到堆上,就是为了防止栈block被销毁,从而引发坏内存访问。
typedef void (^XSYBlock)(void);
- (void)viewDidLoad {
[super viewDidLoad];
XSYBlock block;
{
XSYPerson *person = [[XSYPerson alloc] init];
block = ^{
// 此处堆person有一个retain的操作。
NSLog(@"%@",person);
};
}
block();
}
// person不会被释放的原因:
因为person是自动变量,所以会被block捕获,又因为是对象,则连通修饰符一起被捕获,所以person的引用计数加1。所以在块外部,因为block没有释放,所以person也不会被释放。
如果block是在栈上,block内部不会对person产生强引用。
^{
// 此处堆person有一个retain的操作。
NSLog(@"%@",person);
};
此处block对person没有强引用,即便person自己是强引用。
解释:block自己都保不住自己的命,person更保不住。所以无需强引用。
block copy的操作流程
如果block被拷贝到堆上:
会调用block内部的copy函数,
copy函数会调用_Block_object_assign函数
_block_object_assign函数会根据auto变量的修饰符(__strong, __weak, __unsafe_retain)做出相应的操作,类似于retain(强引用,弱引用)
如果block从堆上移除:
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量,类似于release
__block
不能修饰可以解决block内部无法修改auto变量的问题
__block不能修饰全局变量和静态变量。
编译器将__block变量包装称为一个对象。
__block修饰的自动变量的本质
__block int age = 10;
void (^arrBlockName)(void) = ^ {
age = 11;
};
arrBlockName();
NSLog(@"%d",age);
输出:11
struct __Block_byref_age_0 {
void *isa;
__Block_byref_age_0 *__forwarding;
int flags;
int __size;
int age;
}
struct __main_block_impl_1 {
void *isa;
struct _block_impl *impl;
struct __main_block_desc_0 *Desc;
struct __Block_byref_age_0 *age;
}
__block的内存管理
__block int age = 10;
void (^arrBlockName)(void) = ^ {
age = 11;
};
因为__block对象是自动变量,所以在栈上
block被强引用修饰,被拷贝到堆上
这个时候,__block也会被拷贝到堆上,block内部堆__block对象是强引用,引用计数加一。
当block时放的时候,__block会通过dispose函数销毁
当block使用__block对象和NSObject对象的区别
相同点:
- 当block在栈上,block对两种对象都不会产生强引用
- 两个对象都是通过copy方法拷贝到堆上
- 两个对象都是通过dispost方法销毁
不同点: - NSObject对象会通过修饰符来进行强引用或弱引用
- block对 __block对象只存在强引用
当__block变量在栈上时,不会对__block变量产生强引用
- 当__block变量被copy到堆时,
会调用__block变量内部的copy函数,
copy函数会调用_Block_object_assign函数
_Block_object_assign会根据所指向的对象的修饰符(__strong,__weak, __unsafe_unretain)做出相应的操作,形成强引用或弱引用(注意:这里仅限于ARC时retain,MRC时不会retain)- 如果__block变量从堆上移除
会调用__block变量内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放指向的对象(release)
- 如果__block变量从堆上移除