iOS知识的学习总结--block回调函数
在iOS开发中,block是很强悍的,也是我们必须掌握的,在此对block的回调做一个小结。
首先简略地介绍一下block;Block的实际行为和Function很像,最大的差别是在可以存取同一个Scope的变量值。Block实体形式如下:
^(传入参数列){行为主体};
Block实体开头是“^”,接着是由小括号所包起来的参数列(比如 int a, int b, int c),行为主体由大括号包起来,专有名字叫做block literal。行为主体可以用return回传值,类型会被compiler自动辨别。如果没有参数列要写成: ^(void)。
PS:Block中的一个重要特性:内存释放。凡在block中使用的变量,block都将进行自动的释放。所以,如果使用系统全局变量作为参数传入block,一定在传入之前retain一次,才能保证这个变量不被release。如下:
1 //常见的几种block定义和使用 2 3 - (void)viewDidLoad 4 { 5 [super viewDidLoad]; 6 7 //(1)定义无参无返回值的Block 8 void (^printBlock)() = ^(){ 9 printf("no number"); 10 }; 11 printBlock(); 12 13 14 int mutiplier = 7; 15 //(2)定义名为myBlock的代码块,返回值类型为int 16 int (^myBlock)(int) = ^(int num){ 17 return num*mutiplier; 18 } 19 //使用定义的myBlock 20 int newMutiplier = myBlock(123); 21 NSLog(@"newMutiplier is %d",myBlock(123)); 22 } 23 24 25 //定义在-viewDidLoad方法外部 26 //(3)定义一个有参数,没有返回值的Block 27 void (^printNumBlock)(int) = ^(int num){ 28 NSLog(@"int number is %d",num); 29 };
这篇文章着重介绍一下我对block回调的理解与总结,回调函数可以从下面三个方面去理解:
1、回调函数类似通知、委托
2、回调函数声明、实现、回调可以在都一个类中,也可以把回调写到别的类中
3、在A类中声明函数,在实现函数中,达到满足的条件,回调,在B类中的回调函数会被调用,并去处理满足条件的事情
要想搞明白回调函数是怎么回事?那我们就要先想明白三个问题:
- 什么是回调函数?
- 使用回调函数有什么好处?
- 什么时候用回调函数?
第一个问题:什么是回调函数?
由声明函数的类来调用的函数叫做回调函数。普通函数可以让任何类调用。谁回调?这个谁指的就是声明回调函数的那个类。
回调函数,本质上也是个函数。它是由“声明”、“实现”、“调用”三部分组成。回调函数(其实是Block),它的的声明和调用在TestA类中,而实现部分在TestB类中。也就是说,TestB类实现了回调函数,但并没有权限调用,最终还是 由TestA类去进行调用的。我们称这样的机制为“回调”。简单地说就是:虽然函数的实现写在TestB类中,但是真正的调用还是得由TestA类来完成。而对比我们常用的正常函数函数来看,正常函数的声明、实现均在一个类中完成。下面写个小的例子来说明一下,示例代码如下:
1 TestA.h 2 3 /** 4 * 公司类 5 */ 6 #import <Foundation/Foundation.h> 7 8 @interface TestA : NSObject 9 10 /** 11 * 声明的回调函数:给指定的员工发工资 12 * 13 * @param staffID 普通参数:员工ID 14 * @param amount 回调函数:用来通知员工工资是多少 15 */ 16 - (void)paySalaryForStaff:(int)staffID WithMoney:(void (^)(int salary))amount; 17 /* 18 在这个对象方法中(void (^) (int salary))是修饰回调函数amount的,这里(void (^)(int salary))就像是类型一样,这个amount是一个参数为salary的无返回值函数,用来告知员工工资多少 19 */ 20 @end
1 TestA.m 2 3 #import "TestA.h" 4 5 @implementation TestA 6 7 /** 8 * <#Description#> 9 * 10 * @param staffID <#staffID description#> 11 * @param amount <#amount description#> 12 */ 13 - (void)paySalaryForStaff:(int)staffID WithMoney:(void (^)(int salary))amount { 14 15 //计算工资的多少 16 NSLog(@"工资5000,加班费25000,老大我很中意你,少年"); 17 //计算完之后就触发回调函数,告诉员工发工资 18 amount(30000); 19 } 20 21 @end
1 TestB.h 2 3 #import <Foundation/Foundation.h> 4 5 /** 6 * 员工类 7 */ 8 @interface TestB : NSObject 9 10 /** 11 * 员工败家方法 12 */ 13 - (void)spendMoney;
TestB.m #import "TestB.h" #import "TestA.h" @implementation TestB -(void)spendMoney { //各种无聊因为没有钱 //傻X公司还不发工资 //受不了了 我要问一下 TestA *sbA = [[TestA alloc]init]; //老子是007发工资了吗 [sbA paySalaryForStaff:007 WithMoney:^(int salary) {//这个地方才是回调函数amount的实现 //这里通过接收回调函数传的参数判断工资的多少 if (salary == 30000) { NSLog(@"我去,加班费还阔以,出去嗨一哈"); //各种嗨 钱不花完不回家 }else { NSLog(@"公司不要脸,老纸要跳槽"); } }]; } @end
借用大牛的一个例子来理解“回调函数”概念:
警局(类):
招收一名警察甲(声明函数),并培养、训练他(实现函数)。
招收一名卧底乙(声明函数),但并没有培养他,而是被送进了黑社会。但有任务的时候,警务处会调用卧底乙(回调函数)。
黑社会(类):培养、训练卧底乙(实现函数)。
第二个问题:什么情况下使用回调函数?
假设有TestA、TestB两个类。
(1)TestA类有多种形态,要在TestB类中实现回调函数。如假设TestA类是网络请求开源类ASIHttpRequest,它可能请求成功,也可能请求失败。这个时候,TestB类就要针对以上两个情况,作不同的处理。
(2)TestA类的形态由TestB类决定时,要在TestB类中实现回调函数。如UITableView类就会提供很多回调函数(iOS专业术语称“委托”方法)
(3)TestA类需要向TestB类传递数据时,可以在TestB类中实现回调函数(A类一般是数据层比较耗时的操作类)。如举的那个发工资的例子。在实际编程中,这样的机制有个好处就是可以提升用户的操作体验。比如用户从X页面跳转到Y页面,需要向网络请求数据,而且比较耗时,那我们怎么办?有三种方案:第一种就是在X页面展示一个旋转指示器,当收到网络传回的数据时,在展现Y页面。第二种就是使用回调函数。用户从X页面直接跳转到Y页面,Y页面需要到数据让数据层去执行,当收到数据时,再在Y页面展现。第三种就是在Y页面中开启多线程。让一个子线程专门到后台去取数据。综合来说,第二种更加简介易懂,而且代码紧凑。
第三个问题:使用回调函数有什么好处?
(1)可以让实现方,根据回调方的多种形态进行不同的处理和操作,具体请参照ASIHttpRequest。
(2)可以让实现方,根据自己的需要定制回调方的不同形态。
(3)可以将耗时的操作隐藏在回调方,不影响实现方其它信息的展示。
(4)让代码的逻辑更加集中,更加易读。
下次在对block做一个全面的分析学习。
· C23和C++26的#embed嵌入资源指南
· 「EF Core」框架是如何识别实体类的属性和主键的
· 独立开发,这条路可行吗?
· 我在厂里搞 wine 的日子
· 如何通过向量化技术比较两段文本是否相似?
· 推荐 5 款实用的 Docker 可视化管理工具,工作效率翻倍!
· 瞧瞧别人家的接口重试,那叫一个优雅!
· 【EF Core】框架是如何识别实体类的属性和主键的
· MySQL 13 为什么表数据删掉一半,表文件大小不变?
· 上周热点回顾(7.7-7.13)