block详解

block是C语言的扩充,既像函数那样能够执行又像参数那样进行传递,是可以带自动变量的匿名函数。在OC中,block常常会造成会造成循环引用,我们要用__weak修饰符来打破循引用,达到正常释放的目的。一个小的知识点,block为什么会对其块内的对象进行强引用?因为系统或我们对block进行了copy操作。这些都不是本篇博客的中心,这篇博客主要来看下block的底层实现。
先来看下最简单block(没有参数、没有返回值、block不做任何操作)通过Clang编译后的C++ 代码:

//声明
void (^block)(void) = ^() {
};
//执行
block();
编译后=>
//声明:可以看到这个block是一个没有返回值、没有参数的函数指针
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//执行
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

//C函数:
void func();
void (*funcP)() = &func;
(*funcP)();

下面来看下__main_block_impl_0、__main_block_func_0、__main_block_desc_0_DATA是什么?

//block结构体
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;
  }
};
//block结构体
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

//copy的备份,也就是block块内的内容
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

}

//block的描述:block的已用空间和size
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)};

block会对block块内的局部变量进行值引用:因为block的执行可能已经超出了局部变量的作用域。

    int a = 0;
    NSMutableArray *array = [[NSMutableArray alloc] init];
    void (^block)(void) = ^() {
        a = 1;                                //报错!不能修改
        array = [[NSMutableArray alloc] init]; //报错!不能修改
        [array addObject:[NSObject new]];      //可以正常进行操作
    };
    block();
    NSLog(@"---%d", a);   //a=0; 
}

在block块中a其实就是0相当于一个常量,array是一个地址,这个地址也是不可更改的,当能通过地址进行相关操作。
如果有需求对a和array要进行修改,应该用__block进行修饰。

{
    __block int a = 0;
    __block NSMutableArray *array = [[NSMutableArray alloc] init];
    void (^block)(void) = ^() {
        a = 1;                                //可以修改
        array = [[NSMutableArray alloc] init]; //可以修改
        [array addObject:[NSObject new]];      //可以正常进行操作
    };
    block();
    NSLog(@"---%d", a);   //a=1; 
}

另外一些C语言变量也允许block修改:静态变量,静态全局变量,全局变量。那么这是为什么呢?

先来看下允许block修改C语言变量:静态变量,静态全局变量,全局变量。

int global_int = 1;
static int static_global_int = 2;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int static_int = 3;
            void (^block)(void) = ^() {
            global_int = 10;
            static_global_int = 20;
            static_int = 30;
        };
        block();
    }
    return 0;
}

编译转换后 =>

int global_int = 1;
static int static_global_int = 2;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_int;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_int, int flags=0) : static_int(_static_int) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_int = __cself->static_int; // bound by copy
    global_int = 10;
    static_global_int = 20;
    (*static_int) = 30;
}

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; 
    static int static_int = 3;
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_int));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

可以看到全局变量和静态全局变量的使用和转换前一样,正常访问,因为他们不用担心作用域的问题,而静态变量的指针被copy到了__main_block_func_0结构体中,利用指针是解决超出作用域使用变量的最简单的方法。

来看下一点:__block说明符
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int a = 100;
        void (^block)(void) = ^() {
            a = 1000;
        };
        block();
    }
    return 0;
}

编译转换后 =>

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 100};
         void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref
            (a->__forwarding->a) = 1000;
        }

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

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

和上面最简单的block相比,我们仅仅是加了__block int a = 100;,但是block的代码量却增加了很多。通过代码看到__block的变量a竟然变成了一个结构体变量:该结构体的初始值为100,这表明结构体a和int a是等效的。上面可以看到__Block_byref_a_0结构体的定义,发最后一个成员变量是int a,也能证明我们的猜想。

__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
    (void*)0,
    (__Block_byref_a_0 *)&a,
     0, 
     sizeof(__Block_byref_a_0), 
     100
};

再来看__main_block_impl_0结构体,它持有的是__Block_byref_a_0结构体a的指针;在__main_block_func_0结构体中,通过__Block_byref_a_0结构体a的指针访问到成员变量__forwarding,__forwarding是一个指向自身的指针,通过__forwarding找到自己的成员变量a,并进行修改为1000即(a->__forwarding->a) = 1000
这里留个疑问:为什么要通过__forwarding指针呢?是不是有点多余?这个我们后面再讲!
通过上面的转化代码可以看到__Block_byref_a_0结构体是定义在block的外面的,这么做是为了多个block使用时处理的。
block按照其存储的区域可分为:__NSStackBlock__、__NSMallocBlock__、__NSGlobalBlock__

stackBlock 通过copy到堆区或者全局区来解决block和其使用的__block临时变量在超出作用域被释放的问题。笔者声明了一个global block来看下他的部分转换后的代码

struct __block_block_impl_0 {
  struct __block_impl impl;
  struct __block_block_desc_0* Desc;
  __block_block_impl_0(void *fp, struct __block_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

看到这句代码impl.isa = &_NSConcreteGlobalBlock;这个isa是global block变量的地址,可以看到它已经变成一个全局block了。__block的变量也从栈中复制到了堆上。
如果多个block同时持有一个__block的变量,当一个block从栈中复制到堆中之后会对同时复制到堆上的__block的变量进行强引用,剩余的block再从栈中复制到堆中的时候也会对之前复制到堆中的__block的变量进行强引用,增加引用计数。这种模式和OC的ARC模式一样。
最后我们来回答之前留下的一个问题:为什么要通过__forwarding指针呢?是不是有点多余?
回答当然不是多余的,之所以有这个__forwarding是为了一个目的:无论访问的是堆中的__block变量还是栈中的__block变量都能正确的访问。怎么达到这个目的,就是利用我们说的__forwarding指针。在copy到堆中之后,栈中的__forwarding指针会从指向自己变成指向堆中,这样就能达到目的了。
这里写图片描述

至此我们的block大致就讲完了,我们的讲解是简单的block,没有返回值没有参数,不过这已经足够我们对block进行详细的了解了,当你懂得了这些,更复杂的block也就不在话下!

参考:
《Objective-C高级编程》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值