美文网首页
Block原理分析

Block原理分析

作者: 会跑的鱼_09 | 来源:发表于2020-11-25 15:30 被阅读0次

block类型

我们都知道block有三种类型:NSGlobalBlock、NSStackBlock、NSMallocBlock,一个block没有引用外部变量的那它就是NSGlobalBlock,如果引用了外部变量,它是先声明成NSStackBlock然后经过block_copy方法变成了NSMallocBlock。在我们开发过程中如果直接打印某个block对象,是无法看到其类型的。但通过lldb调试后在汇编层是可以查看具体类型变化的(注意要用真机)。有如下代码:

__block NSString *bb = [NSString stringWithFormat:@"mm"];
void(^blockbb)(void) = ^{
     NSLog(@"%@",bb);
};
blockbb();

断点后查看汇编代码:


step1.png

然后来到


step2.png

按住Ctrol+Step Into,此时读取寄存器

lldb) register read x0
      x0 = 0x000000016f07f6f8
(lldb) po 0x000000016f07f6f8
<__NSStackBlock__: 0x16f07f6f8>
 signature: "v8@?0"
 invoke   : 0x100d86320 (/private/var/containers/Bundle/Application/2400E0E1-2CBA-42B3-B540-F104929D5B03/004-Block结构与签名.app/004-Block结构与签名`__main_block_invoke.10)
 copy     : 0x100d862b0 (/private/var/containers/Bundle/Application/2400E0E1-2CBA-42B3-B540-F104929D5B03/004-Block结构与签名.app/004-Block结构与签名`__copy_helper_block_e8_32r)
 dispose  : 0x100d862c0 (/private/var/containers/Bundle/Application/2400E0E1-2CBA-42B3-B540-F104929D5B03/004-Block结构与签名.app/004-Block结构与签名`__destroy_helper_block_e8_32r)

继续往下执行,直到来到_Block_copy的汇编代码


step3.png

在最下面的ret处打个断点,再次读取寄存器x0

(lldb) register read x0
      x0 = 0x00000002820d0000
(lldb) po 0x00000002820d0000
<__NSMallocBlock__: 0x2820d0000>
 signature: "v8@?0"
 invoke   : 0x100d86320 (/private/var/containers/Bundle/Application/2400E0E1-2CBA-42B3-B540-F104929D5B03/004-Block结构与签名.app/004-Block结构与签名`__main_block_invoke.10)
 copy     : 0x100d862b0 (/private/var/containers/Bundle/Application/2400E0E1-2CBA-42B3-B540-F104929D5B03/004-Block结构与签名.app/004-Block结构与签名`__copy_helper_block_e8_32r)
 dispose  : 0x100d862c0 (/private/var/containers/Bundle/Application/2400E0E1-2CBA-42B3-B540-F104929D5B03/004-Block结构与签名.app/004-Block结构与签名`__destroy_helper_block_e8_32r)

clang分析

在main.m中有如下代码:

void(^block1)(void) = ^{
    printf("global block");
};
block1();

int a = 10;
void(^block2)(void) = ^{
    printf("stack block %d",a);
};
block2();

__block int b = 10;
void(^block3)(void) = ^{
    printf("stack block %d",b++);
};
block3();

NSString *aa = @"aa";
void(^blockaa)(void) = ^{
    NSLog(@"%@",aa);
};
blockaa();

__block NSString *bb = [NSString stringWithFormat:@"mm"];
void(^blockbb)(void) = ^{
    bb = [bb stringByAppendingString:@"+++"];
    NSLog(@"%@",bb);
};
blockbb();

我们利用clang编译器生成.cpp文件后,查看一下block的底层实现,命令:

clang -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk main.m

先看一下block1的生成代码:

 void(*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);

//block1对应的结构体
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __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;
  }
};
//block1对应的方法实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("global block");
}
//block1对应的block描述
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)};

block2的生成代码,注意int a未使用__block:

int a = 10;
//此处是把a的值直接传进去了
void(*block2)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);

//block2的结构体只比block1多一个int a,其他不变
struct __main_block_impl_1 {
  struct __block_impl impl;
  struct __main_block_desc_1* Desc;
  int a;
  __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_1(struct __main_block_impl_1 *__cself) {
    //基础类型,是值传递
    int a = __cself->a; // bound by copy
    printf("stack block %d",a);
}

static struct __main_block_desc_1 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};

看一下block3的,int b使用了__block,所以整个变量b被包装成了__Block_byref类型的对象,拿到的b的指针地址,方便后续对原始的b进行操作:

__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 10};
void(*block3)(void) = ((void (*)())&__main_block_impl_2((void *)__main_block_func_2, &__main_block_desc_2_DATA, (__Block_byref_b_0 *)&b, 570425344));
((void (*)(__block_impl *))((__block_impl *)block3)->FuncPtr)((__block_impl *)block3);

//__block声明的int b会被包装成block_byref类型的对象
struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;//直接指向外面定义的int b包装对象
 int __flags;
 int __size;
 int b;//block内部他用的b
};

struct __main_block_impl_2 {
  struct __block_impl impl;
  struct __main_block_desc_2* Desc;
  __Block_byref_b_0 *b; // by ref
  __main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, __Block_byref_b_0 *_b, int flags=0) : b(_b->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_2(struct __main_block_impl_2 *__cself) {
  __Block_byref_b_0 *b = __cself->b; // bound by ref
  //所以此处操作__forwarding->b,就是操作的外部的包装对象
  printf("stack block %d",(b->__forwarding->b)++);
}
static void __main_block_copy_2(struct __main_block_impl_2*dst, struct __main_block_impl_2*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_2(struct __main_block_impl_2*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_2 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_2*, struct __main_block_impl_2*);
  void (*dispose)(struct __main_block_impl_2*);
} __main_block_desc_2_DATA = { 0, sizeof(struct __main_block_impl_2), __main_block_copy_2, __main_block_dispose_2};

blockaa的,NSString *aa和上面的int a没啥区别,都没对变量进行再次包装,在block中打印aa时也是直接拿的外部传进来的aa的指针进行的:

NSString *aa = (NSString *)&__NSConstantStringImpl__var_folders_np_137s52wn33b_l8qp8rpx951m0000gq_T_main_e269d6_mi_0;
void(*blockaa)(void) = ((void (*)())&__main_block_impl_3((void *)__main_block_func_3, &__main_block_desc_3_DATA, aa, 570425344));
((void (*)(__block_impl *))((__block_impl *)blockaa)->FuncPtr)((__block_impl *)blockaa);

struct __main_block_impl_3 {
  struct __block_impl impl;
  struct __main_block_desc_3* Desc;
  NSString *aa;
  __main_block_impl_3(void *fp, struct __main_block_desc_3 *desc, NSString *_aa, int flags=0) : aa(_aa) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_3(struct __main_block_impl_3 *__cself) {
  NSString *aa = __cself->aa; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_np_137s52wn33b_l8qp8rpx951m0000gq_T_main_e269d6_mi_1,aa);
        }
static void __main_block_copy_3(struct __main_block_impl_3*dst, struct __main_block_impl_3*src) {_Block_object_assign((void*)&dst->aa, (void*)src->aa, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_3(struct __main_block_impl_3*src) {_Block_object_dispose((void*)src->aa, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __main_block_desc_3 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_3*, struct __main_block_impl_3*);
  void (*dispose)(struct __main_block_impl_3*);
} __main_block_desc_3_DATA = { 0, sizeof(struct __main_block_impl_3), __main_block_copy_3, __main_block_dispose_3};

最复杂的blockbb的,这里对NSString *bb变量的包装,比上面的int b还多了一层__Block_byref_id_object_copy和__Block_byref_id_object_dispose,这是因为对象类型不仅需要传值,需要考虑引用计数增加以及释放:

__attribute__((__blocks__(byref))) __Block_byref_bb_1 bb = {(void*)0,(__Block_byref_bb_1 *)&bb, 33554432, sizeof(__Block_byref_bb_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_np_137s52wn33b_l8qp8rpx951m0000gq_T_main_e269d6_mi_2)};
void(*blockbb)(void) = ((void (*)())&__main_block_impl_4((void *)__main_block_func_4, &__main_block_desc_4_DATA, (__Block_byref_bb_1 *)&bb, 570425344));
((void (*)(__block_impl *))((__block_impl *)blockbb)->FuncPtr)((__block_impl *)blockbb);

struct __Block_byref_bb_1 {
  void *__isa;
__Block_byref_bb_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *bb;
};

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);
}

struct __main_block_impl_4 {
  struct __block_impl impl;
  struct __main_block_desc_4* Desc;
  __Block_byref_bb_1 *bb; // by ref
  __main_block_impl_4(void *fp, struct __main_block_desc_4 *desc, __Block_byref_bb_1 *_bb, int flags=0) : bb(_bb->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_4(struct __main_block_impl_4 *__cself) {
  __Block_byref_bb_1 *bb = __cself->bb; // bound by ref

    (bb->__forwarding->bb) = ((NSString *(*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)(bb->__forwarding->bb), sel_registerName("stringByAppendingString:"), (NSString *)&__NSConstantStringImpl__var_folders_np_137s52wn33b_l8qp8rpx951m0000gq_T_main_6649bc_mi_3);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_np_137s52wn33b_l8qp8rpx951m0000gq_T_main_6649bc_mi_4,(bb->__forwarding->bb));
}

static void __main_block_copy_4(struct __main_block_impl_4*dst, struct __main_block_impl_4*src) {_Block_object_assign((void*)&dst->bb, (void*)src->bb, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_4(struct __main_block_impl_4*src) {_Block_object_dispose((void*)src->bb, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_4 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_4*, struct __main_block_impl_4*);
  void (*dispose)(struct __main_block_impl_4*);
} __main_block_desc_4_DATA = { 0, sizeof(struct __main_block_impl_4), __main_block_copy_4, __main_block_dispose_4};

源码解读

上面我们从编译器层面看一下block的实现原理,但block其实还有它的底层实现,例如_Block_copy、_Block_object_assign、_Block_object_dispose的具体实现是怎样的呢。可以通过查看block的底层libclosure-73源码来分析一下

_Block_copy源码

block对象在底层实现的关键方法

void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock; // gloal类型的,啥都不需要做
    }
    else { // statck类型的
        // Its a stack block.  Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        //对于需要持有外部变量的都会触发block对象的__main_block_copy方法
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        //转换成mallocBlock
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
_Block_object_assign源码

block对象捕获外部y变量的关键方法。对于需要持有外部变量的block,在转换成mallocBlock后都会调用其对应的__main_block_copy方法,而__main_block_copy方法就调用_Block_object_assign方法来对外部的包装对象进行赋值逻辑判断

void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
   
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
        //未用__block修饰的外部变量,交给系统进行内存管理,如果是ARC的话会进行自动的retain和release,这也是为什么block会暂时持有外部对象的原因
        case BLOCK_FIELD_IS_OBJECT:        
        _Block_retain_object(object);
        *dest = object;
        break;
        
      //block类型的变量,再次走到上面的block_copy中来
      case BLOCK_FIELD_IS_BLOCK:
        *dest = _Block_copy(object);
        break;
    
      //__block修饰的变量,会进入到_Block_byref_copy
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        *dest = _Block_byref_copy(object);
        break;
        
      //以下几种类型都不会持有对象了
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:        
        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:        
        *dest = object;
        break;

      default:
        break;
    }
}
_Block_byref_copy源码

外部变量包装对象的复制方法

static struct Block_byref *_Block_byref_copy(const void *arg) {
    
    // Block_byref  结构体
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        
        // 对传进来的对象再次copy,并且修改了src的forwarding为新的对象
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        
        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_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
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }
            //此处是触发外部Block_byref_id_object_copy方法调用的关键,如果__block修饰的对象类型的变量,还需要进一步执行_Block_object_assign
            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}

相关文章

  • block分析(下)

    block通过clang分析 带着下面的疑问,我们去探索block原理 探索block底层源码 block在底层是...

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

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

  • Block原理分析

    block类型 我们都知道block有三种类型:NSGlobalBlock、NSStackBlock、NSMall...

  • 基础的block实现原理

    理解定义 BLock官方的定义 分析实现原理 从最简单的block开始 将下面的 simple_test.m 代码...

  • iOS:Block(二)

    推荐阅读:Block原理分析(一) 目录一,修改变量二,基本数据类型的__block变量三,__forwardin...

  • Block原理分析详解

    1 基本说明 Block一直是OC的一个重点、难点、黑科技。Block在日常项目中经常使用,他的实现方式和一般的o...

  • ios block原理分析

    Block本质上也是一个OC对象,它内部也有个isa指针,它是封装了函数调用以及函数调用环境的OC对象。闭包 = ...

  • Block底层原理分析

    iOSBlock底层原理解析 目录 Block底层解析什么是block?block编译转换结构block实际结构b...

  • Block原理分析(1)

    前情提要 1.闭包、Block是一个带有自动变量值(可以截获自动变量值)的匿名函数。截获的含义是保存该自动变量的瞬...

  • Block原理分析(2)完结

    前情提要 基于Block原理分析(1)[https://www.jianshu.com/p/afd031effac...

网友评论

      本文标题:Block原理分析

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