OC底层-Block本质(6、block内修改变量的值)

如何修改

分析从代码入手:ios

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        Block block = ^ {
             age = 20; // 没法修改,会报错
            NSLog(@"%d",age);
        };
        block();
    }
    return 0;
}


复制代码

默认状况下block不能修改外部的局部变量。经过以前对源码的分析能够知道,由于变量的做用于都不在同一个函数中,固然不能去想固然的作修改了,那到底怎么去修改呢,下面会详细说道。bash

age是在main函数内部声明的,说明age的内存存在于main函数的栈空间内部,可是block内部的代码在__main_block_func_0函数内部。__main_block_func_0函数内部没法访问age变量的内存空间,两个函数的栈空间不同,__main_block_func_0内部拿到的age是block结构体内部的age,所以没法在__main_block_func_0函数内部去修改main函数内部的变量。iphone

使用static修饰修改变量的值

前面文章有提到过static修饰的age变量传递到block内部的是指针传递,在__main_block_func_0函数内部就能够拿到age变量的内存地址,所以就能够在block内部修改age的值。函数

__block修改

__block用于解决block内部不能修改auto变量值的问题.ui

注意:__block不能修饰静态变量(static) 和全局变量spa

咱们将上面的代码作个修改,而后运行起来。命令行

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int age = 10;
        Block block = ^ {
             age = 20; 
            NSLog(@"%d",age);
            /// 打印 20
        };
        block();
    }
    return 0;
}


复制代码

经过__block能够修改auto age的值。这是什么缘由形成的呢?咱们能够深刻源码中去找到答案指针

源码

一样经过命令行code

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.mcdn

来生成咱们的main.cpp文件

/// 经过关键字__block生成的age结构体
struct __Block_byref_age_0 {
  void *__isa; // isa指针
__Block_byref_age_0 *__forwarding; //  __forwarding指针指向本身
 int __flags; //默认值,暂时不考虑
 int __size; // 变量占用的空间大小
 int age; // age 变量
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref   变量age生成的结构体
  __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 __main_block_impl_0 *__cself 去找到__main_block_impl_0 内部age结构体

             (age->__forwarding->age) = 20;  // 经过age结构体找到__forwarding指针,而后经过__forwarding指向本身在找到age变量去赋值20,作修改
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_n2_0nslhwnj04qg5hyxlg2d8ych0000gn_T_main_6af310_mi_0,(age->__forwarding->age));

        }
/// 内部函数, block copy会调用
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*/
                         );}
///  block 销毁会调用
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, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
       
        /// block内部的函数调用
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
复制代码

上述源码中能够发现

首先被__block修饰的age变量声明变为名为age的__Block_byref_age_0结构体,也就是说加上__block修饰的话捕获到的block内的变量为__Block_byref_age_0类型的结构体。

__Block_byref_age_0结构体

__isa指针 :__Block_byref_age_0中也有isa指针也就是说__Block_byref_age_0本质也一个对象。

__forwarding :__forwarding是__Block_byref_age_0结构体类型的,而且__forwarding存储的值为(__Block_byref_age_0 *)&age,即结构体本身的内存地址。

__flags :0

__size :sizeof(__Block_byref_age_0)即__Block_byref_age_0所占用的内存空间。

age :真正存储变量的地方,这里存储局部变量10。

接着将__Block_byref_age_0结构体age存入__main_block_impl_0结构体中,并赋值给__Block_byref_age_0 *age;

以后调用block,首先取出__main_block_impl_0中的age,经过age结构体拿到__forwarding指针,__forwarding中保存的就是__Block_byref_age_0结构体自己,这里也就是age(__Block_byref_age_0),在经过__forwarding拿到结构体中的age(10)变量并修改其值。

__forwarding

__forwarding是指向本身的指针。这样的作法是为了方便内存管理,后面会有详细说明。

到此为止,__block为何能修改变量的值已经很清晰了。__block将变量包装成对象,而后在把age封装在结构体里面,block内部存储的变量为结构体指针,也就能够经过指针找到内存地址进而修改变量的值。

__block修饰对象类型

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block Person *person = [[Person alloc] init];
        NSLog(@"%@",person);
        Block block = ^{
            NSLog(@"%@",person);
        };
        block();
    }
    return 0;
}

复制代码

源码

struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
    
//相比于int age  这对象person多了两个函数,是用来管理内存管理的
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__strong person;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__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_person_0 *person = __cself->person; // bound by ref


            NSLog((NSString *)&__NSConstantStringImpl__var_folders_n2_0nslhwnj04qg5hyxlg2d8ych0000gn_T_main_7476f0_mi_1,(person->__forwarding->person));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 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, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"))};
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_n2_0nslhwnj04qg5hyxlg2d8ych0000gn_T_main_7476f0_mi_0,(person.__forwarding->person));
        Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
复制代码

经过源码查看,将对象包装在一个新的结构体中。结构体内部会有一个person对象,不同的地方是结构体内部添加了内存管理的两个函数__Block_byref_id_object_copy和__Block_byref_id_object_dispose,这两个函数会在下一章节内存管理中心详细说明。

疑问

如下代码是否能够正确执行

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray *array = [NSMutableArray array];
        Block block = ^{
            [array addObject: @"20"];
            [array addObject: @"30"];
            NSLog(@"%@",array);
        };
        block();
    }
    return 0;
}

复制代码

答:能够正确执行,由于在block块中仅仅是使用了array的内存地址,往内存地址中添加内容,并无修改arry的内存地址,所以array不须要使用__block修饰也能够正确编译。

所以当仅仅是使用局部变量的内存地址,而不是修改的时候,尽可能不要添加__block,经过上述分析咱们知道一旦添加了__block修饰符,系统会自动建立相应的结构体,占用没必要要的内存空间。

相关文章
相关标签/搜索