美文网首页
__block关键字

__block关键字

作者: lth123 | 来源:发表于2021-03-28 16:44 被阅读0次

一.Block内部为什么不能修改auto变量

在block内部修改auto变量时,编译器会报错,Variable is not assignable (missing __block type specifier,代码如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int a = 10;
        void(^block)(void) = ^{
          // 编译报错Variable is not assignable (missing __block type specifier
            a = 20;
        };
    }
    return 0;
}

为什么在block内部无法修改变量a的值呢,通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m命令,我们看下这段代码的c++实现

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

// block的实现在__main_block_func_0函数中
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_2f53be_mi_0,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, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    // 变量a定义在 main函数中
        int a = 10;
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    }
    return 0;
}

通过block c++实现,我们可以看到,变量a是定义在 main函数中的,并且在初始化block变量时,传入的是变量啊的值10,并不是a的地址。所以在block中保存的是10的值。 block的实现在__main_block_func_0函数中,int a = __cself->a 只是取出block中的值,只能读写block中的变量啊,是无法修改main函数值定义的auto 变量a的值。从作用域的角度来说,__main_block_func_0函数也无法修改main函数中定义的auto变量


二.Block内部修改变量的几种方式

  1. static修饰的局部变量
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *a; // 保存静态变量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;
  }
};

// block实现,修改main函数中定义的变量a
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  //通过block取出变量a的地址,赋值给指针a
  int *a = __cself->a; // bound by copy
  // 把指针a指向的内存单元的的存储值设置为20
  (*a) = 20;
}

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, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        // 定义static变量
        static int a = 10;
        // 传入变量a的地址&a,block内部保存的是main函数中的a的地址,所以在block中是可以访问到main函数中的变量a
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
    }
    return 0;
}

可以看到,block中保存的是a的地址,所以可以在__main_block_func_0取出block中a的地址,进而修改局部变量a的值

  1. 全局变量
    全局变量的作用域是全局的,谁都可以访问,所以在任何地方都能修改全局变量的值
  1. __block修饰auto变量
  • __block无法修饰全局变量和静态变量
  • 编译器会将__block修饰的变量包装成一个对象
// block oc实现
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int a = 10;
        __block Book *book = [[Book alloc] init];
        __block __weak Book *weakBook = book;
        void(^block)(void) = ^{
            a = 20;
            NSLog(@"%d--%@---%@",a,book,weakBook);
        };
         NSLog(@"%d",a);
         NSLog(@"%p---%p",&a,block);
    }
    return 0;
}

// block c++实现
// __block修饰的 int a
struct __Block_byref_a_0 {
  void *__isa; // 说明__Block_byref_a_0是一个对象
__Block_byref_a_0 *__forwarding; //  指向__Block_byref_a_0类型的指针
 int __flags;
 int __size; // 记录了结构体的大小
 int a; //保存变量a的值
};


// __block修饰的 Book book
struct __Block_byref_book_1 {
  void *__isa; // 说明__Block_byref_book_1是一个对象
__Block_byref_book_1 *__forwarding; // 指向__Block_byref_book_1类型的指针
 int __flags;
 int __size; // 记录了结构体的大小
 void (*__Block_byref_id_object_copy)(void*, void*); //指向 __Block_byref_id_object_copy_131函数
 void (*__Block_byref_id_object_dispose)(void*); // 指向 __Block_byref_id_object_dispose_131函数
 Book *__strong book; // 1.指向block外面的book对象 2.__strong对应外面的强指针
};

// __block修饰的 weakBook
struct __Block_byref_weakBook_2 {
  void *__isa; // 说明__Block_byref_weakBook_2是一个对象
__Block_byref_weakBook_2 *__forwarding; // 指向__Block_byref_weakBook_2类型的指针,
 int __flags;
 int __size; // 记录了结构体的大小
 void (*__Block_byref_id_object_copy)(void*, void*); //指向 __Block_byref_id_object_copy_131函数
 void (*__Block_byref_id_object_dispose)(void*);  // 指向 __Block_byref_id_object_dispose_131函数
 Book *__weak weakBook; // 1.指向block外面的book对象 2.__weak对应外面的弱指针
};

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

// block结构体
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref 指向结构体a->__forwarding
  __Block_byref_book_1 *book; // by ref 指向结构体_book->__forwarding
  __Block_byref_weakBook_2 *weakBook; // by ref 指向结构体_weakBook->__forwarding
    // block初始化构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_book_1 *_book, __Block_byref_weakBook_2 *_weakBook, int flags=0) : a(_a->__forwarding), book(_book->__forwarding), weakBook(_weakBook->__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) {
    // 通过block对象取出结构体a
  __Block_byref_a_0 *a = __cself->a; // bound by ref
  __Block_byref_book_1 *book = __cself->book; // bound by ref
  __Block_byref_weakBook_2 *weakBook = __cself->weakBook; // bound by ref
    
    //通过__forwarding,拿到结构体内的成员变量a,修改成员变量a的值,这里修改的是结构体中的成员变量a的值
    (a->__forwarding->a) = 20;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_769491_mi_0,(a->__forwarding->a),(book->__forwarding->book),(weakBook->__forwarding->weakBook));
}

// 对 a book weakBook进行强引用或者弱引用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->book, (void*)src->book, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->weakBook, (void*)src->weakBook, 8/*BLOCK_FIELD_IS_BYREF*/);
}、
// 对 a book weakBook进行内存管理
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->book, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->weakBook, 8/*BLOCK_FIELD_IS_BYREF*/);
}


// 描述block信息的结构体
static struct __main_block_desc_0 {
  size_t reserved; // 保留字段
  size_t Block_size; // block结构体的size
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); //copy 指向 __main_block_copy_0函数的指针
  void (*dispose)(struct __main_block_impl_0*); // dispose 指向 __main_block_dispose_0函数的指针
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};


// main 函数
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        
        // 定义了__Block_byref_a_0类型的结构体 a
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
            (void*)0,                 // isa
            (__Block_byref_a_0 *)&a, // 将结构体a的地址赋值给__forwarding
            0,                        // flags
            sizeof(__Block_byref_a_0),// 传入结构体a的大小
            10                        //保存10
            
          };
        
        //定义了__Block_byref_book_1类型的结构体 book
        __attribute__((__blocks__(byref))) __Block_byref_book_1 book = {
                (void*)0,                      //isa
                (__Block_byref_book_1 *)&book, // 将结构体book的地址赋值给__forwarding
                33554432,                      //flags
                sizeof(__Block_byref_book_1),  //传入结构体__Block_byref_book_1的大小
                __Block_byref_id_object_copy_131, // copy函数
                __Block_byref_id_object_dispose_131, // dispose函数
                ((Book *(*)(id, SEL))(void *)objc_msgSend)((id)((Book *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Book"), sel_registerName("alloc")), sel_registerName("init"))}; // 初始化Book对象,赋值给book指针
        
        //定义了__Block_byref_weakBook_2类型的结构体 book
        __attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_weakBook_2 weakBook = {
            (void*)0,                                   //isa
            (__Block_byref_weakBook_2 *)&weakBook,      //weakBook结构体的地址赋值给__forwarding
            33554432,                                   //flags
            sizeof(__Block_byref_weakBook_2),           //传入结构体__Block_byref_weakBook_2的大小
            __Block_byref_id_object_copy_131,           // copy函数
            __Block_byref_id_object_dispose_131,        // dispose函数
            (book.__forwarding->book)                   // 取出book内部__forwarding指向的对象,赋值给weakBook指针
            
        };
        
        
        // 定义block,传入的是结构体 a  book weakBook 的地址
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_book_1 *)&book, (__Block_byref_weakBook_2 *)&weakBook, 570425344));
    }
    return 0;
}

1.通过NSLog我们可以看到a的值确实被修改成了20,那么为什么加上__block之后就可以修改a的值呢?

  • __block修饰的变量a会包装成一个__Block_byref_a_0类型的结构体a,并且将结构体a的地址保存到block对象中。
  • __Block_byref_a_0结构体有一个成员变量a,保存a的值10;有一个__forwarding指针指向__Block_byref_a_0类型的结构体。
  • 在block代码块中,通过block拿到__Block_byref_a_0类型的结构体a,通过a->__forwarding->a拿到结构体的成员变量a,将20保存到成员变量a中,此时__Block_byref_a_0结构体里面保存的值已经被修改成为了20。
  • 在外面使用变量a的值,都是使用__Block_byref_a_0结构体中保存的a的值。

证明block外面使用的变量a是__Block_byref_a_0内部的成员变量a

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

struct __Block_byref_a_0 {
 void *__isa; //  0x1030776c0  8
 struct __Block_byref_a_0 *__forwarding;// 8
 int __flags; // 4
 int __size; //4
 int a;  //0x1030776d8
};

 struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} ;

struct __main_block_impl_0 {
  struct __block_impl impl; //0x10307e8a0
  struct __main_block_desc_0* Desc;
  struct __Block_byref_a_0 *a; // by ref 0x10307e8d0

};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int a = 10;
        void(^block)(void) = ^{
            a = 20;
        };
        block();
        // 强制类型转换,将block转换成结构体block1
        struct __main_block_impl_0 *block1 = (__bridge struct __main_block_impl_0 *)block;
        NSLog(@"%d",a);
        // 打印变量a的地址 __Block_byref_a_0结构体a的地址
        NSLog(@"%p---%p---%p",&a,block1->a,block1);
        
    }
    return 0;
}

打印a的地址:0x1030776d8,__Block_byref_a_0的地址0x1030776c0,内存相差24个字节。我们知道结构体的地址就是结构体第一个成员变量的地址,所以__isa的地址是0x1030776c0,指针变量占8个字节,int类型的变量占4个字节,可以计算出成员变量a的地址是 0x1030776d8。也就是说block外面使用的a的地址与__Block_byref_a_0成员变量a的地址相同。经过__block修饰的变量,以后在使用的时候,都是使用__Block_byref_a_0内部的成员变量。

三. __forwarding指针的作用

在上面的源码中,访问a的值都是通过(a->__forwarding->a)访问,为什么要不直接使用a->a来访问,而要通过__forwarding指针间接访问呢?

在前面的文章中提过,在ARC环境下,被强指针引用的栈block,编译器会自动将栈block拷贝到堆中。并且block中的用到的__block变量也会被一起拷贝到堆中,并且被堆上的Block持有。即使block代码块结束,由于堆上的block被强指针引用,所以堆上的block不会被销毁,__block变量也不会销毁。示意图如下:


block中使用__block修饰的变量.png

当编译器将__block变量结构体实例在从栈上被拷贝到堆上时,会将成员变量的__forwarding的值替换为复制目标堆上的__block变量结构体实例的地址,堆中的__forwarding指向自己。通过__forwarding,如果访问的是栈block,会通过__forwarding找到堆中的block,堆block的__forwarding指向自己,从而找到堆中的__block变量a;如果访问的是堆中的block,通过__forwarding指针,也是找到堆中的__block变量a。示意图如下:

复制__block变量后__forwarding指针的变化.pn.png
总结:__forwarding则保证了无论在栈上还是堆上访问的都是同一个__block变量

通过对内存地址的分析,我们也可以__main_block_impl_0 里面的指针是 __Block_byref_a_0里面的__forwarding指针;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int a = 10;
        void(^block)(void) = ^{
            a = 20;
        };
        block();
        // 强制类型转换,将block转换成结构体block1
        struct __main_block_impl_0 *block1 = (__bridge struct __main_block_impl_0 *)block;
        NSLog(@"%d",a);
        // 打印变量a的地址 __Block_byref_a_0结构体a的地址
        NSLog(@"%p---%p---%p",&a,block1->a,block1);
        
    }
    return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl; //0x100438440     24
  struct __main_block_desc_0* Desc; //0x100438440+24 = 0x100438458   8
  struct __Block_byref_a_0 *a; // 0x100438458 + 8 = 0x100438460
};

struct __Block_byref_a_0 {
  void *__isa; //   8
 struct __Block_byref_a_0 *__forwarding;// 8
 int __flags; // 4
 int __size; //4
 int a;  
};

block1的地址是0x100438440,__Block_byref_a_0 a的地址是0x100438470。从上面代码的计算过程可以看到__Block_byref_a_0 *a 的地址是0x100438460,并不是0x100438470,而加上__Block_byref_a_0 *a 和void *__isa的16个字节,刚好是0x100438470。

下面是变量val在内存中的变化

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int val = 10;
        NSLog(@"val初始化地址-%p", &val);
        void(^block)(void) = ^{
            val = 20;
            NSLog(@"val block地址-%p", &val);
        };
        block();
        NSLog(@"block之后val的地址-%p", &val);
        NSLog(@"block---%@",block);

    }
    return 0;
}

val初始化地址-0x7ffeefbff4b8 --------------------- 栈
val block地址-0x1005186a8 ---------------------- 堆
block之后val的地址-0x1005186a8 -------------------- 堆
block---<NSMallocBlock: 0x100518660> ----------堆

可以看到val变量在被block捕获之后,从栈上到了堆上。这段代码的c++实现(省去部分实现)如下:

struct __Block_byref_val_0 {
      void *__isa;
    __Block_byref_val_0 *__forwarding; //val在被copy到堆中时,栈上的__forwarding不再指向自己,而是指向堆中的val
     int __flags;
     int __size;
     int val; //
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    //__cself->val,cself在堆上,取出堆中的val成员变量
  __Block_byref_val_0 *val = __cself->val; // bound by ref
  
  //堆中的__forwarding指针指向自己,val->__forwarding->val取出的是堆中的成员变量val,将堆中的val设置为20
  (val->__forwarding->val) = 20;
    
  // Log2.val->__forwarding->val 取出的是堆中的val 20
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_1510ef_mi_1, &(val->__forwarding->val));
}

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

        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
        //Log1. val->__forwarding->val 在栈上
        /**val 结构体在栈上 ,__forwarding 指向栈上的val,所以val->__forwarding->val */
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_1510ef_mi_0, &(val.__forwarding->val));
        
        /* 如果栈block被强指针引用,会被copy到堆上,在block被copy到堆上的同时,
           会将捕获的变量也同时copy到堆上,所以此时val这个结构体在堆上也有一份
         */
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
        
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        
       //Log3.val->__forwarding->val  堆中
        /*
          1.val 在栈中
          2.__forwarding指向的是堆中的val结构体
          3.val.__forwarding->val取出的是堆中的val 20
         */
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_1510ef_mi_2, &(val.__forwarding->val));
        
        
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_1510ef_mi_3,block);

    }
    return 0;
}

// 在main.m中重写__main_block_func_0函数
void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 
    NSObject *obj = [[NSObject alloc] init]; // 0x100510000  堆中
    NSLog(@"obj的对象地址:%p",obj);
    NSLog(@"__cself指向的对象:%p",__cself);
    NSLog(@"__cself参数的地址:%p",&__cself);
}

2021-04-02 14:16:43.134327+0800 Block1[13163:192597] obj的对象地址:0x1004962c0
2021-04-02 14:16:43.134424+0800 Block1[13163:192597] __cself的对象地址:0x100496230
2021-04-02 14:16:43.134486+0800 Block1[13163:192597] __cself的对象地址:0x7ffeefbff488

可以看到__cself指向堆中的block,修改的是堆中block中的值
image.png

如果栈block被强指针引用,会被copy到堆上,在block被copy到堆上的同时,会将val也同时copy到堆上,所以此时val这个结构体在堆上也有一份
val在被copy到堆中时,栈上的__forwarding不再指向自己,而是指向堆中的val
__forwarding 被设计出来就是为了解决这个问题,如上图所示,栈区的 __forwarding 指向了复制到堆后的 val ,而堆上val指向自己,这也就保证了不论是访问栈还是堆上的val都能获取到正确的值。

相关文章

  • ios的block原理

    block:代码块,函数指针和指针 block:使用copy关键字 堆block:@propetry 栈block...

  • 再论block,以及weak和block关键字

    前几天去阿里面试,谈到block,我说加上__block关键字可以满足block内修改block外变量的需求,加上...

  • 关于Block

    关键字 block一般使用copy关键字进行修饰,block使用copy是从MRC遗留下来的“传统”,在MRC中,...

  • Block由浅入深(4):Block修改局部变量

    Block可以修改的变量 我们可能都知道,不使用__block关键字,我们不能在Block内修改变量的值。但是严格...

  • 第二节 block知识学习(copy,__weak,__bloc

    Dear All 这节我们来学习block知识 ,废话不说 、让我们直奔主题 __block关键字的作用 (基本数...

  • __block 关键字

    简介我们可以把Block当做Objective-C的匿名函数。Block允许开发者在两个对象之间将任意的语句当做数...

  • __block关键字

    前提:Objective-C规定,在block中不能修改外部变量的值,若想修改则需在变量前边加__block关键字...

  • __block关键字

    一.Block内部为什么不能修改auto变量 在block内部修改auto变量时,编译器会报错,Variable ...

  • __block关键字

    前提:Objective-C规定,在block中不能修改外部变量的值,若想修改则需在变量前边加__block关键字...

  • Block - __block关键字的底层实现原理

    参考文档 iOS中__block 关键字的底层实现原理 你真的理解__block修饰符的原理么? iOS Bloc...

网友评论

      本文标题:__block关键字

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