Block内修改变量的值

进入主题之前,让我们回顾“Block的类型”中的内存分布图,看看下面这些变量保存在内存的哪块区域:

int var;
int var1 = 1;
static int var2;
static int var3 = 2;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int var4 = 3;
        static int var5;
        int var6 = 4;
    }
    return 0;
}
  1. var、var2、var5保存在BSS区;
  2. var1、var3、var4保存在Data区;
  3. var6保存在Stack区。

现在看下面代码:

typedef void(^Block)(void);

// 全局初始化变量,保存在内存的Data区
int globalVar = 10;

// 静态初始化变量,保存在内存的Data区
static int outsideStaticVar = 20;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 静态初始化变量,保存在内存的Data区
        static int insideStaticVar = 30;

        // 局部变量,保存在内存的Stack区
        int var = 40;

        // 创建一个Stack区的Block
        Block block = ^{
            globalVar = 11;
            outsideStaticVar = 21;
            insideStaticVar = 31;
//            var = 41; // 编译报错,Block内不能修改栈区的变量,只能读取
            NSLog(@"Block内<globalVar = %d, outsideStaticVar = %d, insideStaticVar = %d, var = %d>", globalVar, outsideStaticVar, insideStaticVar, var);
        };
    }
    return 0;
}

由此可见,block内部不能修改block外部Stack区的变量,也就是局部变量。那怎么才能修改呢?这就是下节要讲的。

block的内部实现

没有__block修饰

我们都知道,经过__block修饰的局部变量,可以在Block内部被修改,那它的原理又是什么?我们可以先分析不用__block修饰的代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int var = 10;
        void (^block)(void) = ^{
            printf("%d", var);
        };
        block();
    }
    return 0;
}

使用命令:clang -rewrite-objc main.m得到cpp代码,简化:

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 var;
    // __main_block_impl_0结构体的构造方法,初始化impl、Des和var
    // var = _var
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _var, int flags=0) : var(_var) {
        impl.isa = &_NSConcreteStackBlock; // block类型,但是这里有时不准确
        impl.Flags = flags;
        impl.FuncPtr = fp; // 执行block()时调用的函数
        Desc = desc; // block的描述结构体
    }
};

// block内部执行的代码,被赋值给impl.FuncPtr
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    // 取block结构体中var成员的值
    int var = __cself->var; // bound by copy
    printf("%d", var);
}

// block描述
static struct __main_block_desc_0 {
    size_t reserved; // 保留
    size_t Block_size; // block的大小,就是__main_block_impl_0结构体的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ 
    { __AtAutoreleasePool __autoreleasepool; 
        int var = 10;

        // 创建Block,本质就是创建__main_block_impl_0结构体
        // 参数分别是:block内部执行的函数指针、block描述结构体指针、var变量的值
        struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, var));
        struct __main_block_impl_0 *block = &tmp;

        // 执行block()
        *block->impl.FuncPtr(block);
    }
    return 0;
}

__block修饰

现在我们来看看有__block修饰会有什么不同:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int var = 10;
        void (^block)(void) = ^{
            printf("var = %d,<%p>\n", var++, &var);
        };
        block();
    }
    return 0;
}

转化代码简化如下:

// 使用__block修饰后,多了这个结构体
struct __Block_byref_var_0 {
    void *__isa;
    __Block_byref_var_0 *__forwarding;
    int __flags;
    int __size;
    int var;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    // 现在var是一个结构体指针
    __Block_byref_var_0 *var; // by ref
    // 结构体构造函数,这里注意var = _var->__forwarding,也就是说__forwarding指向了自己
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_var_0 *_var, int flags=0) : var(_var->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// block()指向的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    // 定义结构体指针,指向__main_block_impl_0中的var
    __Block_byref_var_0 *var = __cself->var; // bound by ref
    // 获取结构体中的__forwarding中的var,
    // 其实在这就是:var->var,那为什么还要经过__forwarding指针呢?这个疑问暂时放在这
    printf("var = %d,<%p>\n", (var->__forwarding->var)++, &(var->__forwarding->var));
}

// 当Stack上的Block复制到Heap时调用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->var, (void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);
}

// 当Heap区的Block被废弃的时候调用
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    // block描述结构体多了两个函数:copy,dispose
    // 当Stack上的Block复制到Heap时调用
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    // 当Heap区的Block被废弃的时候调用
    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[]) {
    /* @autoreleasepool */ 
    { __AtAutoreleasePool __autoreleasepool; 

        // 创建__Block_byref_var_0结构体变量var
        __attribute__((__blocks__(byref))) __Block_byref_var_0 var = {(void*)0,(__Block_byref_var_0 *)&var, 0, sizeof(__Block_byref_var_0), 10};

        // 和之前一样,唯一不同的是var是结构体指针
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_var_0 *)&var, 570425344));

        // 执行block()
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

从上面c++代码我们可以看出,使用__block修饰的var变量以结构体的形式出现,传给block的结构体以指针的形式,这样在block内部就可以修改局部变量的值。

results matching ""

    No results matching ""