美文网首页iOS底层原理整理
iOS Block(3)-__block修饰变量

iOS Block(3)-__block修饰变量

作者: 周灬 | 来源:发表于2019-07-18 09:52 被阅读0次

    在block函数体里面修改变量在日常开发中常见,我们可以轻松在block体内部修改static变量或全局变量,但是却无法修改auto变量。尝试在block中修改auto变量,编译器错误如下:

    block内部修改变量值.png

    有三种解决方案:
    ①. 将需要修改的变量设置为全局的;
    ②. 将需要修改的变量设置为static类型;
    ③. 在需要修改的变量前加上“__block”修饰符。
    针对方案①,全局变量定义太多影响代码阅读,全局变量生命周期长,占内存;
    针对方案②,static类型变量的生命周期同APP一样长,一直存在于内存中,所以舍弃。
    方案③是完美方案.

    来看一个简单的demo:

    __block修改变量.png

    OC转换为C++代码:

    void(*block)(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;
                NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_3fd458_mi_0,(age->__forwarding->age));
            }
    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[]) {
        /* @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 = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
            return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
        }
    }
    

    __Block_byref_age_0结构体,根据其内部定义:

    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;
     int __flags;
     int __size;
     int age;
    };
    

    内部包含isa指针,毫无疑问是一个OC对象;
    成员__forwarding指向结构体本身;
    成员__size保存了结构体__Block_byref_age_0本身的内存大小;
    居然有个成员int age,并且由之前老巢__main_block_impl_0迁移到此;
    再看看__main_block_impl_0结构体:

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

    内部也有一个age成员,不过是结构体__Block_byref_age_0类型指针,所以block内部访问的auto变量就是该结构体age指针指向的内容。经过“__block”修饰之后,基本类型的“age”变量被包装成“__Block_byref_age_0”结构体对象。
    在main()函数中,我们看到"__block int age = 10;"被定义成:

     __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_byref_age_0"类型的“age”传入到block的构造函数__main_block_impl_0中,因此__main_block_impl_0中的age被赋值这样的结构体:

     __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,
                (__Block_byref_age_0 *)&age,
                0,
                sizeof(__Block_byref_age_0),
                10};
    

    即:__Block_byref_age_0中最后一个成员“int age;”被赋值10,__forwarding被赋予__Block_byref_age_0age的地址,这里就是结构体本身,与前面讲述的一致。

    再看看block内部的代码块:

    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;
                NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_3fd458_mi_0,(age->__forwarding->age));
            }
    

    OC代码age = 20;对应的C++代码(age->__forwarding->age) = 20;,C++代码中有两个不同类型的age变量,前一个age是__Block_byref_age_0类型的结构体指针,后一个age是__Block_byref_age_0结构体中的int型age变量。中间的__forwarding指向的还是__Block_byref_age_0类型的结构体指针age,即第一个age,具体为什么这么设计,后文会讲到。
    兜了这么一圈,终于明白了,原来"__block"将局部变量包装成一个__Block_byref_age_0结构体对象,结构体中有与局部变量同名同类型的变量。在block体内修改"__block"变量,通过一系列指针指向关系,最终指向了__Block_byref_age_0结构体内与局部变量同名同类型的那个成员,并成功修改变量值。

                                想了解更多iOS学习知识请联系:QQ(814299221)

    相关文章

      网友评论

        本文标题:iOS Block(3)-__block修饰变量

        本文链接:https://www.haomeiwen.com/subject/iwlklctx.html