美文网首页iOS底层探究
Block的实现原理

Block的实现原理

作者: XIAOHANG | 来源:发表于2017-06-21 23:23 被阅读60次

摘要:

我们在开发的过程中经常使用到block,不仅如此,apple的api里面也有很多使用到block,好比gcd里面也是大量使用了block。block在语法上来说比较简洁,不过还是需要注意不要引起了循环引用。

裸block:

我们先来看看最基本的block编译之后长啥样:

typedef void (^Block)();

int main(int argc, const char * argv[]) {
    
    @autoreleasepool
    {
        Block b = ^(){
            printf("Hello Gay");
        };
    }
    return 0;
}

我们使用 clang-rewrite-objc filename 指令编译一下到底是什么鬼

struct __block_impl {
  void *isa;     //什么类型的block
  int Flags;     //block的一些附加信息,里面包含了何种类型的block
                 //以及引用计数,下面讲到copy的时候就能知道到底是干嘛的了
  int Reserved;  //保留位
  void *FuncPtr; //函数指针
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc; //block的描述
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,int flags=0) 
{
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            printf("Hello Gay");
}

static struct __main_block_desc_0 
{
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(int argc, const char * argv[]) {
    { 
        __AtAutoreleasePool __autoreleasepool; 
        Block b = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    }
    return 0;
}

我们可以看到声明的block以结构体的方式进行了存储,从侧面上来说,block是一个指向结构体的指针。其中__block_impl结构体记录的是block的相关信息包括block的类型以及引用计数等。

block里面捕获变量之后

我们假设在block里面捕获了一个变量,看看内部会变成什么样?

typedef void (^Block)();

int main(int argc, const char * argv[]) {
    @autoreleasepool
    {
        int i = 0;
        Block b = ^(){
            printf("%d",i);
        };
    }
    return 0;
}

同样clang编译之后

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int i; //相对于未捕获变量的block来说,多了一个变量来保存值
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
{
   int i = __cself->i; // bound by copy
   printf("%d",i);
 }

int main(int argc, const char * argv[]) 
{
    { 
        __AtAutoreleasePool __autoreleasepool; 
        int i = 0;
        Block b = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, i));
        //注意这里的i传的是值,因此意味着在当我们在外部修改这个值的时候,你调用block打印出来的时候,这个值依旧是之前的值
    }
    return 0;
}

乍一看,好像跟刚才的大差不差,__main_block_impl_0里面产生了一个变量用来保存之前捕获的值。

block捕获可修改的变量之后

贴上low B代码

typedef void (^Block)();

int main(int argc, const char * argv[]) {

    @autoreleasepool
    {
        __block int i = 0;
        Block b = ^(){
            printf("%d",i);
        };
    }
    return 0;
}

我们再次编译一下👀

struct __Block_byref_i_0 {
  void *__isa;  //什么类型的数据
__Block_byref_i_0 *__forwarding;  //这个到copy的时候就用得着了,主要是保证copy之后能够找到在堆上的那个变量
 int __flags;//变量的引用计数
 int __size;
 int i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref 这里跟之前不一样了,这次是用指针来保存的
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) 
{
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
{
      __Block_byref_i_0 *i = __cself->i; // bound by ref
      printf("%d",(i->__forwarding->i));
}

//辅助block copy的时候,对捕获变量的存储方案
static 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*/);}

//辅助block release的时候,对捕获变量的释放策略
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

//这里面添加copy和dispose两个函数
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, const char * argv[]) {

    { __AtAutoreleasePool __autoreleasepool; 

       //这里将结构体i的__forwarding指针指向了自身
        __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};

        Block b = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
//570425344干嘛的?后面识别block类型的时候就能用上了

    }
    return 0;
}

通过编译我们可以看到,block的描述里面多了两个函数(关于copy和release)

Block_copy的实现

之前的热身内容是为了让大家思路更加清(meng)晰(bi)。
我们可以在编译器上使用Block_copy()或者在Block.h里面查看到关于Block_copy的定义

#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))

runtime.c中_Block_copy函数以这种方式实现了

void *_Block_copy(const void *arg) {
    return _Block_copy_internal(arg, WANTS_ONE);
}

该函数内部调用了_Block_copy_internal,同样我们在runtime.c中能够查看到该函数的实现方式

先贴上几个block类型的枚举,在这里能够看到Block_private.h

enum{
    BLOCK_REFCOUNT_MASK =     (0xffff),
    BLOCK_NEEDS_FREE =        (1 << 24), //堆block
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), */* Helpers have C++ code. */*
    BLOCK_IS_GC =             (1 << 27),
    BLOCK_IS_GLOBAL =         (1 << 28),   //全局block
    BLOCK_HAS_DESCRIPTOR =    (1 << 29)
};

_Block_copy_internal实现如下

static void *_Block_copy_internal(const void *arg, const int flags) 
{
    struct Block_layout *aBlock;
    
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
 
    if (!arg) return NULL;
    
    aBlock = (struct Block_layout *)arg;
   //   还记得上面的那个初始化bloc时候赋值给flags标志位的570425344吗
   //   570425344 & (1 << 24) = 0 不满足跳过
    if (aBlock->flags & BLOCK_NEEDS_FREE) { 
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
   //这边也是不满足条件的
    else if (aBlock->flags & BLOCK_IS_GC) {
       //此处省略若干行代码。。。
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) 
    {
        return aBlock;
    }

    //最后来到这
    //isGC在runtime.c文件里面能够找到,该变量被初始化为false
    // Its a stack block.  Make a copy.
    // 对栈block的copy
    if (!isGC) {
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed


        //这里是重点给flags标识为加上BLOCK_NEEDS_FREE标识位
        //同时增加了一个引用计数
        result->flags |= BLOCK_NEEDS_FREE | 1;

       //修改isa指针
        result->isa = _NSConcreteMallocBlock;                  
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) 
       {
            //如果存在copy的辅助函数,会调用该辅助函数,
            //当捕获了引用的时候,显然是满足条件的。
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
    else {
        //此处省略。。。
    }
}

至此,相信都知道了copy是怎么样一个过程了。当对一个堆上的block再次进行调用copy的时候,因为我们之前给flags打入了BLOCK_NEEDS_FREE这个值,所以最后走的是这个判定条件

    if (aBlock->flags & BLOCK_NEEDS_FREE) { 
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }

结果就是增加引用计数,然后返回该block。

辅助copy/dispose函数

1.普通变量的copy

在前面我们说到,如果有block复制到堆上的时候,有copy辅助函数的,该函数会被调用。以__block int i = 0为例子生成的辅助函数如下

static 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*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) 
{
    _Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);
}

在此之前,先赋上block中copy辅助函数,flags标识位能够支持的类型

enum {
    BLOCK_FIELD_IS_OBJECT   =  3,  /* id, NSObject, __attribute__((NSObject)), block, ... */
    BLOCK_FIELD_IS_BLOCK    =  7,  /* a block variable */
    BLOCK_FIELD_IS_BYREF    =  8,  // __block修饰的基本数据类型
    BLOCK_FIELD_IS_WEAK     = 16,  /* declared __weak, only used in byref copy helpers */
    BLOCK_BYREF_CALLER      = 128  /* called from __block (byref) copy/dispose support routines. */
};

runtime.c里面_Block_object_assign函数的实现方式如下

void _Block_object_assign(void *destAddr, const void *object, const int flags) 
{
    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) 
   {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) 
        {
            _Block_assign_weak(object, destAddr);
        }
        else 
        {
            _Block_assign((void *)object, destAddr);
        }
    }
    
    //之前的生成的辅助函数,flags = 8 => BLOCK_FIELD_IS_BYREF

    else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  
    {
       //在这里调用了_Block_byref_assign_copy,看下面的那个函数
        _Block_byref_assign_copy(destAddr, object, flags);
    }
    else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) 
    {
        _Block_assign(_Block_copy_internal(object, flags), destAddr);
    }
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) 
    {
        _Block_retain_object(object);
        _Block_assign((void *)object, destAddr);
    }
}

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;
        
    if (src->forwarding->flags & BLOCK_IS_GC) {
        ;   // don't need to do any more work
    }
    else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        //省略若干行
    }
    //因为之前block已经拷贝到对上了,所以最后走的是这边,对该变量增加了一个引用计数
    else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    // 在这里进行赋值操作
    _Block_assign(src->forwarding, (void **)destp);
}

上面就是一个被__block修饰的基本数据类型拷贝到堆上的时候,copy函数的实际调用过程。

普通oc对象的复制

以捕获NSObject对象为栗子
辅助函数如下:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

同样是调用_Block_object_assign这个函数,不过最终走的是这个

if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) 
{
        _Block_retain_object(object);
        _Block_assign((void *)object, destAddr);
}

把该对象retain之后,然后再赋值,这也就是说为什么block里面的对象需要使用weak的原因。

__block修饰的oc对象的复制

还是以NSObject为栗子,辅助函数如下:

//131即为BLOCK_FIELD_IS_OBJECT|BLOCK_BYREF_CALLER 

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

同样是调用_Block_object_assign这个函数,最终走的是这个

    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
            _Block_assign_weak(object, destAddr);
        }
        else {
            // 最终走的是这个
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }

该函数说明,如果你使用了______block对oc对象进行修饰,那么仅仅只是赋值,不增加引用计数,这就是使用______block不会造成循环引用的原因。

小结

通过上面的分析,相信大家对block有了更加清晰的理解。🐶
如果你看完以上内容觉得so easy,那么你可能是大神。
如果你看完以上内容觉得啥JB玩意,那么可能是我写的太渣了。
如果文章中,有错误的地方欢迎大家提出指正

附录

本文参考了以下两篇帖子,特此奉上:
没事蹦蹦的Block实现原理
BobooO的iOS中block介绍(四)揭开神秘面纱(下)

相关文章

  • iOS-2 Block

    block块 系列文章: iOS Block浅浅析 - 简书 iOS Block实现原理 iOS Block __...

  • iOS Block存储域及循环引用

    系列文章:iOS Block概念、语法及基本使用iOS Block实现原理iOS Block __block说明符...

  • iOS Block 部分一

    主要讲解 Block 的底层实现原理; Block部分一Block部分二Block部分三Block知识点总结 基础...

  • 深入研究Block用weakSelf、strongSelf、@w

    前言 在上篇中,仔细分析了一下Block的实现原理以及__block捕获外部变量的原理。然而实际使用Block过程...

  • Block

    Block 1.Block的定义和语法2.Block的本质和分类3.__block的实现原理 Block的定义和语...

  • Today面试

    Runloop 底层原理Kvo 底层原理ARC 底层原理 如何实现GCD 底层原理Block 底层原理Aut...

  • iOS Block浅析

    Block实现原理 要想知道Block的内部实现,需要知道Block编译完后是什么样子,使用clang可看到Blo...

  • iOS Block概念、语法及基本使用

    系列文章:iOS Block实现原理iOS Block __block说明符 最近又翻了一遍《Objective-...

  • Block - __block关键字的底层实现原理

    参考文档 iOS中__block 关键字的底层实现原理 你真的理解__block修饰符的原理么? iOS Bloc...

  • Block实现原理

    Block是带有自动变量值的匿名函数; 带有自动变量值在Block中表现为截获自动变量值; 自动变量值截获只能保存...

网友评论

    本文标题:Block的实现原理

    本文链接:https://www.haomeiwen.com/subject/ubstcxtx.html