iOS中block介绍(四)揭开神秘面纱(下)

移动开发 iOS
终于有空开始这系列最后一篇的编写。这一篇,我们将看到block的内存管理的内部实现,通过剖析runtime库源码,我们可以更深刻的理解block的内存运作体系。

看此篇时,请大家同时打开两个网址(或者下载它们到本地然后打开):

http://llvm.org/svn/llvm-project/compiler-rt/trunk/BlocksRuntime/runtime.c

http://llvm.org/svn/llvm-project/compiler-rt/trunk/BlocksRuntime/Block_private.h

内存管理的真面目

objc层面如何区分不同内存区的block

Block_private.h中有这样一组值:

  1. /* the raw data space for runtime classes for blocks */ 
  2. /* class+meta used for stack, malloc, and collectable based blocks */ 
  3. BLOCK_EXPORT void * _NSConcreteStackBlock[32]; 
  4. BLOCK_EXPORT void * _NSConcreteMallocBlock[32]; 
  5. BLOCK_EXPORT void * _NSConcreteAutoBlock[32]; 
  6. BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]; 
  7. BLOCK_EXPORT void * _NSConcreteGlobalBlock[32]; 
  8. BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32]; 

其用于对block的isa指针赋值

1.栈

  1. struct __OBJ1__of2_block_impl_0 { 
  2.   struct __block_impl impl; 
  3.   struct __OBJ1__of2_block_desc_0* Desc; 
  4.   OBJ1 *self; 
  5.   __OBJ1__of2_block_impl_0(void *fp, struct __OBJ1__of2_block_desc_0 *desc, OBJ1 *_self, int flags=0) : self(_self) { 
  6.     impl.isa = &_NSConcreteStackBlock; 
  7.     impl.Flags = flags; 
  8.     impl.FuncPtr = fp; 
  9.     Desc = desc; 
  10.   } 
  11. }; 

在栈上创建的block,其isa指针是_NSConcreteStackBlock。

2.全局区

在全局区创建的block,其比较类似,其构造函数会将isa指针赋值为_NSConcreteGlobalBlock。

3.堆

我们无法直接创建堆上的block,堆上的block需要从stack block拷贝得来,在runtime.c中的_Block_copy_internal函数中,有这样几行:

  1. // Its a stack block.  Make a copy. 
  2.     if (!isGC) { 
  3.         struct Block_layout *result = malloc(aBlock->descriptor->size); 
  4.         ... 
  5.         result->isa = _NSConcreteMallocBlock; 
  6.         ... 
  7.         return result; 
  8.     } 

可以看到,栈block复制得来的新block,其isa指针会被赋值为_NSConcreteMallocBlock

4.其余的isa类型

  1. BLOCK_EXPORT void * _NSConcreteAutoBlock[32]; 
  2. BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]; 
  3. BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32]; 

其他三种类型是用于gc和arc,我们暂不讨论

复制block

 对block调用Block_copy方法,或者向其发送objc copy消息,最终都会调用runtime.c中的_Block_copy_internal函数,其内部实现会检查block的flag,从而进行不同的操作:

  1. static void *_Block_copy_internal(const void *arg, const int flags) { 
  2.     ... 
  3.     aBlock = (struct Block_layout *)arg; 
  4.     ... 
  5. }1.栈block的复制 
  6.  
  7.         // reset refcount 
  8.         result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed 
  9.         result->flags |= BLOCK_NEEDS_FREE | 1; 
  10.         result->isa = _NSConcreteMallocBlock; 
  11.         if (result->flags & BLOCK_HAS_COPY_DISPOSE) { 
  12.             //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock); 
  13.             (*aBlock->descriptor->copy)(result, aBlock); // do fixup 
  14.         } 

 除了修改isa指针的值之外,拷贝过程中,还会将BLOCK_NEEDS_FREE置入,大家记住这个值,后面会用到。

***,如果block有辅助copy/dispose函数,那么辅助的copy函数会被调用。

2.全局block的复制

  1. else if (aBlock->flags & BLOCK_IS_GLOBAL) { 
  2.         return aBlock; 
  3.     }全局block进行copy是直接返回了原block,没有任何的其他操作。

全局block进行copy是直接返回了原block,没有任何的其他操作。

3.堆block的复制

  1. if (aBlock->flags & BLOCK_NEEDS_FREE) { 
  2.     // latches on high 
  3.     latching_incr_int(&aBlock->flags); 
  4.     return aBlock; 

栈block复制时,置入的BLOCK_NEEDS_FREE标记此时起作用,_Block_copy_internal函数识别当前block是一个堆block,则仅仅增加引用计数,然后返回原block。

辅助copy/dispose函数

1.普通变量的复制

辅助copy函数用于拷贝block所引用的可修改变量,我们这里以 __block int i = 1024为例:

先看看Block_private.h中的定义:

  1. struct Block_byref { 
  2.     void *isa; 
  3.     struct Block_byref *forwarding; 
  4.     int flags; /* refcount; */ 
  5.     int size; 
  6.     void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src); 
  7.     void (*byref_destroy)(struct Block_byref *); 
  8.     /* long shared[0]; */ 
  9. }; 

而我们的__block int i = 1024的转码:

  1. struct __Block_byref_i_0 { 
  2.   void *__isa; 
  3. __Block_byref_i_0 *__forwarding; 
  4.  int __flags; 
  5.  int __size; 
  6.  int i; 
  7. };//所以我们知道,当此结构体被类型强转为Block_byref时,前四个成员是一致的,访问flags就相当于访问__flags,而内部实现就是这样使用的 
  8. ... 
  9. __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024};//i初始化时__flags为0static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);} 

此时,复制时调用的辅助函数:

  1. void _Block_object_assign(void *destAddr, const void *object, const int flags) {//此处flags为8,即BLOCK_FIELD_IS_BYREF 
  2.     ... 
  3.     if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  { 
  4.         // copying a __block reference from the stack Block to the heap 
  5.         // flags will indicate if it holds a __weak reference and needs a special isa 
  6.         _Block_byref_assign_copy(destAddr, object, flags); 
  7.     } 
  8.     ... 
  9.  
  10. static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {//此处flags为8,即BLOCK_FIELD_IS_BYREF 
  11.     struct Block_byref **destp = (struct Block_byref **)dest; 
  12.     struct Block_byref *src = (struct Block_byref *)arg; 
  13.     ... 
  14.     else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {//当初次拷贝i时,flags为0,进入此分支会进行复制操作并改变flags值,置入BLOCK_NEEDS_FREE和初始的引用计数 
  15.        ... 
  16.     } 
  17.     // already copied to heap 
  18.     else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {//当再次拷贝i时,则仅仅增加其引用计数 
  19.         latching_incr_int(&src->forwarding->flags); 
  20.     } 
  21.     // assign byref data block pointer into new Block 
  22.     _Block_assign(src->forwarding, (void **)destp);//这句仅仅是直接赋值,其函数实现只有一行赋值语句,查阅runtime.c可知 

所以,我们知道,当我们多次copy一个block时,其引用的__block变量只会被拷贝一次。

2.objc变量的复制 

当objc变量没有__block修饰时:

  1. static void __OBJ1__of2_block_copy_0(struct __OBJ1__of2_block_impl_0*dst, struct __OBJ1__of2_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}void _Block_object_assign(void *destAddr, const void *object, const int flags) { 
  2.     ... 
  3.     else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) { 
  4.         //printf("retaining object at %p\n", object); 
  5.         _Block_retain_object(object);//当我们没有开启arc时,这个函数会retian此object 
  6.         //printf("done retaining object at %p\n", object); 
  7.         _Block_assign((void *)object, destAddr); 
  8.     } 
  9.     .... 

当objc变量有__block修饰时:

  1. struct __Block_byref_bSelf_0 { 
  2.   void *__isa; 
  3. __Block_byref_bSelf_0 *__forwarding; 
  4.  int __flags; 
  5.  int __size; 
  6.  void (*__Block_byref_id_object_copy)(void*, void*); 
  7.  void (*__Block_byref_id_object_dispose)(void*); 
  8.  OBJ1 *bSelf; 
  9. }; 
  10. static void __Block_byref_id_object_copy_131(void *dst, void *src) { 
  11.  _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);//131即为BLOCK_FIELD_IS_OBJECT|BLOCK_BYREF_CALLER 
  12. static void __Block_byref_id_object_dispose_131(void *src) { 
  13.  _Block_object_dispose(*(void * *) ((char*)src + 40), 131); 
  14.   
  15. ... //33554432即为BLOCK_HAS_COPY_DISPOSE 
  16.     __block __Block_byref_bSelf_0 bSelf = {(void*)0,(__Block_byref_bSelf_0 *)&bSelf, 33554432, sizeof(__Block_byref_bSelf_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self}; 

BLOCK_HAS_COPY_DISPOSE告诉内部实现,这个变量结构体具有自己的copy/dispose辅助函数,而此时我们的内部实现不会进行默认的复制操作:

  1. void _Block_object_assign(void *destAddr, const void *object, const int flags) { 
  2.     //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags); 
  3.     if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) { 
  4.         if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) { 
  5.             _Block_assign_weak(object, destAddr); 
  6.         } 
  7.         else { 
  8.             // do *not* retain or *copy* __block variables whatever they are 
  9.             _Block_assign((void *)object, destAddr); 
  10.         } 
  11.     } 

当我们没有开启arc,且flags中具有BLOCK_BYREF_CALLER时,会进入_Block_assign函数,而此函数仅仅是赋值

所以,如果要避免objc实例中的block引起的循环引用,我们需要让block间接使用self:

__block bSelf = self;

其他

对于dipose辅助函数,其行为与copy是类似的,我们不再重复同样的东西,如果大家要了解,自行查阅runtime.c和Block_private.h即可。

我们已经理解了非arc非gc情况下的block的内存管理内部实现,对arc和gc的情况,其行为也是类似的,只是一些函数的指针指向的真正函数会改变,比如_Block_use_GC函数,会将一些函数指向其他的实现,使其适用于gc开启的情况。

小结

block实际上是一些执行语句和语句需要的上下文的组合,而runtime给予的内部实现决定了它不会浪费一比特的内存。

我们知道cocoa中的容器类class有mutable和immutable之分,实际上我们可以将block看做一个immutable的容器,其盛放的是执行的代码和执行此代码需要的变量,而一个immutable变量的无法改变的特质,也决定了block在复制时,的确没有必要不断分配新的内存。故而其复制的行为会是增加引用计数。

责任编辑:闫佳明 来源: dreamingwish
相关推荐

2013-07-19 14:00:13

iOS中BlockiOS开发学习

2015-08-20 13:43:17

NFV网络功能虚拟化

2010-05-26 19:12:41

SVN冲突

2015-09-08 10:06:15

2021-07-28 21:49:01

JVM对象内存

2021-06-07 08:18:12

云计算云端阿里云

2010-05-17 09:13:35

2014-03-12 11:11:39

Storage vMo虚拟机

2010-05-11 10:19:17

VMforceJava云计算

2009-06-01 09:04:44

Google WaveWeb

2018-03-01 09:33:05

软件定义存储

2023-11-02 09:55:40

2009-09-15 15:34:33

Google Fast

2016-04-06 09:27:10

runtime解密学习

2012-08-17 09:27:34

奥运会云计算虚拟化

2016-11-16 09:06:59

2024-02-14 09:00:00

机器学习索引ChatGPT

2015-09-07 13:52:04

2015-09-06 13:40:02

HTTP网络协议

2024-11-11 16:36:41

点赞
收藏

51CTO技术栈公众号