block存储区域
首先我们得明白iOS系统存储有哪几大内存区域:
1、栈区
由编译器自动分配并释放,存放函数的参数值(实参),局部变量等。栈是向低地址扩展的数据结构,是不连续的内存区域,采用后进先出(LIFO )。优点是快速高效,缺点时有限制,数据不灵活。
2、堆区
由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收。堆是向高地址扩展的数据结构,是不连续的内存区域,以链表的方式进行存储。
3、全局静态区
全局区分为初始化全局区(bbs)和未初始化全局区(data)。全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,在程序结束后有系统释放。
4、常量区
存储常量数据,通常程序结束后由系统自动释放。
5、代码区
用来存放函数的二进制代码,在运行时要防止被非法修改,只允许读取不允许操作。
下面看一段示例
block是一个匿名函数,具体自动捕获外部变量的能力
从上面的示例我们可以发现:
一个普通的不包含任何外部变量的block(比如示例中的1、4、7)是存储在全局静态区的,不管这个block作为临时变量还是属性或者其他方式;
单纯作为block存在,但是有用到外部变量的(比如示例中的1、2、3)存储在栈区,因为这样可以更快的访问外部变量;
如果block用=复制给了一个变量,同时block获取了外部变量(比如示例中的5、6、8、9)存储在堆区,因为相当于从栈区拷贝到堆区。ARC情况下,可以用copy、strong修饰block,只是为了习惯,基本都是用的copy。
block循环引用产生情况及解决方法
1、最常见的情况,循环链self -> block -> self
解决方法:
(1)用__weak修饰self
(2)用一个中间临时变量保存self
(3)将self作为参数传进block
2、单纯用__weak修饰self有时也会产生一个新的问题
因为在 block里面用到self的属性时,self已经被释放,self = nil,所以此时拿到的属性值为空。
解决方法:__strong再次强引用一遍
下面再看看 block到底做了什么,或者说block是怎么实现的?
现在main.m有这么一段代码
在终端执行clang -rewrite-objc main.m得到main.cpp
void (^block)(void) = ^{
NSLog(@"这是一个block");
};
block();
反编译之后,再简化
block = __main_block_impl_0(__main_block_func_0, __main_block_desc_0_DATA);
block->FuncPtr(block);
block是__main_block_impl_0
block->FuncPtr = fp = __main_block_func_0 = ^{ NSLog(@"这是一个block"); };
最后看__block修饰是如何修改外部变量
main.m有这么一段代码
在终端执行clang -rewrite-objc main.m得到main.cpp
__block int a = 1;
void (^block)(void) = ^{
a++;
};
block();
反编译再简化
a = {0, &a, 0, sizeof(__Block_byref_a_0), 1};
block = __main_block_impl_0(__main_block_func_0, __main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, flag);
block->FuncPtr(block)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
现在用的是a的地址,所以block里面修改a的值,外面会有变化
(a->__forwarding->a)++;
}