http://www.jianshu.com/p/51d04b7639f1
行赋值是没有意义的,所以编译器给出了错误。我们可以通过地址传递来消除以上错误:
<code class="cpp">- (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)test
{
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> a = <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>;
<span class="hljs-comment" style="color: rgb(136, 0, 0);">// 利用指针p存储a的地址</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> *p = &a;
^{
<span class="hljs-comment" style="color: rgb(136, 0, 0);">// 通过a的地址设置a的值</span>
*p = <span class="hljs-number" style="color: rgb(0, 102, 102);">10</span>;
};
}</code>
但是变量a的生命周期是和方法test的栈相关联的,当test运行结束,栈随之销毁,那么变量a就会被销毁,p也就成为了野指针。如果block是作为参数或者返回值,这些类型都是跨栈的,也就是说再次调用会造成野指针错误。
OC Block 源码
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) { struct Block_byref **destp = (struct Block_byref **)dest; struct Block_byref *src = (struct Block_byref *)arg; //printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags); //printf("src dump: %s\n", _Block_byref_dump(src)); if (src->forwarding->flags & BLOCK_IS_GC) { ; // don't need to do any more work } else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) { //printf("making copy\n"); // src points to stack bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)); // if its weak ask for an object (only matters under GC) struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak); copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier) src->forwarding = copy; // patch stack to point to heap copy copy->size = src->size; if (isWeak) { copy->isa = &_NSConcreteWeakBlockVariable; // mark isa field so it gets weak scanning } if (src->flags & BLOCK_HAS_COPY_DISPOSE) { // Trust copy helper to copy everything of interest // If more than one field shows up in a byref block this is wrong XXX copy->byref_keep = src->byref_keep; copy->byref_destroy = src->byref_destroy; (*src->byref_keep)(copy, src); } else { // just bits. Blast 'em using _Block_memmove in case they're __strong _Block_memmove( (void *)©->byref_keep, (void *)&src->byref_keep, src->size - sizeof(struct Block_byref_header)); } } // already copied to heap else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) { latching_incr_int(&src->forwarding->flags); } // assign byref data block pointer into new Block _Block_assign(src->forwarding, (void **)destp); }
<code class="cpp"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> __Person__test_block_func_0(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// bound by ref</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0);">// 注意,这里的_forwarding用来保证操作的始终是堆中的拷贝a,而不是栈中的a</span>
(a->__forwarding->a) = <span class="hljs-number" style="color: rgb(0, 102, 102);">10</span>;
}
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> __Person__test_block_copy_0(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*dst, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*src) {_Block_object_assign((<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>*)&dst->a, (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>*)src->a, <span class="hljs-number" style="color: rgb(0, 102, 102);">8</span><span class="hljs-comment" style="color: rgb(136, 0, 0);">/*BLOCK_FIELD_IS_BYREF*/</span>);}
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> __Person__test_block_dispose_0(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*src) {_Block_object_dispose((<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>*)src->a, <span class="hljs-number" style="color: rgb(0, 102, 102);">8</span><span class="hljs-comment" style="color: rgb(136, 0, 0);">/*BLOCK_FIELD_IS_BYREF*/</span>);}
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_desc_0 {
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">size_t</span> reserved;
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">size_t</span> Block_size;
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> (*copy)(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*);
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> (*dispose)(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">sizeof</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> _I_Person_test(Person * self, SEL _cmd) {
<span class="hljs-comment" style="color: rgb(136, 0, 0);">// __block将a包装成了一个对象</span>
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>*)<span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>,(__Block_byref_a_0 *)&a, <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">sizeof</span>(__Block_byref_a_0)};
;
(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> (*)())&__Person__test_block_impl_0((<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, <span class="hljs-number" style="color: rgb(0, 102, 102);">570425344</span>);
}</code>
可以看到,对比上面的结果,明显多了__Block_byref_a_0
结构体,这个结构体中含有isa
指针,所以也是一个对象,它是用来包装局部变量a的。当block被copy到堆中时,__Person__test_block_impl_0
的拷贝辅助函数__Person__test_block_copy_0
会将__Block_byref_a_0
拷贝至堆中,所以即使局部变量所在堆被销毁,block依然能对堆中的局部变量进行操作。其中__Block_byref_a_0
成员指针__forwarding
用来指向它在堆中的拷贝,其依据源码如下:
<code class="objectivec"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> _Block_byref_assign_copy(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *dest, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *arg, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> flags) { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> Block_byref **destp = (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> Block_byref **)dest; <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> Block_byref *src = (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> Block_byref *)arg; ... <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 堆中拷贝的forwarding指向它自己</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">copy</span>->forwarding = <span class="hljs-keyword" style="color: rgb(0, 0, 136);">copy</span>; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// patch heap copy to point to itself (skip write-barrier)</span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 栈中的forwarding指向堆中的拷贝</span> src->forwarding = <span class="hljs-keyword" style="color: rgb(0, 0, 136);">copy</span>; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// patch stack to point to heap copy</span> ... }</code>
<code class="objectivec"></code><p class="p1" style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-size: 16px;">当block从栈上被copy到堆上时,会调用__main_block_copy_0将__block类型的成员变量i从栈上复制到堆上;而当block被释放时,相应地会调用__main_block_dispose_0来释放__block类型的成员变量i。</span></p><p class="p2" style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-size: 16px;">一会在栈上,一会在堆上,那如果栈上和堆上同时对该变量进行操作,怎么办?</span></p><p class="p3" style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-size: 16px;"><span class="s5">这时候,__forwarding的作用就体现出来了:</span>当一个__block变量从栈上被复制到堆上时,栈上的那个__Block_byref_i_0结构体中的__forwarding指针也会指向堆上的结构<span class="s5">。</span></span></p>
这样做是为了保证操作的值始终是堆中的拷贝,而不是栈中的值。(处理在局部变量所在栈还没销毁,就调用block来改变局部变量值的情况,如果没有__forwarding指针,则修改无效)
口可口可口达: @Ian_He 感觉这么说不严谨,应该是在block被copy(到堆上)后,__block修饰的变量才会被copy到堆上(__forwarding指向了堆上的__block修饰的变量)。
Ian_He: @tripleCC 也就是说, 编译器把用__block修饰的基本类型变量全部放到堆上包装成对象了, "栈里面的那个变量"在block内外都不会用到, 或者说是不存在.
以前觉得加了__block修饰后, 就能访问到外面了, 其实是加了__block之后, 外面全部都访问堆了.
学到很多, 感谢楼主的无私奉献(´ε`)