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 的一些简单认识_第1张图片
不对 block 赋值,直接调用

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

2.循环引用

Block 的一些简单认识_第2张图片
循环引用

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

怎么知道发生了循环引用

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


debug memory

点击 Debug Memory 那个选项


Block 的一些简单认识_第3张图片
内存的图

本来这个 pool 结束的时候,user 应该被释放掉,但是因为在 block 内部强引用了 user,造成了循环引用。
如果我们去掉 block 内部的那些代码
Block 的一些简单认识_第4张图片
没有形成循环引用

如果没有了 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的内部使用弱引用,这样的话,就能打破这个环。内存图就像下面这个样子


Block 的一些简单认识_第5张图片
解决循环引用

参考内容

一篇文章看懂iOS代码块Block

你可能感兴趣的:(Block 的一些简单认识)