ios编程之回调机制详解: ios
————————————————c++
函数/方法/block块一系列概念:程序员
函数在大部分高级语言中都是个重要的概念,函数实现就是对一段代码的封装,咱们通常会为了完成某一个业务功能或编程逻辑而须要组织数行代码,而这数行代码还有可能被使用屡次,因此将它们封装成一个函数,每一次的执行咱们称之为调函数或函数调用。编程
在C程序中,咱们知道程序是从main函数开始执行,执行完main函数就退出程序,其实咱们程序员不多去跟踪整个程序的执行流,一个程序(一段二进制代码)如何从加载到运行咱们本身的代码,这其中的过程咱们不多涉及,做为应用层的程序员也确实不必去理解这些幕后过程,但我仍是要说明一点:C程序的main函数是由咱们程序员实现,由C的幕后去调用这个main函数,因此咱们要实现这个函数。 api
在面程对象的语法中,有了类和对象的概念,一段代码可能归属于某个类或类的对象, 即该段代码只有某个类或类的对象才有调用权限,那么这段代码被封装成类的方法或类的实例方法,方法和函数这两个名称其实不必区别,在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日]———