iOS篇-block篇

一 : 科普一分钟

  • 什么是block: 个人简单的理解为就是一个存放代码片段的容器,作用就是保存代码.
  • block 苹果官方定义为 对象 可以用数组 和 字典进行操作,写到这同学们可能会明白了,block 说的简单点就是 一个可以存放代码片段的对象,可以进行内存管理,可以作为属性,等普通对象操作.

既然分析明白了就可以做事情了,接下来进入block 的神器世界

二 : block的基本使用

  • block的声明
    void(^block)();
    声明一个名字为 block 的 block 对象;当然 名字可以换啊 我们可以用doit 代替,更形象
    void(^doit)();
    声明了名字 为doit 的block 代码块对象

  • block 的定义
    block 的定义方式通常有三种方式

  • 第一种 :没有参数 没有返回值 = 右边相当对名字为 doit1这个block对象的代码块的赋值 里面存放一段代码
void(^doit1)() = ^(){
       NSLog(@"关注我吧,会有更多精彩");
};
  • 第二种 :如果没有参数,参数可以隐藏

    void(^block2)() = ^{
    NSLog(@"关注我吧,会有更多精彩");
    };

    如果有参数,定义的时候 必须要写参数 而且必须要有参数变量名

    void(^block22)(int) = ^(int a){
    
      NSLog(@"关注我吧,会有更多精彩");
    
    };
  • 第三种 block 返回值可以省略,不管有没有返回值 都可以省略

    int(^block3)() = ^int{
    
          return 3;
    
      };

    也可以写成

    int(^block3)() = ^{
    
       return 3;
    
       };
  • block 的类型
    int(^)(NSString *) 我们定义了一个返回值 为int 参数为 NSString block 代码块对象类型 它现在还没有名字 我们现在给它取名为 :doit4

    int(^doit4)(NSString *) = ^(NSString *name){
    
         return 2;
    
     };
  • block 调用
    不要以为定义完成 就大功告成了 ,因为我们还没有去调用它,我们写block 代码块的 目的 就是去使用它 .
    doit1();
    之前我们已经声明了 doit1 这个block 代码块对象 所以运行后的结果是
    NSLog(@"关注我吧,会有更多精彩");
  • block 的快捷方式
    我们手动去写block 会很麻烦 我们可以敲 inline 然后选择第一个 接下来 就可以方便我们使用了,会自动联想代码

    <#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
       <#statements#>
       };
  • block作为属性
    block 对象也可以当成属性使用 属性的书写原则是 如何声明 就如何属性 block

例如
@property(nonatomic,strong) void(^doit)() ;
一个无返回值,无参数的block类型 名字为doit的属性对象

当然这样书写 我们会不习惯 我们还可以重新定义类型 来写成我们熟悉的形式.
例如重新声明类型

//BlockType;类型的别名
typedef void(^BlockType)();

注意 这个BlockType 是类型名 并不是对象名
所以我们要再定义一个 类型 为BlockType 名字为doit 的对象
@property(nonatomic,strong) BlockType doit;

- blcok 内部变量传递分析
  • 看代码分析结果:
- (void)viewDidLoad {
    [super viewDidLoad];
    int a = 3;

    a = 5;

    void(^block)() = ^{

        NSLog(@"-- %d",a);

    };
    block();


}

结果为 : 5

  • 看代码分析结果
- (void)viewDidLoad {
    [super viewDidLoad];
    int a = 3;



    void(^block)() = ^{

        NSLog(@"-- %d",a);


    };

     a = 5;
    block();


}

结果为 : 3

  • 原因分析
    如果是局部变量 block 是值传递,当定义block 是 a = 3 此时,block 内部代码块的a = 3 ,改变a = 5,但是不影响block 代码块内容.

  • 看代码分析结果

- (void)viewDidLoad {
    [super viewDidLoad];
  static  int a = 3;
    void(^block)() = ^{

        NSLog(@"-- %d",a);
    };

     a = 5;
    block();  
}
  • 结果 : 5
  • 原因分析 : 如果是静态变量,全局变量,block 是指针传递

三 : block 在开发中的基本应用

1. block 保存一段代码

实例场景:在我们写tableView列表中 根据不同的cell 判断不同点击事件 我们多数人的做法是 通过很多很麻烦的判断如下:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    if (indexPath.row == 0) {

    }else if (indexPath.row == 1){

    }else if (indexPath.row == 2){

    }

}

其实我们可以用Block 存储要做的事情 来代替这些复杂的判断,因为这样增加了代码的可读性,而且需求发生变化的时候也非常容易的完成了需求的变化
做法:

  • 我们在cellItem 这个模型里添加 block 属性
    //保存每个cell 做的事情
    @property(nonatomic,strong) void(^block)();
  • 保存每个模型对应cell 要做的事情

    cellItem *item1 = [cellItem itemWithTitle:@"打电话"];
      item1.block = ^{
          NSLog(@"睡觉");
      };
    
      cellItem *item2 = [cellItem itemWithTitle:@"发短信"];
      item2.block = ^{
          NSLog(@"吃饭");
      };
    
      cellItem *item3 = [cellItem itemWithTitle:@"发邮件"];
      item3.block = ^{
          NSLog(@"装逼");
      };
      _items = @[item1,item2,item3];
  • 替换复杂又low 的判断
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    cellItem *item = self.items[indexPath.row];
    if (item.block) {
        item.block();
    }

}
2. 逆向传值

我们的经典逆向传值的例子就是代理了吧,大多数的开发者也是习惯于代理的方式,但是block 的传值方式要比代理好太多了,代理的6步骤比较复杂. 接下来我们要用block 替换代理
实例场景:

  • 首先我们定义了两个控制器ViewController 和 TZpopViewController 点击ViewController 的view模态到TZpopViewController 然后 点击TZpopViewController的view 返回 并且传至到ViewController

  • ViewController方法

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{


    TZpopViewController *vc  = [[TZpopViewController alloc]init];
    vc.block = ^(NSString *index) {

        NSLog(@"index = %@",index);

    };

    vc.view.backgroundColor = [UIColor brownColor];
    [self presentViewController:vc animated:YES completion:nil];


}
  • TZpopViewController 属性
    @property(nonatomic,strong)void(^block)(NSString*index) ;

  • TZpopViewController 方法

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    if (_block) {
        _block(@"tz123");

    }

}
  • 分析
    首先当我们在ViewController点击触发 touchesBegan 方法时候 创建了 TZpopViewController 并且给它的 block 属性赋值了一段带代码
= ^(NSString *index) {

        NSLog(@"index = %@",index);

    };

此时这个代码片段是不走的 ,因为没有操作执行block

当我们触发TZpopViewController 中的 touchesBegan方法时候 做了执行block 的方法

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    if (_block) {
        _block(@"tz123");

    }

}

此时则会在ViewController 中走打印方法 打印结果为tz123

三 : block 内存管理

  • block 在MRC下的注意事项
    由于MRC没有strong ,weak局部对象相当于基本类型,存放于栈区,代码走过就销毁

1.在MRC 中block只能使用copy 修饰,不能使用retain,因为使用retain 了block 存放于栈区,被销毁了.

2.在MRC只要block引用了外部局部变量,block 放在栈里面,只要block 没有引用外部局部变量,block 放在全局区里面.

  • block在MRC下的注意事项

1.只要block 引用外部局部变量,block放在堆里面

注意:在ARC block 使用 strong 修饰,最好不用使用 copy 节省性能.
  • 分析
    我们使用 copy 通常是浅拷贝 因为 是不可变赋值给不可变 ,不用考虑 重新分配内存的问题, 我们copy 修饰 内部方法会走 [block copy] 这个方法系统会分析是否从新分配内存 ,浪费资源.所以我们通常情况下使用strong

四 : block 的循环引用问题

  • 我们在使用的时候稍微疏忽会造成循环引用,双方都不会销毁,导致内存泄漏.
  • 模拟循环引用
    在ViewController 定义属性
    @property(nonatomic,strong)void(^block1)();
- (void)viewDidLoad {
    [super viewDidLoad];

    _block1 = ^{

     NSLog(@"关注我有更多精彩%@",self);

         };

    _block1(); 
}

这种写法就会造成循环引用.

  • 分析
    block造成循环引用:block 会对里面所有的强指针变量都强引用一次


    循环引用图解.png
  • 解决办法

    __weak typeof (self) weakself = self;

 _block1 = ^{


        NSLog(@"关注我有更多精彩%@",weakself);

    };

    _block1();
}

解决循环引用图解.png
  • 思考
    假如我们想在block 代码块种写一段延迟做的事情怎么办
    通常block 代码块走过,则会销毁拿不到.

  • 解决办法

- (void)viewDidLoad {
    [super viewDidLoad];

    __weak typeof (self) weakself = self;

    _block1 = ^{

        __strong typeof (weakself) strongself = weakself;

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"关注我有更多精彩%@",strongself);

        });

    };

    _block1();


}

再次强引用一次,知道 延迟代码走完,则指针释放 .

五 : block 在开发中的高级应用

- block 当参数在开发中的应用

1.什么情况下block 被当成参数了呢,
看参数有没有^ 如果有^ 怎参数为block.
2.什么时候需要把block 当成参数,
做的事情由外部决定,什么时候做由内部决定.

代码举例:

  • 做一个计算器:要求怎么计算由外部决定,时候时候计算由内部决定

TZcaculoterManager

@property(nonatomic,assign)NSInteger result;
//计算
-(void)cacutor:(NSInteger(^)(NSInteger result))cacultorblock;
-(void)cacutor:(NSInteger(^)(NSInteger result))cacultorblock{


   _result = cacultorblock(_result);

}

调用

- (void)viewDidLoad {
    [super viewDidLoad];
    caculoterManager *magr = [[caculoterManager alloc]init];

    [magr cacutor:^(NSInteger result){

        result += 5;
        result += 7;
        return result;

    }];

}
  • 分析 表面看有点难理解 其实我们不妨把它当成另一个函数
-(void)magrCautor:(NSString*)tz{

}

感觉是一个意思,后者传递的是字符串,前者传递的是block 代码块,区别在于前者控制 传递的代码块何时调用.AFN 封装 等各种封装 很多采用这种

- block 当返回值在开发中的应用
  • 主要应用场景:链式编程,Masonry就是最典型的链式编程,其内部原理用就是用block实现

  • 链式编程特点:把所有的语句 用.号连接起来,好处:可读性非常号.

  • 代码实现 我们先简单写一个返回值为block 的函数

-(void(^)())test{


    return ^{


    };

}

这个是一个返回值 为无返回值,无参数的block
现在我们来调用这个函数

self.test();
相当于 self.test 这个函数给我们返回block
然后我们去执行 block()

  • 操作:我们现在还做 一个需求 封装一个计算器,提供一个累加方法

cacutotermanager

.h

@property(nonatomic,assign)int result;

-(cacutotermanager* (^)(int))add;

.m

-(cacutotermanager* (^)(int))add{

    return ^(int value){

        _result += value;

        return self;

    };

}

调用

  cacutotermanager *magr1 = [[cacutotermanager alloc]init];

    magr1.add(1).add(2).add(4);

模仿了 Masonry的链式编程方法.

  • 解析
    magr1.add调用方法后 返回block 执行 block(1);
    此时block 的返回类型是 cacutotermanager magr 再调用 magr(2)....
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值