JHHK

欢迎来到我的个人网站
行者常至 为者常成

Blocks(四)

目录

__block修饰符

一、为什么在Block内不能修改变量的值

typedef void(^blk)(void);
int main(int argc, char * argv[]) {
    @autoreleasepool {
        int age = 10;
        Person * person = [[Person alloc] init];
        blk b=^(){
            NSLog(@"age is %d,person is %@",age,person);
            //age = 20;//该行代码会报错 
            //person = [[Person alloc] init];//该行代码会报错 
        };
        
        b();
    }
    return 0;
}

通过前面学习我们知道Block会捕获auto变量,其结构体如下。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  Person *person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, Person *_person, int flags=0) : age(_age), person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

调用函数如下

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int age = __cself->age; // bound by copy
    Person *person = __cself->person; // bound by copy
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_s1_dbzn9w0x6h121vpvtg1vfcf80000gn_T_main_468f42_mi_0,age,person);
}

可知Block表达式内访问的age和person变量实质是结构体的成员变量。另外在函数__main_block_func_0内是没办法做到访问main函数内的age和person变量的。所以程序会报错。

那么我们要在Block表达式内修改age和person变量要怎么做呢?
一种方法是将age和person变为全局变量,另外一种方法是age和person变量变为静态局部变量使用 static修饰。
还有一种方法就是使用__block修饰符。

二、__block修饰符

typedef void(^blk)(void);
int main(int argc, char * argv[]) {
    @autoreleasepool {
        __block int age = 10;
        blk b=^(){
            age = 20;
        };
        b();
        NSLog(@"age = %d",age);
    }
    return 0;
}

用__block修饰后就可以在Block表达式内修改age变量的值。

执行指令并精简代码:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

typedef void(*blk)(void);
struct __Block_byref_age_0 {
    void *__isa;
    __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_age_0 *age; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref
    
    (age->__forwarding->age) = 20;
}

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

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

int main(int argc, char * argv[]) {
    __Block_byref_age_0 age = {
        (void*)0,
        (__Block_byref_age_0 *)&age,
        0,
        sizeof(__Block_byref_age_0),
        10
    };
    
    
    blk b=&__main_block_impl_0(
                               __main_block_func_0,
                               &__main_block_desc_0_DATA,
                               (__Block_byref_age_0 *)&age,
                               570425344));
    
    
    ((__block_impl *)b)->FuncPtr(b);

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_s1_dbzn9w0x6h121vpvtg1vfcf80000gn_T_main_0d7163_mi_1,(age.__forwarding->age));

    return 0;
}

我们看到int age = 10;变成了结构体__Block_byref_age_0 age;
查看结构体__Block_byref_age_0的定义,可以看出有一个isa指针,就是变成了对象。
结构体__main_block_impl_0捕获的是__Block_byref_age_0 age;对象的地址。
方法__main_block_func_0内部的调用就是通过该地址找到对象,在找到对象下面的成员变量age进行修改的。

另外有一个细节需要注意:就是NSLog(@"age = %d",age);访问的age是age.__forwarding->age。而不是__Block_byref_age_0 *age;

__Block_byref_age_0的示意图

img

Block的内存管理

当Block在栈上时与访问对象auto变量一样并不会对__block修饰的变量(其本质就是对象)进行内存管理。

当Block被拷贝的堆上时,会调用Block内部的copy函数,这时Block会对捕获的对象进行内存管理,如下图所示。

img

当Block从堆中移除时,会调用Block内部的dispose函数,这时的内存管理如下图所示。

img

这里需要说明一下,__block修饰的变量底层会转为对象,Block对其捕获与Block捕获正常的对象auto变量是相同的,但有一点需要注意。

//Person对象可以有__strong __weak 修饰符修饰,来决定Block内部对Person对象的内存管理。
Perosn * person = [[Person alloc] init];
__weak Perosn * person = [[Person alloc] init];

//age变量此处不能指定__strong __weak 所以Block内部对__block int age变量一定是强引用。
__block int age = 10;

//另外还有一种形式可以通过指令转成C++代码看下其底层。
__block __weak  Person * person = [[Person alloc] init];

forwarding指针

当__block修饰的变量只在栈上的时候,forwarding指针指向自身。
当__block修饰的变量即在栈上存在,又被copy到堆上时,在栈上的__block变量的forwarding指针指向堆上的__block。

img

这就是为什么是 (age->__forwarding->age) = 20;而不是(age->age) = 20;的原因


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.