IOS回调方法总结
- 什么是回调
- 四种回调
- 循环对象
- 目标-动作对
- 辅助对象
- 通知
- Block
什么是回调
回调(callback)就是将一段可执行的代码和一个特定的事件绑定起来,当特定的事情被触发的时候,就会执行这段代码,这就是回调。IOS里面回调包括四种:目标-动作对(target-action)、辅助对象(helper objects)、通知(notification)以及Block对象(Blocks);
四种回调
- 目标-动作对(target-action): 在程序开始等待之前,要求“当事件发生时,向指定的对像发送某个特定的消息”,这里接受消息的对象是目标(target),消息的选择器(selector)是动作(action);
- 辅助对象(Helper Objects): 在程序开始等待前,要求“当事件发生时,向遵守相应协议的辅助对象发送消息”。Delegate(委托对象) 和 DataSource(数据源)是我们常见的辅助对象;
- 通知(Notification): 某个对象正在等待某些特定的通知。当其中的某个通知出现时,向指定的对象发送特定的消息。当事件发生时,相关的对象会向通知中心发布通知,然后再有通知中心将通知转发给正在等待该通知的对象;
- Block对象(Blocks): 在程序开始等待前,声明一个Block对象,当事件发生时,执行这段Block对象;
循环对象
在将案例之前让我们先来了解一下循环对象(下文有用上),IOS有个循环类叫NSRunLoop,这个实例一直会持续等待着,当有特定的事件发生的时候,就会给相应的对象发送对应的消息,这个实例会在特定事件发生时出发回调,类似Android的handler消息机制,用法如下:
[[NSRunLoop currentRunLoop] run];
目标-动作对
首先先创建一个Logger类,代码如下:
Logger.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Logger : NSObject
@property (nonatomic) NSDate *LastTime;
-(NSString *)LastTimeString;
-(void)UpdateLastTime:(NSTimer *)t;
@end
NS_ASSUME_NONNULL_END
然后在Logger.m文件中实现对应的方法:
#import "Logger.h"
@implementation Logger
-(NSString *)LastTimeString{
static NSDateFormatter *dateFormatter = nil;
if (!dateFormatter) {
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
NSLog(@"create dateFormatter");
}
return [dateFormatter stringFromDate:self.LastTime];
}
-(void)UpdateLastTime:(NSTimer *)t{
NSDate *now = [NSDate date];
[self setLastTime:now];
NSLog(@"Just set time to %@",self.LastTimeString);
}
@end
在main.m文件中,创建一个Logger实例,让它成为NSTimer的目标,把动作设置为updateLa stTime,mian文件如下:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Logger.h"
#import <Foundation/Foundation.h>
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
Logger *logger = [[Logger alloc] init];
//这里用了@selector语句来传递动作消息的名称给相应的方法,要传递实参,不能只传递方法名
__unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger selector:@selector(UpdateLastTime:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
运行结果如下所示:
每隔2秒就会像对应的目标发送指定动作的消息,当收到回调之后就会打印出相关日志;
辅助对象
辅助对象的话我们用一个异步机制来讲解。
我们使用NSURLConnection从服务器获取数据时,通常都是通过异步方式完成的,NSURLConnection通常不会一次就发送全部数据,而是多次的发送块状数据。也就是说,我们需要在程序中不断的响应接受数据的事件。因此,我们需要一个对象来帮助NSURLConnection完成这些操作。
因为要完成NSURLConnection的操作,所以Logger当中要实现它的协议,在这个简单的例子中,我们只需要实现NSURLConnection的三个协议方法就好,辅助对象可以根据协议实现相应的方法,以下是该协议所声明的部分方法(在main中创建一个NSURL和NSURLRequest对象,然后再创建一个NSConnection对象,把Logger设置为它的委托对象):
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Logger.h"
#import <Foundation/Foundation.h>
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
Logger *logger = [[Logger alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:logger selector:@selector(zoneChanhe) name:NSSystemTimeZoneDidChangeNotification object:nil]
NSURL *url = [NSURL URLWithString:@"https://www.gutenberg.org/cache/epub/205/pg205.txt"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
__unused NSURLConnection *fetchConn = [[NSURLConnection alloc] initWithRequest:request delegate:logger startImmediately:YES];
[[NSRunLoop currentRunLoop] run];
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
然后再Logger中实现响应特定事件的回调方法,Logger.h:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Logger : NSObject
<NSURLConnectionDelegate,NSURLConnectionDataDelegate>
{
//Data用来保存数据,当一块块数据发送过来的时候,将它们添加到NSMutableData中;
NSMutableData *_incomingDate;
}
//将logger设置为了NSURLConnection的辅助对象,因此网络下载相关的信息都会在辅助对象logger中进行响应
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
-(void)connectionDidFinishLoading:(NSURLConnection *)connection;
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
@end
NS_ASSUME_NONNULL_END
Logger.m
#import "Logger.h"
@implementation Logger
//收到一定的字节数的时候会被调用
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSLog(@"received %lu bytes", [data length]);
//如果NSMutableData不存在则创建一个
if(!_incomingDate){
_incomingDate = [[NSMutableData alloc] init];
}
[_incomingDate appendData:data];
}
//最后的数据处理完成之后会被调用
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(@"got it all!");
//将NSData数据以utf-8解码为字符串
NSString *string = [[NSString alloc] initWithData:_incomingDate encoding:NSUTF8StringEncoding];
_incomingDate = nil;
NSLog(@"string has %lu characters", [string length]);
}
//获取数据失败的时候会被调用
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(@"connectiong failed : %@",[error localizedDescription]);
}
-(void)zoneChange:(NSNotification *)note{
NSLog(@"the system time zone has change!!!");
}
@end
构建程序并运行,程序会陆续收到来自web服务器的数据。当数据全部接受完成以后,也会收到对应的委托消息;
当要向一个对象发送一个回调的时候,选择使用目标-动作对来实现;当要向一个对象发送多个消息的时候,选择辅助对象来实现;
通知
当改变系统的时区设置时,程序中的很多对象都可以知道这一变化。之所以能够实现,是因为这些对象都可以通过通知中心将自己注册成为观察者Observer。当系统时区发生改变的时候,会像通知中心发布NSSystemTimeZondeDidChangeNotification通知,然后通知中心将该通知转发给所有注册了该Name的观察者。
类似,我们将logger注册为观察者,当有人改变时区的时候,该观察者就会收到对应的通知:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Logger.h"
#import <Foundation/Foundation.h>
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
Logger *logger = [[Logger alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:logger selector:@selector(zoneChanhe) name:NSSystemTimeZoneDidChangeNotification object:nil]
[[NSRunLoop currentRunLoop] run];
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
在Logger.m里面实现如下方法:
-(void)zoneChange:(NSNotification *)note{
NSLog(@"the system time zone has change!!!");
}
当Mac的时区发生改变的时候,便会收到对应的通知;
- 通知中心不拥有观察者,如果将某个对象注册为观察者,那么通常应该在释放该对象的时候将其移出通知中心;
- 对象不拥有委托对象或数据源对象,如果某个新创建的对象是另一个对象的委托对象或数据源对象,那么该对象应该在其dealloc方法中取消相对应的关联;
- 对象不拥有目标,如果某个新创建的对象是另一个对象的目标,那么该对象应该在其dealloc方法中将其目标指针置为nil;
Block
Block看上去和c类似,都是在一个花括号内的一套指令,但是它没有函数名,对应的只有一个 ^ 符号,^表示这段代码是一个block对象,block对象可以有返回值;
^(int a,int b){
int c = a + b;
return c;
}
后续…