Block 的一些简单认识

Block 与局部变量

 int global = 100;
 void(^myBlock)(void) = ^{
     NSLog(@"global = %d",global);
 };
 global = 101;
 myBlock();

block 可以捕获局部变量,在你声明 myBlock 后,因为需要在block 内使用了 global 变量,所以 block 为你捕获了这个变量。

局部变量的使用

1.如果使用 global 变量

在使用 clang -rewrite-objc main.m 后查看.cpp 文件可以看到

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int global; //block 捕获了这个变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _global, int flags=0) : global(_global) {
     impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

2.如果不使用 global 变量

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};//没有捕获 global

局部变量的修改

值类型的变量

直接修改报错,提示缺少了__block 的修饰符,那么加了__block 修饰符后有什么不一样呢。同样的我们还是查看.cpp 文件来比较

使用__block 不使用
__Block_byref_global_0 *global; // by ref int global;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_global_0 *_global, int flags=0) : global(_global->__forwarding) __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _global, int flags=0) : global(_global)

可以看到 global 由一个值类型变成了一个指针类型的变量,所以使用了__block 后可以修改 global 的值。

局部变量的修改结果

1.使用__block 修饰

 __block int global = 100;
 void(^myBlock)(void) = ^{
      NSLog(@"global = %d",global);
  };
  global = 101;
  myBlock();
  //运行结果
  global = 101

2.不使用__block 修饰

 int global = 100;
 void(^myBlock)(void) = ^{
      NSLog(@"global = %d",global);
  };
  global = 101;
  myBlock();
  //运行结果
  global = 100

修改结果总结

  1. 如果使用__block 修饰,并且在调用 block 之前修改 global ,那么block 内部的 global 也会跟着变。
  2. 如果不使用__block的话,并且在调用 block 之前修改 global,那么 block 内部的 global 不会发生改变。

原因

  1. 使用__block 修饰
 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_global_0 *global = __cself->global; // bound by ref

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_bq_bv7y3wj94sg1_5ldvtywqy7c0000gn_T_main_b116b4_mi_0,(global->__forwarding->global));
 }

2.不使用__block修饰

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

使用__block 修饰:
__Block_byref_global_0 *global = __cself->global; // bound by ref
不使用__block 修饰:
int global = __cself->global; // bound by copy
划重点,因为一个是bound by ref 一个是bound by copy。一个是传递了指针,一个是对当前捕获的值的拷贝

Block 与 全局变量

在外层定义一个全部变量,执行下面的代码

   void(^myBlock)(void) = ^{
        NSLog(@"global = %d",global);
    };
    global = 101;
    myBlock();
     //运行结果
    global = 101

为什么不使用__block 也可以修改,我们打开.cpp 进行查看

int global = 100;//先定义了一个全局的变量

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

在block 结构体的上方,已经定义了一个全局的变量,所以直接使用就可以了。不需要像局部变量一样,而且它并没有被 block 所捕获。

Block 与 静态变量

定义一个静态变量,执行下面的代码

   static int global = 100;

    void(^myBlock)(void) = ^{
        NSLog(@"global = %d",global);
    };
    global = 101;
    myBlock();

     //运行结果
     global = 101

为什么不使用__block 也可以修改,我们打开.cpp 进行查看

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *global;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_global, int flags=0) : global(_global) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

在 global 进行赋值的时候是这样的

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int *global = __cself->global; // bound by copy

     NSLog((NSString *)&__NSConstantStringImpl__var_folders_bq_bv7y3wj94sg1_5ldvtywqy7c0000gn_T_main_d41b2e_mi_0,(*global));
 }

它和局部变量不使用__block 修饰看起来很像,所以做了下对比

静态全局变量:
int *global = __cself->global; // bound by copy
不使用__block 修饰的局部变量:
int global = __cself->global; // bound by copy
划重点,虽然都是 copy,但是静态全局变量 copy 了指针,而不使用__block修改的局部变量进行了值的 copy。

ARC 下的 Block

1.EXC_BAD_ACCESS (code=1, address=0x10)

我有一个 User类

user.h

typedef void(^nameBlock)(void);
@interface User : NSObject
@property (nonatomic,strong)NSString *name;
@property (nonatomic,copy)nameBlock block;
-(instancetype)initWithName:(NSString *)name;

@end

user.m

import "User.h"

@implementation User
-(instancetype)initWithName:(NSString *)name{
    self = [super init];
    if (self) {
         _name = name;
    }
    return self;
}
@end

如果不赋值直接调用user.block();那么直接就会报错EXC_BAD_ACCESS (code=1, address=0x10),在使用时要注意


不对 block 赋值,直接调用

所以在调用 block 之前需要先赋值然后调用,这样就能解决这个问题

2.循环引用

循环引用

user有一个属性是一个 block,并且在内部又调用了 user,造成了循环引用。在编写代码的时候,编译器就给出了上面的黄色警告。

怎么知道发生了循环引用

xcode 9 的话,我们可以打断点看,我们在 autoreleasepool 结束的时候打断点


debug memory

点击 Debug Memory 那个选项


内存的图

本来这个 pool 结束的时候,user 应该被释放掉,但是因为在 block 内部强引用了 user,造成了循环引用。
如果我们去掉 block 内部的那些代码
没有形成循环引用

如果没有了 block 内部对 user 的强引用,那么便不会发生这种情况

如果我们一定要在 block 内部调用 user

修改我们的代码

    User *user = [[User alloc]initWithName:@"Likee"];
    __weak typeof(user)weakUser = user;
    user.block = ^{
        weakUser.name = @"cat";
        NSLog(@"name is %@",weakUser.name);
    };
    NSLog(@"name is %@",user.name);
    user.block();

在 block的内部使用弱引用,这样的话,就能打破这个环。内存图就像下面这个样子


解决循环引用

参考内容

一篇文章看懂iOS代码块Block

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容