block介绍

  • 基本概念
    • 作用:用来保存一段代码块
    • 标志符:^
    • 可以有返回值
    • 可以有形参
    • 类似函数的调用方式
  • block的定义
    • 无返回值、无形参的block的定义
      // 定义block变量
      void (^myBlock) () = ^()// 无形参这里的括号可以省略
      {
        // 代码逻辑
      };
      // 简写形式
      void (^myBlock) () = ^
      {
        // 代码逻辑
      };
      // 调用block
      myBlock();
      
    • 有返回值、有形参的block定义
      // 定义block变量
      int (^sumBlock) (int,int) = ^int (int a,int b)// 这里的返回值类型可以省略
      {
        return a+b;
      };
      // 简写形式
      int (^sumBlock) (int,int) = ^(int a,int b)
      {
        return a+b;
      };
      // 调用block
      int num = sumBlock(1,3);
      
  • block可以访问外部变量
      int a = 2;
      // 定义block变量
      void (^myBlock);
      // 给block变量赋值
      myBlock = ^
      {
          NSLog(@"a = %d",a);
      };
      // 调用block
      myBlock();
    
  • block可以修改外部变量

    • block代码块内可以直接修改全局变量
    • block直接修改不是代码块内部的局部变量(直接给局部变量赋值),必须在变量前加上__block关键字
      __block int a = 2;
      // 定义block变量
      void (^myBlock);
      // 给block变量赋值
      myBlock = ^
      {
        a = 3;
      };
      // 调用block
      myBlock();
      
    • 进一步分析

      void testObject1()
      {
        NSMutableString *string = [NSMutableString string];
      
        // 遍历所有的键值对
        void (^myblock)() = ^{
            [string appendFormat:@"\t%@", @"key"];
            [string appendString:@" : "];
            [string appendFormat:@"%@,\n", @"obj"];
            NSLog(@"%@",string);
      
            // 在block中不能直接操作局部变量对应的那块内存空间
            // 上面string是追加内存空间,并没有修改
      //        string = [NSMutableString string];
        };
      
        myblock();
      }
      
      void testObject2()
      {
        Person *p = [[Person alloc] init];
      
        // 遍历所有的键值对
        void (^myblock)() = ^{
            // 这样是可以的
            p.name = @"bruce";
            p.age = 18;
            NSLog(@"name---%@,age---%zd",p.name,p.age);
      
            // 在block中不能直接操作局部变量对应的那块内存空间
            // 如果要想直接修改必须显示添加__block关键字
      //        p = [[Person alloc] init];
        };
      
        myblock();
      }
      
  • typedef定义block类型

      typedef int (^MyBlock)(int,int);
    
      // 备注:有时将block作为方法的参数,那么在定义block时加上参数名这样在使用方法时会自动生成对应参数名,
      // 如果不写就得让用户自己添加参数名,体验不是很好
      typedef int (^MyBlock)(int a,int b);
    
      // 以后就可以利用MyBlock变量类型来定义block变量了
      MyBlock sumBlock = ^int (int a,int b)// 返回值类型可以省略
      {
          return a+b;
      };
    
      // 简写如下
      MyBlock sumBlock = ^(int a,int b)
      {
          return a+b;
      };
    
      // 调用block
      int num = sumBlock(1,2);
    
  • block类型参数示例

      + (void)animateWithDuration:(NSTimeInterval)duration
                            delay:(NSTimeInterval)delay
           usingSpringWithDamping:(CGFloat)dampingRatio
            initialSpringVelocity:(CGFloat)velocity
                          options:(UIViewAnimationOptions)options
                       animations:(void (^)(void))animations          // 无形参
                       completion:(void (^)(BOOL finished))completion;// 有形参
    
      // 有返回值有形参
      - (NSUInteger)indexOfObjectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate;
    
      NSArray *array = @[@11,@23,@55];
      // 有形参有返回值的block的调用方式演示
      [array indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
          // 代码逻辑
          return YES;
      }];
    
  • 在说清楚block在内存中的存储,首先的先介绍下栈内存堆内存的概念
    • 栈内存
      • 栈内存空间是由系统来管理的
      • 对象的地址是存储在栈中
      • 程序的执行都是在栈内存中执行的,执行完毕后就会被系统释放
      • 在OC中,栈内存中不存在什么强引用和弱引用的概念
    • 堆内存
      • 程序员来管理的
      • 对象本身是在中存储的
  • 为什么block属性要用copy策略(这个只是MRC遗留的问题,在ARC中使用copy或者strong都行,但是为了让调用者知道内存策略,建议还是写copy)?
    • block默认创建出来就存储在栈内存
    • 如果不采取copy策略那么程序执行完毕后block那块代码就会被销毁,那么下次要是再想用就没有了
    • 采取copy策略将block的代码块拷贝一份放到堆内存中,这样只要引用block的对象不死,那么这个block代码块就不会死
  • 随之而来会产生一个问题就是block的循环引用,这里先图形说明一下:

    • 循环引用

    • 解决循环引用

      • ARC环境
        • 使用__weak关键字,iOS5.0以后才有
        • 使用__unsafe_unretained关键字,兼容4.x之前的版本
      • MRC环境
        • 使用__block关键字

  • 在block中修改外部变量的本质

    • 全局变量在block代码块中是可以被直接修改,因为全局变量的地址在程序中是固定的,如下图的验证过程
      • 首先定义了一个Son类(随便取的名字)
      • 在Car中定义一个Son类型的全局变量,在类方法test2中初始化该全局变量
      • 在Person中通过extern关键字引用该全局变量,然后在类方法test2中修改这个全局变量
      • 方法执行过程,可以发现全局变量的地址一直没发生过改变
    • 局部变量在block中是不能被直接修改的,要在block内部修改局部变量必须使用关键字__block或者static修饰局部变量,才能被修改
    • 使用__block关键字修饰的局部变量,会在block的代码块中将变量的引用计数器+1,相当于添加了一个强指针指向这个局部变量,这样即使外部的局部变量被销毁了,也不会影响block代码块内部修改这个局部变量,这就是为什么加上__block后就可以修改局部变量了
    • 总结:全局变量、用static修饰的局部变量,还有使用__block修饰的局部变量可以在block被修改,最根本的原因就是在block代码块执行的时候可以访问到这些变量

block用法总结

  • 作为局部变量

    // 格式
    returnType (^blockName)(parameterTypes) 
    = ^returnType(parameters) {...};
    
    // 示例
    int (^sumBlock) (int,int) = ^int (int a,int b)// 这里的返回值类型可以省略
    {
        return a+b;
    };
    
    // 简写,要能看得懂,建议写上返回类型
    int (^sumBlock) (int,int) = ^(int a,int b)
    {
        return a+b;
    };
    
  • 作为属性

    // 格式
    @property (nonatomic, copy) returnType (^blockName)(parameterTypes);
    
    // 示例
    @property (nonatomic, copy) int (^sumBlock)(int,int);
    
  • 作为方法参数

    // 格式,建议写上参数名,这样在自动生成时也会生成参数名,方便使用者
    - (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;
    
    // 示例,建议带参数名
    - (void)someMethodThatTakesABlock:(int (^)(int a,int b))sumBlock;
    
    // As an argument to a method call
    [someObject someMethodThatTakesABlock:^int (int a,int b) {...}];
    
  • 使用typedef定义block

    // 定义blokc类型时,建议写上参数名,这样在自动生成时也会生成参数名,方便使用者
    typedef returnType (^BlockTypeName)(parameterTypes);
    BlockTypeName blockName = ^returnType(parameters) {...};
    
    // 示例
    typedef int (^OperationBlock)(int a,int b);
    OperationBlock sumBlock = ^int(int a,int b) {...};
    

    使用block的注意点

    • 调用block是会报错的,所以调用block方法必须先判断block是否为空

results matching ""

    No results matching ""