笔记:iOS Block的基本使用

作者: 子斌 | 来源:发表于2017-02-06 12:36 被阅读192次

    目录

    1. Block概述
    2. Block定义方式
    3. Block保存代码
    4. Block传值
    5. Block对外部变量的传递
    6. Block做参数
    7. Block做返回值(实现链式编程)
    8. Block内存管理
    9. Block循环引用
    10. Block其他注意点

    1. Block概述

    Block是C语言级别和运行时方面的一个特征。Block封装了一段代码逻辑,用{}括起,和标准C语言中的函数/函数指针很相似。

    2. Block定义方式

    • 声明:(返回类型)(^声明的Block名称)(参数列表);
    • 实现:^(返回类型)(参数列表){代码块}
    • 返回类型(^block变量名)(参数列表) = ^(形参列表) {};
      void(^MyBlock)(int a,int b) = ^(int a, int b){}
    返回类型:void
    Block名字:MyBlock
    实际参数: int a ,int b
    形式参数: int a ,int b
    
    • Block三种定义方式以及block类型
    1. 没有返回值,没有参数的定义方式
    //返回值类型(^block的名字)(参数类型) = ^(参数类型和参数名) {};
      void(^block)() = ^(){
          NSLog(@"调用了block");
      };
    //当然,没有参数的时候可以把括号省去
      void(^block)() = ^{
          NSLog(@"调用了block");
      };
    
    1. 有返回值,有参数的定义方式
    //返回值类型(^block的名字)(参数类型) = ^(参数类型和参数名) {};
    //如果有参数,定义的时候,必须要写参数,而且必须要有参数变量名
      int(^block)(int) = ^(int a){
          return 1;
      };
    
    1. 定义时带有返回类型的(不常用)
      int(^block)() = ^int{//这里的int就是这个block的返回值类型
          return 1;
      };
    
    1. 系统提供了一个定义block的宏
    // block快捷方式   输入:inline
          returnType(^blockName)(parameterTypes) = ^(parameters) {       
                      statements
      };
    
    1. block的调用
    //定义block
      void(^block)() = ^{
          NSLog(@"调用了block");
      };
    //调用block
      block();
    
    1. block的类型
    //block有自己的类型,就想@"string"是NSString类型一样
    //格式就是 返回值(^)(参数类型)
    //比如这个block的类型就是: int(^)(int)
      int(^block)(int) = ^(int a){
          return 1;
      };
    //这个block的类型就是void(^)()
      void(^block)() = ^{
          NSLog(@"调用了block");
      }; 
    //在ARC中把block定义成属性要用strong类型,定义方式如下:
    @property (nonatomic, strong) void(^block)();//这样在类中可以拿到self.block
    //当然也可以取别名:
    typedef void(^BlockType)();//BlockType不是变量名,而是这种类型的block的别名
    //然后就可以这样
    @property (nonatomic, strong) BlockType block;
    

    3. Block保存代码

    这种方式在app的设置界面中使用到,需求大约是这样:
    设置界面每一行cell会做不同的事,有的是跳转界面,有的是switch开关,有的是需要显示一下AlertView.
    这样的话,我们可以把需要执行的代码包装成block,放在cell的模型里面,当点击cell的时候,拿出模型中的block来执行。

    //这是cell的模型
    typedef void(^optionBlock)();
    @interface SettingItem : NSObject
    @property(nonatomic,copy)NSString *icon;//图标
    @property(nonatomic,copy)NSString *title;//文字
    @property(nonatomic,assign) Class destVc;//需要跳转的界面
    @property(nonatomic,copy) optionBlock option;//需要执行的代码
    @end
    

    然后我们可以在didSelectRowAtIndexPath里面这么做:

    -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
      [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
      if (item.option != nil) {//block存在就执行
          item.option();
      }else if ([item isKindOfClass:[SettingArrowItem class]]) {//执行跳转界面
          SettingArrowItem *newItem = (SettingArrowItem *)item;
          UIViewController *vc = [[newItem.destVc alloc]init];
          vc.title = item.title;
          [self.navigationController pushViewController:vc animated:YES];
      }
    }
    

    4. Block进行传值

    需求如下:
    在ViewControllerOne跳到ViewControllerTwo
    ViewControllerTwo拿到数据后dismiss
    在ViewControllerOne打印数据

    //ViewControllerOne.m:
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{//点击控制器View的时候调用
      ModalViewController *modalVc = [[ModalViewController alloc] init];
      //对Block属性进行赋值
      modalVc.block = ^(NSString *value) {
            NSLog(@"%@",value);
      };
      [self presentViewController:modalVc animated:YES completion:nil];
    }
    //ViewControllerTwo.h:
    @interface ModalViewController : UIViewController
    @property (nonatomic, strong) void(^block)(NSString *value);
    @end
    //ViewControllerTwo.m:
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
      // 调用Block 传值给ViewControllerOne
      if (_block) {
          _block(@"123");
      }
      [self dismissViewControllerAnimated:YES completion:nil];
    }
    

    5. Block对外部变量的传递

    一个很简单的问题:block使用外部变量,是值传递,还是指针传递。

    1. 值传递
    //block为值传递只有一种情况:
      int a = 3;
      void(^block)() = ^{
          NSLog(@"%d",a);
      };
      a = 5;
      block();//这里调用block打印出的是3,是值传递
    *注意:因为block中使用的外面变量的值是拷贝过来的即值拷贝,所以在调用之前修改外界变量的值,不会影响到block中拷贝的值.
    

    结论:如果block访问的变量是局部变量,那么变量是值传递.

    1. 指针传递
    static int a = 3;
      void(^block)() = ^{
          NSLog(@"%d",a);
      };
      a = 5;
      block();//这里调用block打印出的是5,是指针传递
    //另外全局变量,静态变量,__block修饰的变量都是指针传递
    

    结论:如果是全局变量或者静态变量,那么变量是指针传递。

    1. 经过其他测试总结:
      (1)如果是局部变量,Block是值传递
      (2)如果是静态变量,全局变量,__block修饰的变量,block都是指针传递

    6. Block做参数

    第一次见到block当做参数是在AFN框架中,AFN帮你拿到数据以后,执行你传给他的block:

    //这一个个对AFN进行简单封装的方法:
    +(void)requestWihtMethod:(RequestMethodType)methodType
                     success:(void (^)(id response))success  //是一个block
                     failure:(void (^)(NSError* err))failure //是一个block
    {
         AFHTTPSessionManager *manage = [AFHTTPSessionManager manager];
         [manage GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
              } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                  if (success) {
                      success(responseObject);//拿到数据后执行你传入的block
                  }
              } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                      failure(error);//拿到数据后执行你传入的block
              }];
          }
    }
    

    接下来我们自定义一个计算器,在block里面自定义计算方式,将block传入计算器来进行计算:

    //CalculatorManager.h:
    @interface CacultorManager : NSObject
    @property (nonatomic, assign) NSInteger result;//要计算的数据
    -(void)calculatorWithMethod:(NSInteger(^)(NSInteger // 计算方法parameterresult))methodBlock;
    @end
    //CalculatorManager.m:
    -(void)cacultor:(NSInteger (^)())cacultorBlock
    {
      if (cacultorBlock) {//判断block是否为空
        _result =  cacultorBlock(_result);//执行block中的计算方法
      }
    }
    //ViewController.m:使用计算器
    -(void)viewDidLoad {
      [super viewDidLoad];
      // 创建计算器管理者
      CalculatorManager *mgr = [[CalculatorManager alloc] init];
      [mgr calculator:^(NSInteger result){//自定义计算方法block,作为参数传进去
          result += 5;
          result += 6;
          result *= 2;
          return result;
      }];
      NSLog(@"%ld",mgr.result);
    

    7. Block做方法返回值(链式编程)

    我们平时写一些工具类的方法的时候(比如计算器)

    //注:result是CalculatorManager的属性,用来保存计算结果
    //如果计算方法这么写
    -(int)add:(int)value{
      _result += value;
      return result;
    }
    //就要这么调用:
    CalculatorManager *mgr = [[CalculatorManager alloc] init];
    [mgr add:[mgr add:[mgr add:[mgr add:[mgr add:[mgr add:5]]]]]];//返回值是数字,要继续用mar调用add来执行操作
    //如果计算方法这么写:
    -(CalculatorManager *)add:(int)value
    {
      _result += value;
      return self;
    }
    //就要这么调用:
    CalculatorManager *mgr = [[CalculatorManager alloc] init];
    [[[[[mgr add:5] add:5] add:5] add:6] add:7];//返回值是计算器本身,可以继续调用add方法
    

    现在我们要用返回值是block的方法来实现链式编程:

    //计算方法这么写,返回值是 返回值为CalculatorManager的block.
    -(CalculatorManager *(^)(int))add//相当于一个get方法
    {
      return ^(int value){
          _result += value;
          return self;
      };
    }
    //就可以这么调用
      CalculatorManager *mgr = [[CalculatorManager alloc] init];
      mgr.add(5).add(5).add(5).add(5);
    

    下面简单介绍一下调用原理:
    mgr.add相当于get方法的调用: [mgr add];
    mgr.add返回的是一个block,所以你可以给他一个参数5,于是写成这样:mgr.add(5)
    然后block返回的又是CalculatorManager,所以继续调用add
    如此循环下去.......

    8. Block内存管理

    首先,在oc中block是一个对象,只有对象才涉及到内存管理
    block的内存管理在MRC和ARC中有不同的地方,接下来将分别介绍

    1.MRC:
    Block存放位置:

      int a = 3;//这是一个局部变量
      void(^block)() = ^{
          NSLog(@"调用block%d",a);
      };
      NSLog(@"%@",block);
    //打印结果:<__NSStackBlock__: 0x7fc498746000>
    //此时引用了外部局部变量,block放在栈里面
    
    static int b = 2;
    -(void)viewDidLoad {
      [super viewDidLoad];
      void(^block)() = ^{
          NSLog(@"调用block%d",b);
      };
      NSLog(@"%@",block);
    }
    //打印结果:<__NSGloBalBlock__: 0x7fc498746000>
    //此时引用了全局变量,block放在全局区
    
    定义属性时:
    @property (nonatomic, copy) void(^block)();
    block只能使用copy,不能使用retain,
    因为使用retain,block还是在栈里面 代码块过了方block就销毁了,再次访问self.block会出现坏内存访问
    使用copy是放在堆里面,代码块过了不会销毁
    

    MRC管理Block总结:
    只要Block没有引用外部局部变量,Block放在全局区
    只要Block引用外部局部变量,Block放在栈里面.

    2.ARC:
    Block存放位置:

      int a = 3;//这还是一个局部变量
      void(^block)() = ^{
          NSLog(@"调用block%d",a);
      };
      NSLog(@"%@",block);
    //打印结果:<__NSMallocBlock__: 0x7fc498746000>
    //此时引用了外部局部变量,block放在堆里面
    
    static int b = 2;
    -(void)viewDidLoad {
      [super viewDidLoad];
      void(^block)() = ^{
          NSLog(@"调用block%d",b);
      };
      NSLog(@"%@",block);
    }
    //打印结果:<__NSGloBalBlock__: 0x7fc498746000>
    //此时引用了全局变量,block放在全局区
    
    定义属性时:
    @property (nonatomic, strong) void(^block)();
    block只能使用strong,不要使用copy
    因为当使用copy的时候,set方法是调用了copy帮你深拷贝一次,没有这个必要.
    就像NSString一样,他一般都是@"a"这种常量,没必要再去深拷贝一次,所以NSString常量也用strong不用copy.
    

    ARC管理Block总结:
    只要Block没有引用外部局部变量,Block放在全局区
    只要Block引用外部局部变量,Block放在堆里面.
    block循环引用

    9. Block的循环引用问题

    1. 为什么会产生循环引用:
      因为block会给内部的强指针对象进行一次强引用,比如常见的传入block中的self进行强引用
      并且在self中,block又是strong的,self对block是强引用
      所以,你强引用我,我强引用你,谁也不会被释放,就造成了循环引用
      所以,为了避免循环引用,我们要在block使用self之前,进行这一步操作:
    __weak typeof(self) weakSelf = self;
    

    在block中使用weakSelf,就不会产生循环引用问题了.
    使用了__weak修饰符的对象,作用等同于定义为weak的property。自然不会导致循环引用问题.

    1. 接下来是双层block的循环引用问题:
      先来看这样一个例子:
      __weak typeof(self) weakSelf = self;
      block = ^{
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//延时2秒执行下面的代码
               NSLog(@"%@",weakSelf);
          });
      };
      block();
    

    这样执行下去,如果在延时时间2秒还没到,控制器就dismiss或者pop(总之是销毁)了,打印出的weakSelf是null,
    为什么呢?
    因为只有push self或者present self的控制器对self强引用,当self dismiss了或者pop了,就没有人对self强引用了(block对self没有强引用),根据ARC的内存管理原则,当没有人对一个对象强引用的时候,该对象就会销毁.
    所以,当self dismiss了或者pop了,self就销毁了,2秒后block再访问self的时候,self已经不再了.
    这时,我们要做如下处理:

      __weak typeof(self) weakSelf = self;
      block = ^{
          __strong typeof(weakSelf) strongSelf = weakSelf;//这个是局部变量 栈内的强指针 当这个block执行完毕  这个指针就会释放  
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//延时2秒执行下面的代码
               NSLog(@"%@",strongSelf);
          });
      };
      block();
    

    加上这一句:strong typeof(weakSelf) strongSelf = weakSelf; strong 就相当于定义为strong的property
    那么当self销毁的之前(pop或者dismiss之前),有两个人对self强引用(一个是push self或者present self的控制器,一个是这个block中定义的strongSelf).
    当控制器销毁的时候(pop或者dismiss时),push self或者present self的控制器不在强引用self,self失去一个强引用,但是self不会销毁,因为block中定义的strongSelf还在对self强引用.
    但是你会问,那这么不会造成循环引用吗?不着急,继续往下看:
    当延时2秒到了,block可以访问到strongSelf
    当延时block代码块过了,strongSelf就会指向nil了(因为strongSelf是局部变量,存在栈内的强指针 当这个block执行完毕,这个指针就会释放)
    此时就没有人对self进行强引用了,self也会销毁,
    至此,大家都销毁了..

    10. Block其他注意点

    • block中可以定义和外界同名的变量,并且如果在block中定义了和外界同名的变量,在block中访问的是block内部的变量。可以通过打印变量的地址看出两个变量地址不同。

    • 因为block中使用的外面变量的值是拷贝过来的即值拷贝,所以在调用之前修改外界变量的值,不会影响到block中拷贝的值

    • 默认情况下,在block内部不能改变外面变量的值,如果想在block中修改外界变量的值,必须在外界变量前面加上__block。如果在block中修改了外界变量的值,会影响到外界变量的值。

    • 如果block中访问到了外界的变量,block会将外界变量拷贝一份到堆内存中。

    相关文章

      网友评论

      本文标题:笔记:iOS Block的基本使用

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