Block在开发中的入门学习

鸡汤

“你知道一败涂地有什么好处吗?”
“那就是,你仅剩一事可做:绝地反击”

前言

本人作为一个菜鸟干iOS开发一年左右,除了深感目前该行业的艰难以外,也在慢慢学习当中,今天和大家谈谈关于 我对Block的认识,有什么不足的地方,还望大家多多指正

1.Block的认识

Block实际上是OC语言对闭包的实现,是带有自动变量值的匿名函数
Block的作用: 保存一段代码
闭包:一个函数,或者一个指向函数的指针,加上这个函数执行的非局部变量。通俗来说就是闭包允许一个函数访问声明该函数运行上下文中的变量,甚至可以访问不同运行上文中的变量。

2.Block的基本语法 (声明 -> 实现 -> 调用)

书写Block的快捷键 : inline

  1. 无参数无返回值
 void(^b1)(void);   //声明   ^托字符:表示后面的是 block变量
 void(^b1)(void)   //block的类型
//b1是 block名字,也叫 block 变量
 ^(void){ };  //block的实现部分
    void(^b1)(void) = ^(void){  //声明实现放一起了
        NSLog(@"无参无返回值");
//      block 的实现部分
    };
//调用 block.
    b1();
  1. 无返回值,有参数
//定义block 参数只需要有一个类型,但是实现部分需要有参数名。
    void(^b2)(NSString *) = ^(NSString *string){
        NSLog(@"%@",[string stringByAppendingString:@"有人说写字难看的男生长得很帅"]);
    };
    b2(@"照镜子发现,都TM是骗人的");  //调用block 
  1. 有参数,有返回值
// 写一个block实现两个数的 乘积,并返回结果
    NSInteger(^b3)(NSInteger,NSInteger) = ^(NSInteger a,NSInteger b){
        return a * b;
    };
    NSInteger result1 = b3(3,5);
    NSLog(@"%ld",result1);
  1. 有返回值,无参数
// 写一个 block实现,返回一个[30,50]的随机数;
    int (^b5)(void) = ^(){
        int i = arc4random()%21+30;
        return i;
    };
    int i = b5();
    NSLog(@"%d",i);

3.Block在开发中的应用场景

  • 在一个方法中定义,在另一个方法中调用
  • 在一个类中定义,在另一个类中调用 (常用)

声明

//BlockType:类型的别名,这里不是变量名
typedef void(^BlockType)();

@interface ViewController ()
//block怎么声明,就如何定义成属性
@property (nonatomic, strong) void(^block)();  //开发中推荐用这种
//@property (nonatomic, strong) BlockType block; //和上面一样
@end

实现

void(^block)() = ^() {
        NSLog(@"这里就是Block块里的内容,注意:只有在调用block的时候这个block块里的内容才会被执行");
    };
    _block = block;

调用

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // block的调用:就去寻找保存的代码,直接调用
    _block();
}

需求:TableView展示3个cell,打电话,发短信,发邮件

首先我们创建一个模型Cell item 在.h中代码如下

#import <Foundation/Foundation.h>
@interface CellItem : NSObject

//设计模型:控件需要展示什么内容,就定义什么属性
@property (nonatomic, strong) NSString *title;

//保存每个cell做的事情
@property (nonatomic, strong) void(^block)();

+ (instancetype)itemWithTitle:(NSString *)title;
@end

在Cellitem.m中

#import "CellItem.h"
@implementation CellItem

+ (instancetype)itemWithTitle:(NSString *)title{
    CellItem *item = [[self alloc] init];
    item.title = title;   
    return item;
}
@end

在ViewController.m中,我们实现需求

#import "TableViewController.h"
#import "CellItem.h"

@interface TableViewController () <UITableViewDataSource,UITableViewDelegate>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *items;
@end

@implementation TableViewController
/*
 需求:tableView展示3个cell,打电话,发短信,发邮件
 */
- (void)viewDidLoad {
    [super viewDidLoad];
    _tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:(UITableViewStylePlain)];
    [self.view addSubview:_tableView];
    _tableView.delegate = self;
    _tableView .dataSource = self;
    
    //创建模型
    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];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return _items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *cellIdentifier = @"cellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:cellIdentifier];
    }
    CellItem *item = self.items[indexPath.row];
    cell.textLabel.text = item.title;
//    if (indexPath.row == 0) {
//        //打电话
//    }else if (indexPath.row == 1){
//        //发短信
//    }else if (indexPath.row == 2){
//        //发邮件
//    }
    return cell;
}

//点击cell的时候就会调用
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    
    //把要做的事情(代码)保存到模型
    CellItem *item = self.items[indexPath.row];
    if (item.block) {
        item.block();  //此处调用了Block,所以才会走block块中保存的代码
    }   
}
@end

接下来我们看一下Block中的逆向传值

首先我们创建2个控制器, FirstViewController和SecondViewController, 在后者.h中我们实现block的声明代码如下

#import <UIKit/UIKit.h>

typedef void(^passValue)(NSString *str,NSURL *url);

@interface SecondViewController : UIViewController
@property (nonatomic, copy) passValue block;  //block作为属性
@end

在SecondViewController.m中实现布局以及在返回上一页的方法中进行block的调用

- (void)leftAction:(UIBarButtonItem *)sender{
    [self.navigationController popViewControllerAnimated:YES];
    
    if (self.block) {
        self.block(self.secondTF.text,self.url);  //调用Block
    }
}

在FirstViewController.m的点击跳转的方法中实现Block块的内容

- (void)rightAction:(UIBarButtonItem *)sender{
    SecondViewController *secondVC = [SecondViewController new];
    [self.navigationController pushViewController:secondVC animated:YES];
    
    __weak FirstViewController *weakSelf = self;
//这个block只有在second中被调用时才会执行,通过回调,将second中的文本框的内容传递给first
    secondVC.block = ^(NSString *str,NSURL *url){ //这里面的string就是回调second页面中传递的参数
        weakSelf.textField.text = str;
        NSData *data = [NSData dataWithContentsOfURL:url];
        weakSelf.imageV.image = [UIImage imageWithData:data];
    };
}

接下来是Block作为参数的使用
什么时候需要把block当做参数去使用呢? 做什么事情由外界决定,但是什么时候做由内部决定

需求: 封装一个计算器,提供一个计算方法,怎么计算由外界决定,什么时候计算由我内部决定
首先,我们创建一个CacultorManager类,代码如下:

#import <Foundation/Foundation.h>

@interface CacultorManager : NSObject
@property (nonatomic, assign) NSInteger result;
//计算
- (void)cacultor:(NSInteger(^)(NSInteger result))cacultorBlock;
@end
#import "CacultorManager.h"

@implementation CacultorManager

- (void)cacultor:(NSInteger (^)(NSInteger))cacultorBlock{
    if (cacultorBlock) {
        _result = cacultorBlock(_result);   
// 作为方法中的参数,这里开始调用 参数cacultorBlock, 也就是由方法内部来决定的
    }
}
@end

在ViewController.m中,我们实现如下代码:

- (void)viewDidLoad {
    [super viewDidLoad];
/**
 * 怎么区分参数是blcok,就看有没有^,只要有^,把block当做参数就行
 * 注意:把block当做参数,并不是马上就去调用Block,什么时候调用,由方法内部决定
 */
    //创建计算器管理者
    CacultorManager *mgr = [[CacultorManager alloc] init];

//调用了方法,block是参数,不管block内有多少东西,都只作为参数进行了传递,并不一定会执行。调用block才会执行    
    [mgr cacultor:^(NSInteger result) { 
        result += 5;
        result += 6;
        result *= 2;
        return result;
//上面的计算器类调用了方法就走方法里面的东西,方法里面又调用block块,就返回到这里走block块中的内容,也就是说具体执行不执行block块中的内容要看调没调用block
    }];
    NSLog(@"%ld",mgr.result);
}

接下来我们再看一下Block作为方法返回值的用法
这里要提到关于第三方框架Masonry的链式编程思想,即把所有的语句用 点语法 连接起来,这样优点就是可读性非常高,其核心就是返回值是一个block

- (void(^)())test{       //block 作为返回值
    NSLog(@"%s",__func__);
    return ^{
        NSLog(@"调用了blcok");
    };
}
----------------------- 分割线 ----------------------
- (void)viewDidLoad {
    [super viewDidLoad];
    self.test(); //这就是为什么masonry为什么能写出这样的效果,因为返回值是一个blcok
   
   //我们做个比较
    void(^block)() = ^{
        NSLog(@"调用了block");
    };
    block(); //这个和上面的 self.test()一样
}

需求: 封装一个计算器,提供一个加号方法
同样的,我们创建一个CalculatorManager类, 在这个类中我们先实现普通的方法,然后和作为返回值的block做一个比较

#import <UIKit/UIKit.h>

@interface CalculatorManager : UIViewController

@property (nonatomic, assign) int result;

- (CalculatorManager *)add:(int)value;   //普通方法

- (CalculatorManager * (^)(int))add;  // block作为返回值方法

@end
#import "CalculatorManager.h"

@interface CalculatorManager ()

@end

@implementation CalculatorManager
- (CalculatorManager *)add:(int)value{
    _result += value;
    return self;
}

- (CalculatorManager * (^)(int))add{
    return ^(int value){
        _result += value;
        return self;
    };
}
@end

我们在ViewController.m中使用这个类,看看有什么不同点

- (void)viewDidLoad {
    [super viewDidLoad];
    CalculatorManager *mgr = [[CalculatorManager alloc] init];
       [mgr add:5];
       [mgr add:5];
       [mgr add:5]; //如果这样的话,不太好,会有很多行
       NSLog(@"%d",mgr.result);
    
      mgr.add(5);
      mgr.add(5).add(6).add(7).add(8).add(9).add(10);
      NSLog(@"%d",mgr.result);
}

以上就是一些block的基础使用,接下来我们来看一下使用block过程中的一些注意点

Block的内存管理

在苹果的api文档中,block是一个对象,所以存在着内存管理的方式(MRC/ARC)

在MRC中:

  • 只要block没有引用外部的局部变量,block放在全局区
  • 只要block引用外部局部变量,block放在里面
  • block只能使用copy,不能使用retain,如果使用retain,block还是在栈里面(既然是在栈里面,代码块一过,block就会销毁)

在ARC中:

  • 只要block没有引用外部的局部变量,block放在全局区
  • 只要block引用外部局部变量,block放在里面
  • blcok使用Strong,最好不要使用copy

Block的循环引用:

  • Block造成循环引用:Block会对里面所有强指针变量(外部对象变量)全部强引用一次 所以加 WeakSelf。
  • 如果是局部变量,block是值传递
  • 如果是静态变量,全局变量,__block修饰的变量,block是指针传递

总结:

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

推荐阅读更多精彩内容