Apple开发_Block代码块

前言

  • iOS4.0开始,Block横空出世,自他出生开始,就深受Apple和开发者的喜爱.他其实就是c预言的补充,书面点说就是带有自动变量的匿名函数.
  • 其实很多初级开发者也很喜欢使用Block,第一呢感觉他很简洁,代码的可读性也高,第二确实无形中提升了代码的逼格,
  • Block 是一段预先准备好的代码,可以在需要的时候执行,可以当作参数传递。
  • Block 可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。
  • Block 是C语言的,类似于一个匿名函数,它和传统的函数指针很类似,
  • 但是Block是inline(内联函数)的,并且默认情况下它对局部变量是只读的。
  • 苹果官方建议尽量多用 Block。在多线程、异步任务、集合遍历、集合排序、动画转场用的很多。

1、Block 的基本使用格式

  • Block的格式
  • 1.1 无参数无返回值

    void(^tempBlock)() = ^() {
        NSLog(@"无参无返回值");
    };
    
    // 调用
    tempBlock();
    
  • 1.2 无参数有返回值

    int(^tempBlock)() = ^() {
        return 10;
    };
    
    // 调用的时候,无论你输入的是什么都返回的是10;
    tempBlock(100);
    
  • 1.3 有参数无返回值

    void(^tempBlock)(int) = ^(int temp) {
        NSLog(@"有参数无返回值");
    };
    
  • 1.4 有参数有返回值

    /* 定义了一个名叫 MySum 的 Block 对象,它带有两个 int 型参数,返回 int 型。等式右边就是 Block 的具体实现,大括号后需加分号 */
    int (^MySum)(int, int) = ^(int a, int b) {
        return a + b;
    };
    
    // 调用 Block
    int sum = MySum(10, 12);
    

2、Block 的经典实用场景

  • 2.1 修改外部变量

    • Block 可以访问局部变量,但是不能修改,如果要修改需加关键字 __block(双下划线)。
      // 这样定义时,局部变量 sum 只能读取值不能修改,编译时会报错
      // int sum = 100;
      // 这样定义时,局部变量 sum 既可以读取值又能修改
      __block int sum = 100;
      
      void(^sumWithYBlock)(int) = ^(int y) {
          sum = sum + y;
          NSLog(@"new value %d", sum);
      };
      
      打印的值就是sum + y, 100 + 100 = 200
      sumXWithYBlock(100);
      
  • 2.2 页面间的传值

    • 在第二个页面(SecondViewController)首先声明一个属性
      /* 先声明block的名字,并确定参数的类型 */
      /* 要使用 copy 类型,格式:@property (nonatomic, copy) 返回值类型 (^变量名) (参数类型列表); */
      @property(nonatomic, copy) void (^netViewBlock)(NSString *text);
      
    • 在点击按钮返回的时候,往回传你需要传的参数,参数类型要一致
      - (void)back {
          self.netViewBlock(@"你好");
          [self.navigationController popViewControllerAnimated:YES];
      }
      
    • 在第一页(FirstViewController),准备push进入下一页的时候,获取ViewController2的属性,并实现.
      - (void)click:(UIButton *)sender {
          // 把第二页的返回的值显示在label上
          UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(30, 100, 200, 30)];
          [self.view addSubview:label];
          SecViewController *vc = [[SecViewController alloc] init];
      
          vc.netViewBlock = ^(NSString *text) {
              label.text = text;
          };
          [self.navigationController pushViewController:vc animated:YES];
      }
      
  • 2.3 自定义Block

    • 例子:点击Button,需要改变Button的title
    • 实现:
    • 创建一个工具类,声明一个类方法,并自定义一个block,需要传title,所以传参类型是NSString
      @interface ChangeBuTitleTool : NSObject
      
      + (void)changeBuTitleWithText:(void(^)(NSString *titleText))text;
      
      @end
      
    • 实现
      @implementation ChangeBuTitleTool
      + (void)changeBuTitleWithText:(void(^)(NSString *titleText))text {
          if (text) {
              text(@"键盘风筝");
          }
      }
      @end
      
    • 在控制器里Button的点击的时候,实现改变title的方法
      - (void)addButton {
          UIButton *bu = [UIButton buttonWithType:(UIButtonTypeCustom)];
          bu.backgroundColor = [UIColor blueColor];
          bu.frame = CGRectMake(30, 90, 100, 50);
          [self.view addSubview:bu];
          [bu addTarget:self action:@selector(click:) forControlEvents:(UIControlEventTouchUpInside)];
      }
      
      - (void)click:(UIButton *)sender {
          [ChangeBuTitleTool changeBuTitleWithText:^(NSString *titleText) {
              [sender setTitle:titleText forState:(UIControlStateNormal)];
          }];
      }
      
  • 2.4 Block与typedef的结合

    • 在上一个例子中, 声明一个类方法, 其中定义block直接写在类方法里, 看起来很不和谐,
    • 尤其是对新手看起来可读性不太高, 可以用typedef单独定义一个block, 增加代码的可读性
      // 这样看起来是不是整洁多了
      typedef void(^titleBlock)(NSString *titleText);
      
      @interface ChangeBuTitleTool : NSObject
      
      + (void)changeBuTitleWithText:(titleBlock)text;
      //+ (void)changeBuTitleWithText:(void(^)(NSString *titleText))text;
      
      @end
      
      @implementation ChangeBuTitleTool 
      
      + (void)changeBuTitleWithText:(titleBlock)text {
          if (text) {
              text(@"键盘风筝");
          }
      }
      @end
      

3、Block 属性定义中为什么使用 copy 修饰

  • ARC 开发的时候,编译器底层对 block 做过一些优化,使用 copy 修饰可以防止出现内存泄漏。
  • 从内存管理的角度而言,程序员需要管理的内存只有堆区的。如果用 strong 修饰,相当于强引用了一个栈区的变量。
  • 而使用 copy 修饰,在设置数值的时候,可以把局部变量从栈区复制到堆区。
    // 用 copy 修饰定义属性
    @property (nonatomic, copy) void (^myTask)();
    
    // 定义,myBlock 是保存在栈区的,出了作用域,就应该被销毁
    void (^myBlock)() = ^ {
        NSLog(@"hello");
    };
    
    // 用属性记录
    self.myTask = myBlock;
    // 执行
    self.myTask();
    

4、循环引用

  • 在 Block 中调用 self 容易产生循环引用,无法释放对象,在程序中可以使用析构方法判断是否产生了循环引用。
    @implementation ViewController
    
    // 在 Block 中调用 self 容易产生循环引用
    [[QWebImageManager sharedManager] downloadImage:self.urlStr completion:^(UIImage *image) {
        self.image = image;
    }];
    
    @end
    
    // 判断是否存在循环引用,无法释放时即存在循环引用
    - (void)dealloc {                                               
        NSLog(@"成功退出");
    }
    
  • 可以使用关键字 __weak 声明一个弱变量,或者为属性指定 weak 特性。如:
    @implementation ViewController
    
    // 弱引用 self,typeof(self) 等价于 ViewController
    __weak typeof(self) weakSelf = self;
    
    [[QWebImageManager sharedManager] downloadImage:self.urlStr completion:^(UIImage *image) {
        weakSelf.image = image;
    }];
    @end
    
posted @ 2018-08-20 21:14  CH520  阅读(171)  评论(0编辑  收藏  举报