您的位置:首页 > 移动开发 > IOS开发

iOS block __block 关键字详解 OC block 源码详解

2016-06-26 22:01 302 查看
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: @tripleCC 也就是说,
编译器把用__block修饰的基本类型变量全部放到堆上包装成对象了, "栈里面的那个变量"在block内外都不会用到, 或者说是不存在.

以前觉得加了__block修饰后, 就能访问到外面了, 其实是加了__block之后, 外面全部都访问堆了.

学到很多, 感谢楼主的无私奉献(´ε`)
回复2015.07.23
15:23

口可口可口达: @Ian_He 感觉这么说不严谨,应该是在block被copy(到堆上)后,__block修饰的变量才会被copy到堆上(__forwarding指向了堆上的__block修饰的变量)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: