本文要将block的以下机制,并配合具体代码详细描述:
- block 与 外部变量
- block 的存储域:栈块、堆块、全局块
定义
块与函数类似,只不过是直接定义在另一个函数里,和定义它的那个函数共享同一个范围内的东西。
访问外部变量
堆块内部,栈是红灯区,堆是绿灯区。
根据块的存储位置,可将块分为全局块、栈块、堆块。这里先主要针对堆块讲解。
- Block不允许修改外部变量的值。Apple这样设计,应该是考虑到了block的特殊性,block也属于“函数”的范畴,变量进入block,实际就是已经改变了作用域。在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。又比如我想在block内声明了一个与外部同名的变量,此时是允许呢还是不允许呢?只有加上了这样的限制,这样的情景才能实现。于是栈区变成了红灯区,堆区变成了绿灯区。
几种演算
- block调用 基本数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
|
栈块、堆块、全局块
块本身也是对象,由isa指针、块对象正常运转所需的信息、捕获到的变量组成。
根据Block创建的位置不同,Block有三种类型,创建的Block对象分别会存储到栈、堆、全局数据区域。
block_storage.png
上面讲了块会把它所捕获的所有变量都拷贝一份,这些拷贝放在 descriptor 变量后面,捕获了多少个变量,就要占据多少内存空间。请注意,拷贝的并不是对象本身,而是指向这些对象的指针变量。
1. 在全局数据区的Block对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
2. 在堆上创建的Block对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
3. 在栈上创建的Block对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
内存泄漏
堆块访问外部变量时会拷贝一份指针到堆中,相当于强引用了指针所指的值。如果该对象又直接或间接引用了块,就出现了循环引用。
解决方法:要么在捕获时使用__weak解除引用,要么在执行完后置nil解除引用(使用后置nil的方式,如果未执行,则仍会内存泄漏)。
- 注意:使用__block并不能解决循环引用问题。
优缺点
优点:
- 捕获外部变量
- 降低代码分散程度
缺点:
- 循环引用引起内存泄露
总结
- 在block内部,栈是红灯区,堆是绿灯区。
- 在block内部使用的是将外部变量的拷贝到堆中的(基本数据类型直接拷贝一份到堆中,对象类型只将在栈中的指针拷贝到堆中并且指针所指向的地址不变。)
- __block修饰符的作用:是将block中用到的变量,拷贝到堆中,并且外部的变量本身地址也改变到堆中。
- 循环引用:分析实际的引用关系,block中直接引用self也不一定会造成循环引用。
- __block不能解决循环引用,需要在block执行尾部将变量设置成nil(但问题很多,比如block永远不执行,外面变量变了里面也变,里面变了外面也变等问题)
- __weak可以解决循环引用,block在捕获weakObj时,会对weakObj指向的对象进行弱引用。
- 使用__weak时,可在block开始用局部__strong变量持有,以免block执行期间对象被释放。
- 块的存储域:全局块、栈块、堆块
- 全局块不引用外部变量,所以不用考虑。
- 堆块引用的外部变量,不是原始的外部变量,是拷贝到堆中的副本。
- 栈块本身就在栈中,引用外部变量不会拷贝到堆中。
参考