oc block详解

本文详细讲解了OC Block的实现过程,包括Block的数据结构、类型、捕捉变量的方式、内存管理以及在ARC环境下的工作原理。通过对Block的底层解析,阐述了__block修饰符的作用以及Block在栈和堆之间的转换,强调了Block作为参数传递和返回值时的注意事项。
摘要由CSDN通过智能技术生成

oc block详解

我们知道在Block使用中,Block内部能够读取外部局部变量的值。但我们需要改变这个变量的值时,我们需要给它附加上__block修饰符。__block另外一个比较多的使用场景是,为了避免某些情况下Block循环引用的问题,我们也可以给相应对象加上__block 修饰符。今天为大家详细讲解Block实现的详细过程

  • block的实际结构
  • block的类型
  • block是如何捕捉变量的
  • 不同类型的block的复制
  • ARC中block的工作
  • 外部变量的复制

block的实际结构
下面是block的数据结构定义

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

通过该图,我们可以知道,一个 block 实例实际上由 6 部分构成:
1. isa 指针,所有对象都有该指针,用于实现对象相关的功能。
2. flags,用于按 bit 位表示一些 block 的附加信息。
3. reserved,保留变量。
4. invoke,函数指针,指向具体的 block 实现的函数调用地址。
5. descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
6. variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。

了解block数据结构后,我们接下来对block进行底层解析,首先看一段最简单的block代码:

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

        ^{ };
    }
    return 0;
}

利用clang编译器把这段代码编译成c++的实现,我们会获得一个.cpp文件,打开它,可以从它最底部获得如下代码:

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}

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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    }
    return 0;
}

不难看出其中的__main_block_impl_0就是block的一个C++的实现(最后面的_0代表是main中的第几个block),整个结构和之前的所讲的结构是完全吻合的。在定义__main_block_impl_0结构体时,同时创建了__main_block_func_0__main_block_desc_0的对象__main_block_desc_0_DATA__main_block_func_0为block实现函数(因为block为空,所以实现函数也为空),__main_block_desc_0_DATA为block的附加描述信息。并且在main函数中利用__main_block_impl_0的构造函数将上面两个参数传入进去。这就是对一个简单的block块的底层解析。


block的类型
block的常见类型有3种:

  • _NSConcreteGlobalBlock(全局)
  • _NSConcreteStackBlock(栈)
  • _NSConcreteMallocBlock(堆)

附上UNIX的进程虚拟内存段分布图:
这里写图片描述

  • _NSConcreteStackBlock 保存在栈中的block,出栈时会被销毁
  • _NSConcreteGlobalBlock 全局的静态block,不会访问任何外部变量
  • _NSConcreteMallocBlock 保存在堆中的block,当引用计数为0时会被销毁

注意:分配在全局变量上的Block,在变量作用域外也可以通过指针安全的访问。但分配在栈上的Block,如果它所属的变量作用域结束,该Block就被废弃。同样地,__block变量也分配在栈上,当超过该变量的作用域时,该__block变量也会被废弃。

这时候_NSConcreteMallocBlock就显得格外重要了。Blocks提供了将Block和__block变量从栈上复制到堆上的方法来解决这个问题。将分配到栈上的Block复制到堆上,这样但栈上的Block超过它原本作用域时,堆上的Block还可以继续存在。由于block的复制最终都会调用_Block_copy_internal函数,所以观察这个函数就可以知道堆中block是如何被创建的了:

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    ...
    aBlock = (struct Block_layout *)arg;
    ...
    // Its a stack block.  Make a copy.
    if (!isGC) {
        // 申请block的堆内存
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        // 拷贝栈中block到刚申请的堆内存中
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        // 改变isa指向_NSConcreteMallocBlock,即堆block类型
        result->isa = _NSConcreteMallocBlock;
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
    else {
        ...
    }
}

从以上代码以及注释可以很清楚的看出,函数通过memmove将栈中的block的内容拷贝到了堆中,并使isa指向了_NSConcreteMallocBlock
block主要的一些学问就出在栈中block向堆中block的转移过程中了。


block是如何捕捉变量的
我们通过编译转换捕捉不同类型的block,以对他们进行区别。
局部变量
前:

- (void)test
{
    int a;
    ^{a;};
}

后:

struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  int a;
  // a(_a)是构造函数的参数列表初始化形式,相当于a = _a。从_I_Person_test看,传入的就是a
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
a;}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值