Block实现iOS回调

回调函数是我们在编程中经常使用到的,但是很多新手只知道怎么用,不知其所以然。今天我们就来剖析下回调函数到底是个什么鬼。

先来看一个关于回调函数的形象的比喻

你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件

再看下比较正式的定义

callback(回调)就是一段「代码」,我们会通过某种途径,将这段「代码」和一个特定的事件(event)联系起来,当特定事件(event)发生后,这段「代码」被执行。


好了,大家先不管能不能理解上面两种定义,先在脑袋里面有个印象。我们下面来通过实际的例子帮助大家理解回调函数的使用,然后在回头看看上面的定义,定会有拨开乌云见天日的感觉。

为什么要有「回调(callback)」

在这里,斗胆将程序分为两种:

  • 「非事件驱动」型程序
  • 「事件驱动(event-driven)」型程序

「非事件驱动」型程序。

这类程序,遵循这样一个流程:启动程序 -> 执行程序(代码) -> 退出程序。程序会在执行完所有代码后,立刻退出,中途不会有任何事件(event)发生(除非有bug)。

「事件驱动(event-driven)」型程序

这类程序,遵循这样一个流程:启动程序 -> 等待事件(event) -> 事件被触发 -> 执行callback(回调) -> 继续等待事件(event) -> 人为退出程序。

打个比方,我想用淘宝APP帮手机充值,一打开APP,它并不会马上跳到充值页面,是要等待我的点击事件,当点击了充值的按钮,才会跳到充值页面(执行了callback)。

所以,大家应该很容易联想到,iOS的应用几乎都是「事件驱动(event-driven)」的,应用一经启动,就在等待事件的发生,当发生某个事件(比如点击了某个按钮),应用就会执行某段代码(callback)进行响应。

<font color =red>这里的「事件(event)」,是非常宽泛的,可以是使用者的一次点击、可以是系统的一次通知、可以是服务器返回的一次数据、可以是蓝牙外设连接成功后,发送给手机的一条指令等等。</font>


通过上面我们知道在事件驱动型的程序里面,我们需要当某一个特定条件被触发的时候去调用回调函数来执行某个动作。

下面我们来看看具体的例子。

实例1、数据更新触发回调

如下图所示:

在个人主页我们需要显示用户的昵称、头像、性别、签名,然后点击编辑按钮,跳转到编辑个人信息页面,编辑完个人信息之后,点击返回到主页的时候,我们需要根据用户设定的值来更新相应的信息。

个人主页

image

编辑用户信息

image

实现代码如下:

personalHomePage.m文件
**************************
- (void)editPersonalInfo{
    EditPersonalInfoController *edit = [[SGEditPersonalInfoController alloc]init];
    
    __weak typeof(self)weakself = self;
    edit.updateUserInfoBlock = ^(NSString *userName, NSString *userSex, NSString *userSign ,UIImage *userImage)
    {
        weakself.userHeaderImageView.image = [userImage circleImage];//用户头像
        weakself.nickNameLabel.text = userName;//昵称
        weakself.signLabel.text = userSign;//签名
        weakself.sexImageView.image = ([userSex isEqualToString:@"女"]) ? [UIImage imageNamed:@"icon_female"] : [UIImage imageNamed:@"icon_male"];//性别
    
     }
}

EditPersonalInfoController.h文件
***********************************
@interface EditPersonalInfoController :UITableViewController
@property(nonatomic, copy)void(^updateUserInfoBlock)(NSString *userName, NSString *userSex, NSString *userSign ,UIImage *userImage) ;
@end


EditPersonalInfoController.m文件
***********************************
- (void)personalInfoHadChanged{
    if (self.updateUserInfoBlock) {
        self.updateUserInfoBlock(self.userName, self.userSex, self.userSign, self.userImage);
    }
}

为了方便叙述,我们做如下规定:
personalHomePage类为A,EditPersonalInfoController为B

1、首先我们在A页面的editPersonalInfo方法里面跳转到B页面,在这个方法里面我们<font color =red>登记回调函数(block)</font>,这个回调函数的作用就是更新A页面的信息。

2、首先B页面需要<font color =red>申明回调函数</font>,就是@property(nonatomic, copy)void(^updateUserInfoBlock)(NSString *userName, NSString *userSex, NSString *userSign ,UIImage *userImage) ;,然后B页面在个人信息被更改,这个时候触发了<font color =red>回调关联的事件</font>,调用方法personalInfoHadChanged方法,在方法里面来<font color =red>调用回调函数</font>,参数就是自己页面更改的信息。

3、一旦回调函数被调用,A页面就<font color =red>响应回调事件</font>,作用就是根据B页面传递过来的参数更改A页面的各个控件的值。

    edit.updateUserInfoBlock = ^(NSString *userName, NSString *userSex, NSString *userSign ,UIImage *userImage)
    {
        weakself.userHeaderImageView.image = [userImage circleImage];//用户头像
        weakself.nickNameLabel.text = userName;//昵称
        weakself.signLabel.text = userSign;//签名
        weakself.sexImageView.image = ([userSex isEqualToString:@"女"]) ? [UIImage imageNamed:@"icon_female"] : [UIImage imageNamed:@"icon_male"];//性别
    
     }

现在再回头把我在这三个步骤里面标记的红色字体部分和文章开头的比喻对照看下,是不是感觉有些明白了。我们再接再厉,多看几个例子,加深理解。

实例2、人为触发回调

除了实例1中提到的当特定的数据(页面数据,服务器返回数据等)被更改的时候需要触发回调函数做一些事情,还有一种情况也需要触发回调函数来做一些处理。

比如用户点击了某个按钮或者页面,我们需要做回调去处理一些逻辑。

具体来看一个实际场景:

如下图所示,是当网络无连接的时候显示的一个view,提示用户可以点击页面进行重新加载。

网络错误View

image
image

我们需要给该view添加一个Tap手势,当用户点击的时候我们就触发回调函数,让使用该viwe的控制器去重新加载数据。

代码实现:

NetworkErrorPromptView.h文件
**********************************
@interface NetworkErrorPromptView : UIView
@property(copy,nonatomic)void(^reloadBlock)();//申明回调函数
@end


NetworkErrorPromptView.m文件
**********************************
@implementation NetworkErrorPromptView

-(instancetype)initWithFrame:(CGRect)frame{
    if (self == [super initWithFrame:frame]) {
        self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil] firstObject];
        [self addGestureRecognizer:[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(reload)]];
    }
    return self;
}

-(void)reload{
    if (self.reloadBlock)
    {
        self.reloadBlock();//调用回调函数
    }
}
@end

假设在一个UITableViewController里面使用了该view,使用懒加载初始化该view

-(NetworkErrorPromptView*)errorView{
    if (!_errorView) {
        _errorView = [[SGNetworkErrorPromptView alloc]init];
        _errorView.frame = CGRectMake(0, 0, self.tableView.w, self.tableView.h);
        __weak typeof(self) weakself = self;
        _errorView.reloadBlock = ^{//登记回调函数,被触发就调用
            [weakself.tableView.mj_header beginRefreshing];
        };
    }
    return _errorView;
}

PS:
实例1和实例2非常类似,只是触发回调函数的方式不一样而已,大家理解了实例1,实例2应该不难理解。
所以我就不再一步步的讲解,只是在使用和触发回调函数的地方做了注释


iOS的四种回调模式

其实在iOS里面有四种回调模式,具体如下图所示:

image

我们上面讲解的是使用block实现回调,因为block的语法看起来有些怪异,所以很多人刚开始使用block实现回调函数就容易出错。

其实block我们可以理解为简版的delegate模式,delegate模式我相信大家理解起来不困难吧,我们经常使用UITableViewController就有很多代理方法。

代理模式就是A页面在特定状态(我们上面说的实例1和2)委托B页面去做某一件事(触发回调函数)。

仔细想一下是不是就是block啊。

如果你不熟悉block的使用,完全可以使用delegate来代替block,用多了就理解回调函数了,然后你想用delegate还是block亦或是notification都可以。(notification少用,影响性能)


总结

我们知道如何使用回调函数,那么我们什么时候才需要用到回调函数呢?

结合上面的实例1和2,我们不难发现,以下场景需要使用回调函数。

如果我们的代码逻辑就是从上到下按顺序执行,中途不需要去做其他的逻辑,那就没必要使用回调函数了。

如果我们需要让只有当某一特定条件满足的时候(数据被更新,用户点击事件),我们才会去执行一段逻辑。这个时候我们就需要使用回调函数来实现了。


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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • iOS网络架构讨论梳理整理中。。。 其实如果没有APIManager这一层是没法使用delegate的,毕竟多个单...
    yhtang阅读 5,174评论 1 23
  • 1.JQuery 基础 改变web开发人员创造搞交互性界面的方式。设计者无需花费时间纠缠JS复杂的高级特性。 1....
    LaBaby_阅读 1,167评论 0 1
  • 1.JQuery 基础 改变web开发人员创造搞交互性界面的方式。设计者无需花费时间纠缠JS复杂的高级特性。 1....
    LaBaby_阅读 1,330评论 0 2
  • 本来是想完成30天每天看一个开眼小视频的挑战的,可是这么简单的任务竟然都不能持续完成,鄙视自己...分享24个开眼...
    South_Lin阅读 1,180评论 0 0