iOS篇-block篇-0基础到熟练应用全面解析

写在前面:demo 或者代码 评论发邮箱地址,我会及时给需要的小伙伴发过去.

一 : 科普一分钟

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

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

二 : block的基本使用

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

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

  1. 第一种 :没有参数 没有返回值 = 右边相当对名字为 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在ARC下的注意事项

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

注意:在ARC block 使用 strong 修饰,最好不用使用 copy 节省性能.

有的小伙伴会反驳说用copy ,其实是看需求而定 ,因为我们大部分需求是不可变赋值给不可变 ,所以这里建议用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)....

七 : 总结

通过上述大家对block 的应用应该了解的非常透彻了,希望大家活学活用. 根据需求选择正确的方法.效率更高. ^ _ ^ 下期再见. 想听什么可以在评论区留言. H5,Java 陆续会开始. 加油!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容