陈江川

邮箱:jiangchuanc@gmail.com

Objective-C中的Block<一>

注意:文中涉及到的代码及结论如果没有特别说明,那么都是在MRC环境下测试!!!

关于block的定义就不扯了,直奔主题。Block本质也是对象,所以我们先了解下三种Block的类:

它们之间关系图大致如下:

Block三种类型

NSGlobalBlock

搞明白这个以后,我们先从__NSGlobalBlock__开始着手:

#include <stdio.h>

int main(int argc, char *argv[])
{
    void (^globalBlk)(void) = ^{
        printf("globalBlk");
    };
    return 0;
}

使用clang -rewrite-objc globalBlock.c

// 下面代码为简化版
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

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) {

  printf("globalBlk");
}

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, char *argv[])
{
  void (*globalBlk)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
 
    return 0;
}

从main开始看:

  • main调用了__main_block_impl_0结构体的构造函数,传入两个参数:__main_block_func_0和__main_block_desc_0_DATA;

    • __main_block_func_0就是block中的printf打印函数;
    • __main_block_desc_0_DATA为block的描述信息。
  • 再看__main_block_impl_0结构体,里面有两个成员和一个构造函数:

    • impl:为struct __block_impl类型,里面包含了该block所属的类,以及block内部的代码块;
    • Desc:为struct __main_block_desc_0*类型,里面对block的大小进行了描述。

这里说明下,这里block所属的类为__NSGlobalBlock__,而不是_NSConcreteStackBlock,我们可以在Xcode上进行验证:

// 只要不访问外界局部变量,那么block就属于NSGlobalBlock类型
void (^blk2)(void) = ^{
};

blk2 = [blk2 copy];
blk2();
NSLog(@"blk2 = %@", blk2);
[blk2 release];

打印log为:
blk2 = <__NSGlobalBlock__: 0x1079340d0>

出现这个问题估计是clang编译的差异吧。

NSStackBlock

#include <stdio.h>

int main(int argc, char *argv[])
{
    int a = 2;
    void (^staticBlk)(void) = ^{
        printf("staticBlk %d", a);
    };
    return 0;
}

输入命令:clang -rewrite-objc staticBlock.c

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

  printf("staticBlk %d", a);
}

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, char *argv[])
{
 int a = 2;
 void (*staticBlk)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));
 return 0;
}

我们在block中访问了外部的局部变量a,这里我们只说不同点:

  • main函数中调用了__main_block_impl_0结构体的构造方法,里面多了一个参数a,这个a就是我们访问的外部局部变量;
  • __main_block_impl_0结构体中多了int a;成员,用来保存我们访问的外部局部变量;
  • __main_block_func_0函数中,我们取出__main_block_impl_0结构体中成员a的值,然后打印出来。

该示例中的block所属的类为_NSConcreteStackBlock,我们可以在Xcode中验证:

int a = 0;
void (^blk2)(void) = ^{
    NSLog(@"blk2 %d", a);
};
blk2();
NSLog(@"blk2 = %@", blk2);

打印log为:
blk2 = <__NSStackBlock__: 0x7fff52ba3958>

NSMallocBlock

要把block拷贝到堆区,就需要用到copy,所以这里我们直接在Xcode中验证:

int a = 0;
void (^blk2)(void) = ^{
   NSLog(@"blk2 %d", a);
};
blk2 = [blk2 copy];
blk2();
NSLog(@"blk2 = %@", blk2);
[blk2 release];

打印log为:
blk2 = <__NSMallocBlock__: 0x7fd1d9ca1d60>

__block

在了解完Block的三种类型后,我们再聊聊怎么在block中修改变量的值。变量可分为:局部变量、静态局部变量、全局变量、静态全局变量;

  • 修改局部变量,直接报红,提示:Variable is not assignable (missing __block type specifier),就是说变量不能被修改,要修改局部变量,必须在定义的时候加上 __block。

  • 修改静态局部变量,可以被修改,打印的log为3

  • 修改全局变量,可以被修改,打印log为4

  • 修改静态全局变量,可以被修改,打印log为5

综上我们得出一个结论:保存在栈区的变量,在block中是不能被修改的。

那有什么办法在block中修改栈区变量的值?当然有,那就是使用 __block修饰局部变量。
我们先在Xcode中验证:

打印log为2。那 __block到底做了什么能让我们在block中修改局部变量?我们又得使用clang了。

#include <stdio.h>

int main(int argc, char *argv[])
{
    __block int localVal = 1;

    void (^blk)() = ^{
        localVal = 2;
    };

    blk();
    printf("%d", localVal);
    
    return 0;
}

clange -rewrite-objc blockModifier.m

// 简化了很多代码
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __Block_byref_localVal_0 {
  void *__isa;
__Block_byref_localVal_0 *__forwarding;
 int __flags;
 int __size;
 int localVal;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_localVal_0 *localVal; // by ref
  
  __main_block_impl_0(void *fp, 
                      struct __main_block_desc_0 *desc, 
                      __Block_byref_localVal_0 *_localVal, 
                      int flags=0) : localVal(_localVal->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) 
{
    _Block_object_dispose((void*)src->localVal, 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};


static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_localVal_0 *localVal = __cself->localVal; // bound by ref

    (localVal->__forwarding->localVal) = 2;
}

int main(int argc, char *argv[])
{
    __Block_byref_localVal_0 localVal = {(void*)0, 
                                         &localVal, 
                                         0, 
                                         sizeof(__Block_byref_localVal_0),
                                         1};

    void (*blk)() = __main_block_impl_0(__main_block_func_0, 
                                        &__main_block_desc_0_DATA,
                                        &localVal, 
                                        570425344));

    blk->FuncPtr(blk);
    
    printf("%d", (localVal.__forwarding->localVal));

    return 0;
}

逐步分析:

main函数:

  • 首先初始化了localVal变量,而变量的类型为:__Block_byref_localVal_0(__block的原型);

  • 初始化 __main_block_impl_0

从上面可以知道,__Block_byref_localVal_0结构体中的__forwarding,指向了自身。
而且localVal都是以地址的方式进行传值!!!

struct __main_block_desc_0结构体中多个两个函数:

// [block copy]本质调用了下面函数
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
// [block release]本质调用了下面函数
void (*dispose)(struct __main_block_impl_0*);

//   copy函数的实现
static void __main_block_copy_0(struct __main_block_impl_0*dst, 
                                struct __main_block_impl_0*src) 
{
    _Block_object_assign((void*)&dst->localVal, 
                         (void*)src->localVal, 
                         8/*BLOCK_FIELD_IS_BYREF*/);
}

// dispose函数的实现
static void __main_block_dispose_0(struct __main_block_impl_0*src) 
{
    _Block_object_dispose((void*)src->localVal, 8/*BLOCK_FIELD_IS_BYREF*/);
}

/*
    _Block_object_assign和_Block_object_dispose最后一个参数都是8,这个8代表BLOCK_FIELD_IS_BYREF,
    
    Block_private.h中对BLOCK_FIELD_IS_BYREF进行了定义:
    
    // the on stack structure holding the __block variable
    // 用来说明被__block修饰的是栈区的变量
    BLOCK_FIELD_IS_BYREF    =  8,
    
 */

然后调用__main_block_func_0函数:

  • 首先获取__block localVal变量的地址:__Block_byref_localVal_0 *localVal = __cself->localVal;
  • 给localVal中__forwarding指向的localVal赋值为2

最后我们再验证一个东西,__block修饰的局部变量地址有没有被改变?

- (void)testBlock {
    __block int localVal = 1;
    NSLog(@"11 %p-(%d)", &localVal, localVal);
    void (^blk)() = ^{
        localVal = 2;
        NSLog(@"22 %p-(%d)", &localVal, localVal);
    };

    NSLog(@"33 %p-(%d)", &localVal, localVal);
    blk();
    NSLog(@"44 %p-(%d)", &localVal, localVal);
}

log:
11 0x7fff5b9409b8-(1)
33 0x7fff5b9409b8-(1)
22 0x7fff5b9409b8-(2)
44 0x7fff5b9409b8-(2)

__block修饰的变量,并不能改变该变量的地址,该变量之所以能在Block中被修改,是因为把该变量的地址传给了Block!!!

« Objective-C中的Block<二> objc_msgSend内部到底做了什么? »