标签:
ios编程之回调机制详解:
————————————————
函数/方法/block块一系列概念:
函数在大部分高级语言中都是个重要的概念,函数实现就是对一段代码的封装,我们一般会为了完成某一个业务功能或编程逻辑而需要组织数行代码,而这数行代码还有可能被使用多次,所以将它们封装成一个函数,每一次的执行我们称之为调函数或函数调用。
在C程序中,我们知道程序是从main函数开始执行,执行完main函数就退出程序,其实我们程序员很少去跟踪整个程序的执行流,一个程序(一段二进制代码)如何从加载到运行我们自己的代码,这其中的过程我们很少涉及,作为应用层的程序员也确实没必要去理解这些幕后过程,但我还是要说明一点:C程序的main函数是由我们程序员实现,由C的幕后去调用这个main函数,所以我们要实现这个函数。
在面程对象的语法中,有了类和对象的概念,一段代码可能归属于某个类或类的对象, 即该段代码只有某个类或类的对象才有调用权限,那么这段代码被封装成类的方法或类的实例方法,方法和函数这两个名称其实没必要区别,在c++中,方法也叫类的成员函数,在Objective-C中,方法的写法和函数也只有书写形式上的区别。既然有了方法,那么执行这个方法的代码也叫调方法或方法调用。
在Objective-C的语法中又多了一个概念叫block, 译为代码块, 它也是对一段代码的封装,只是它相对函数或方法有更炫酷的功能(可以不通过传参即可访问块外的变量),如果想执行这个代码块,就只需像调函数一样来调block。
————————————————
回调的概念:
有三个角色(A,B,C),它们均可以为函数/方法/block块这一。在A的内部去调B, 但在调B时将C作为参数传入B的内部, 在B的内部会去调C ,这样一来,A没有直接调用C,而是通过角色B来调C ,这个过程叫回调,其中的C被称为回调函数/回调方法/回调block
————————————————
回调的分类:
随着角色C的种类不同,回调也分为: 函数回调,方法回调和block回调
————————————————
函数回调:
函数回调(角色C是函数)代码示例一:
// 函数回调: 只要角色C是函数(不管A和B是方法还是block),这个过程叫 函数回调
// 角色C: 是函数, 是一个回调函数
int getSum(int a, int b){
return a+b;
}
// 角色B:是函数
float getAvg(int a, int b, int (*sumFunc)(int,int)){
float sum = sumFunc(a,b);
return sum/2;
}
// 角色A: 是函数
int main(int argc, const char * argv[]) {
int x = 5;
int y = 6;
// 在A中调B的时候将C传入B的内部
float avg = getAvg(x, y, getSum );
NSLog(@"avg=%f", avg);
return 0;
}
解释:
A : main函数
B : getAvg函数
C : getSum函数
main内部调了getAvg,在调getAvg时将getSum作为参数传入getAvg的内部(以指针变量sumFunc保
存了传入的函数的地址,在内部调sumFunc从而达到了调用getSum函数的目的),上述是函数回调,只要C
是函数,我们这个过程叫 函数回调,不管A和B是函数/方法/block
注意: 角色B需要为角色C安排一个函数指针类型的形参
回调过程的传参通道:
在上边示例代码中,角色C需要两个参数,而角色B也通过形参传进来了两个数据正好为角色C所用,所以
我们说:如果角色B为了给C提供实参而定义了形参变量,我们将这个形参变量称作这个回调过程中
的传参通道 注意: 一般在C语言中,角色B只需用一个指针类型的形参即可提供给C任何数量的实参,如果是多个数据,
可以将多个数据封装成一个结构体,将结构体的地址传入即可,在oc语言中,角色B也只需传一个id类
型的对象即可,如果角色C需要多个对象,可以将多个对象加入到一个容器类对象(数组或字典)中,将
这个容器对象传入到B内部即可。
函数回调代码示例二:
// 函数回调二: 角色B为方法,角色C还是函数, 角色A为函数
int function(int a){
printf("a=%d",a);
return a;
}
@interface Person : NSObject
// 角色B: 方法的声明
-(void)workUsingFunction:(int (*)(int)) func;
@end
@implementation Person
// 角色B: 方法的实现
-(void)workUsingFunction:(int (*)(int))func{
func( 888 );
}
@end
// 角色A: 函数
int main(int argc, const char * argv[]) {
Person* p = [Person new];
// 调 B, 传入 C
[p workUsingFunction:function ];
return 0;
}
注意: 因为角色B是方法,OC中的方法的书写形式和函数有区别,形参的类型必须用圆括号括起来,所以上边
的形参类型写法是: (int (*)(int)) func 。 其中,func为形参变量名,(int (*)(int))为形参类型。如果
理解这种格式有困难,可以使用typedef的用法, 例如: typedef int (*cFunc_ptr)(int); 则cFunc_ptr就
代表 int (*)(int) 这种函数指针类型,那么角色B可写成: -(void)workUsingFunction:(cFunc_ptr)func;
————————————————
方法回调:
方法回调(角色C是方法)示例代码:
@interface Person : NSObject
// 角色C的接口,是方法
-(void)eat:(id)food;
@end
@implementation Person
// 角色C的实现
-(void)eat:(id)food{
NSLog(@"Person正在吃%@",food);
}
@end
// 角色B 是函数
void BFunction(id target, SEL selector, id object){
[target performSelector:selector withObject:object];
}
// 角色A 是函数
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person* p = [Person new];
BFunction(p, @selector(eat:), @"米饭");
}
return 0;
}
注意: 因为角色C是方法,而方法归属于类的对象,而只有类的对象才有对C的调用权限,所以要想在B的内部
完成对C的回调,必需同时将C所属的对象传入到B的内部,再让对象执行 performSelector: …来完成对
C的调用, 方法回调过程有两个特点与函数回调不一样,第一,在函数回调中,在角色B中直接用B的形参(
是一个函数指针类型的形参,待C以实参传入B时,这个形参就代表角色C)来调用即可完成对C的回调。但在
方法回调中,需要用对象来调performSelector: selector …来完成对角色C的调用。第二,传参通道必须
传oc对象类型
———————————————
block回调:
block回调(角色C是block块)示例代码一:
@interface Person : NSObject
// 角色B的接口 是方法
-(void)doBlock:(void (^)(void))block;
@end
@implementation Person
// 角色B的实现
-(void)doBlock:(void (^)(void))block{
// 执行block
block();
}
@end
// 角色A 是函数
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 角色C: 一个block代码块
void (^cBlock)(void) = ^(){
NSLog(@"此处是角色C的实现代码");
};
Person* p = [ Person new];
[p doBlock:cBlock];
}
return 0;
}
解释:
上边的示例中,角色C是一个不需要参数的block,所以角色B不需要为它开辟传参通道
注意:
由于block的特点,代码的形式千变万化,例如: [p doBlock:^{ NSLog(@"此处是角色C的实现代码");}];
block回调(角色C是block块)示例代码一:
@interface Person : NSObject
// 角色B的接口 是方法
-(void)doBlock:(void (^)(id))block withObject:(id)object;
@end
@implementation Person
// 角色B的实现
-(void)doBlock:(void (^)(id))block withObject:(id)object{
// 执行block
block(object);
}
@end
// 角色A 是函数
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 角色C: 一个block代码块
void (^cBlock)(id object) = ^(id object){
NSLog(@"此处是角色C的实现代码,参数=%@",object);
};
Person* p = [ Person new];
[p doBlock:cBlock withObject:@"肖在"];
}
return 0;
}
注意: 上边的示例中,角色C需要参数,所以角色B需要为它开辟一个传参通道
———————————————
回调过程中的异步处理:
在基于C的unix系统api接口中,创建一个新线程,将一段代码放到这个新线程中去执行,这个过程本来也是
属于一个异步回调的过程。那么在ios中的异步处理被多个框架封装,例如:NSThread, GCD的异步,NSQueue
等, 多线程异步回调花样繁多,但我们还是从上边的ABC三者的回调过程中来理解异步。
同步回调:
如果角色C被A传入B之后,C和B在同一个线程中执行,那么这个过程叫同步回调,同步回调的特
点是C没有返回时B也不可能返回,即在B中阻塞等待C的返回。
异步回调:
如果角色C被A传入B之后, C又被安排在另一个线程中去执行,即B和C不在同一个线程中执行,那
么这个过程叫异步回调,异步回调的特点是角色B不会等待C的返回。即角色B会很快执行完毕,但角色
C在B返回时可能还在执行
异步回调的示例代码:
@interface Person : NSObject
-(void)eat:(id)food;
@end
@implementation Person
//角色C:
-(void)eat:(id)food{
sleep(3); //休眠三秒
NSLog(@"Person正在吃:%@",food);
}
@end
// 角色B:
void BFunction(id target, SEL selector, id object){
// 这是NSObject的一个分类提供的异步执行接口
[target performSelectorInBackground:selector withObject:object];
}
// 角色A:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person* p = [ Person new];
BFunction(p, @selector(eat:), @"水果");
// 注意: 在回调eat:时,在eat:内部会休眠三秒,而异步回调是把eat:放到
// 一个新线程中去运行,角色B会很快返回,即不等待C的返回,所以在
// BFunction这个函数返回后,main函数也要退出了,那么整个程序退
// 出了, 那么在运行角色C的线程也会被迫退出(即还没执行完eat:就
// 会被退出),所以我们要阻止程序过快退出,可以用NSRunLoop,即可
// 以让主线程(即执行main函数的线程)休眠更长的时间等待即可
sleep(10);
}
return 0;
}
注意: 异步方法有很多种,不要拘泥于某一种,本文只是做最简单的举例
最强大的异步应该是GCD中的异步,能指定延时时间,或等待其它任务
完成之后再回调角色C,功能异常强大,而且GCD的性能也很高效,它是
从内核级别去优化多线程操作。
———————————————
方法回调过程中的对象内存管理:
在方法回调过程中,因为需要传入方法所属的对象,如果所有回调过程都是同步回调,
那么不需要考虑传入的这个对象的内存管理,正因为有了异步回调,那么有可能出现角
色C还没开始执行,C所属的对象y就已经被释放掉了,那么再对一个被释放的对象(野指针)
调方法就会发生内存错误或完不成对C的调用,所以在异步回调过程中,对传入的对象
有必要做强引用。在执行完C之后,又有必要对该对象做一次减计数。
———————————————
Objective-C中的定时器回调及其传参通道:
在ios中,定时器可以在指定的时间回调对象的方法,同时可以以某个时间间隔重复回
调对象的方法,此时定时器不能确保传入的对象何时会被释放掉,所以ios中的定时器
在启动时就将对象强引用,直到这个定时器对象关闭后才将对象计数减1,所以在定时
器回调的应用当中,要注意定时器的关闭,否则会引起内存泄漏。
另外需要注意的一点: 定时器回调时的角色C定义时的参数必须是NSTimer类的对象,
而真正需要传进来的参数就是NSTimer对象的userInfo属性,但在角色B传参时正常传即可。
定时器回调的示例代码:
@interface Person : NSObject
-(void)eat:(id)food;
@end
@implementation Person
// 角色C: 注意此处的形参必须是NSTimer,用于定时器回调
-(void)eat:(NSTimer*)timer{
// 实际的传参通道在timer的userInfo属性中,如果需要多个参数,那么该属性
// 可以是容器
id food = timer.userInfo;
NSLog(@"Person对象在吃:%@",food);
}
@end
// 角色A
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person* p = [Person new];
// 角色B: 是定时器NSTimer实现的一个类方法,参数传入到userInfo
[NSTimer scheduledTimerWithTimeInterval:1 target:p selector:@selector(eat:) userInfo:@"海参" repeats:YES];
// 注意,定时器也是异步处理,而且定时器是以事件形式触发执行,所以必须
// 何定主线程不退出,而且还要能接收定时器事件,所以需要开启NSRunLoop循
// 环,关于NSRunLoop和定时器如果不理解,可以去查阅相关文档
[[NSRunLoop currentRunLoop]run];
}
return 0;
}
注意: 1, 定时器处理必需启动NSRunLoop循环,
2, 传参通道
3, 定时器启动后p在定时器内部会被强引用
4, 何时关闭定时器要根据具体业务场景决定,关闭定时器即可取消对p的一次引用计数
—————完毕——————[作者: 肖在 ]——[公司: 北京千锋互联科技有限公司]——[日期:2016年4月24日]———
标签:
原文地址:http://www.cnblogs.com/xiaozai05/p/5428899.html