oc基础iOS程序员

iOS回调方法总结(超详细总结)

2017-09-14  本文已影响496人  LeeLom

声明:未经许可,禁止转载。

整个项目的Gihub地址:https://github.com/LeeLom/CallBackDemo


回调(callback)就是将一段可执行的代码和一个特定的事件绑定起来,当特定的时间发生时,就会执行这段代码。
在Objective-C中,有四种途径可以试下回调:

iOS回调方式.png

在iOS开发中最常使用的就是辅助对象和Blocks. 下面将会通过四个例子来看一下这四种回调方式都是怎么实现的。

目标-动作对 (Target-Action)


这个程序每隔2秒,NSTimer就会像其目标发送指定的动作消息。此外,在创建一个Logger类,这个类的实例将被设置为NSTimer对象的目标。

//1. 目标-动作对
// 创建一个Logger的实例logger
Logger *logger = [[Logger alloc]init];
// 每隔2秒,NSTimer对象会向其Target对象logger,发送指定的消息updateLastTime:
__unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0
                                                  target:logger
                                                selector:@selector(updateLastTime:)
                                                userInfo:nil
                                                 repeats:YES];

输出结果:

WX20170913-105013@2x.png

Target: logger
Action: logger对象的updateLastTime方法

// 创建一个按钮
UIButton *btn = [[UIButton alloc]init];
// 为按钮添加事件
[btn addTarget:self
        action:@selector(btnClick)
forControlEvents:UIControlEventTouchUpInside];
- (void)btnClick {
    NSLog(@"按钮点击事件");
}

从这种目标-动作的回调方式我们可以发现,NSTimer它只负责一件事情updateLastTime,btn它只负责btnClick。也就是说,对于只做一件事情的对象,我们可以是使用目标动作对。

辅助对象 (Delegate/Datasource)


self.tableView.delegate = self;
self.tableView.dataSource = self;

上面的两行代码,我们在某个ViewController当中使用的话,意味着我们将ViewController设置成为了tableView的辅助对象。当tableView需要更新或者是响应某些特定的事件时,就会向该ViewController发送消息。
具体发送哪些消息就看我们怎么实现的了,比如我们点击某行需要响应点击事件时,我们就需要实现下面这个方法:

// Called after the user changes the selection.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;

关于UITableView的使用网上有大量的资料,在这里就不再重复了。我们在这个部分只要明确一点,UITableView它的回调方式是通过这种委托对象来实现的,而委托的对象通常是使用它的ViewController,我们需要委托对象为UITableView完成什么事情,就需要在委托对象ViewController中实现相应的协议Protocol(也即delegatedatasource)。

我们使用NSURLConnection从服务器获取数据时,通常都是通过异步方式完成的,NSURLConnection通常不会一次就发送全部数据,而是多次的发送块状数据。也就是说,我们需要在程序中不断的响应接受数据的事件。

因此,我们需要一个对象来帮助NSURLConnection完成这些操作。继续前面的例子,我们使用Logger类的实例来完成。因为要完成NSURLConnection的操作,所以Logger当中要实现它的协议,在这个简单的例子中,我们只需要实现NSURLConnection的三个协议方法就好。

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

PS: 其中1.2是NSURLConnectionDataDelegate, 第三条是NSURLConnectionDelegate.

//2. 辅助对象
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];

在这里,我们将logger设置为了NSURLConnection的辅助对象,因此网络下载相关的信息都会在辅助对象logger中进行响应。
输出的结果如下:

WX20170913-113155@2x.png
从上面的UITableViewNSURLConnection的例子中我们可以发现,辅助对象和目标动作对的实现逻辑非常相似,如果吧目标理解为辅助对象,动作理解为协议的话,二者几乎是一一对应的。但是二者的区别主要在于:当要向一个对象发送多个回调的时候,通常选择符合相应协议的辅助对象;如果要向一个对象发送一个回调是,通常使用目标动作对。

辅助对象也常被成为委托对象delegate和数据源datasource

通知 Notifications


上面所说的目标动作对和辅助对象都是向一个对象发送消息,如果要向多个对象发送消息,那么我们就需要使用通知这种方式了。

同样的,我们继续在Logger这个类中继续进行操作。这次,我们Logger的实例注册为观察者,让它能够在系统的失去发生变化的时候收到相应的通知。

[[NSNotificationCenter defaultCenter]addObserver:logger
                                       selector:@selector(zoneChange:)
                                           name:NSSystemTimeZoneDidChangeNotification
                                         object:nil];
return YES;
- (void)zoneChange:(NSNotification *)note {
   NSLog(@"The system time zone has changed!");
}

(这个例子需要在My Mac中执行,才能看到效果)

WX20170913-115850.png
    NotificationA *notiA = [[NotificationA alloc]init];
[[NSNotificationCenter defaultCenter] addObserver:notiA
                                         selector:@selector(receiveNotification)
                                             name:@"receiveNotification"
                                           object:nil];
NotificationB *notiB = [[NotificationB alloc]init];
[[NSNotificationCenter defaultCenter] addObserver:notiB
                                         selector:@selector(receiveNotification)
                                             name:@"receiveNotification"
                                           object:nil];
#import "NotificationA.h"
@implementation NotificationA
- (void)receiveNotification {
NSLog(@"Notification A receive this notification");
}
@end
    #import "NotificationB.h"
@implementation NotificationB
- (void)receiveNotification {
    NSLog(@"Notification B receive this notification");
}
@end
[[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotification"
                                                object:nil];

这样,notiA和notiB都会接收到这个通知,并且做出响应,如图:


image.png

因此,在程序中如果需要出发多个(其他对象中)的回调对象时,可以使用通知的方式来完成。

Blocks


上述的委托机制(Delegate)和通过机制(notification)已经能够很好的帮助程序在特定事件发生时调用制定的方法。但是他们都存在一个缺点:回调的设置代码和回调方法的具体实现通常都间隔很远,甚至出现在不同的文件中。
为了克服这个确定,我们可以通过Block对象,将回调相关的代码写在同一个代码段中。

在梳理Block回调之前,我们先要明确一点:
谁要传值谁就定义含有参数的Block, 谁要调用谁就执行这个Block
明确了这一点后,根据我们例子1中的需求,我们需要将BViewController中用户的输入传递给AViewController。因此BViewController需要定义一个Block, 然后在AViewController中进行相应的操作。
BViewController.h文件中:定义CallBackBlock

#import <UIKit/UIKit.h>
typedef void(^CallBackBlock)(NSString *text); // 定义带有参数text的block
@interface BViewController : UIViewController
@property (nonatomic, copy)CallBackBlock callBackBlock;
@end

BViewController.m文件中:将textFiled中输入的字符串传递给Block

- (IBAction)popToA:(id)sender {
   NSLog(@"text:%@",_textField.text);
   self.callBackBlock(_textField.text);
   [self.navigationController popToRootViewControllerAnimated:YES];
}

AViewController.m文件中:对BViewController传递过来的字符串进行显示

- (IBAction)getValueFromB:(id)sender {
   BViewController *vc = [[BViewController alloc]init];
   __weak AViewController *weakSelf = self; //避免循环引用
   vc.callBackBlock = ^(NSString *text) {
       weakSelf.textLabel.text = text;
   };
   [self.navigationController pushViewController:vc animated:YES];
}

BViewController.m文件中:

```
// 另一种实现方式
- (void)passBlock:(CallBackBlock)block {
    block(@"这是另外一种方式的...");
}
```

AViewController.m文件中

```
- (IBAction)anotherButtonClick:(id)sender {
    BViewController *vc = [[BViewController alloc]init];
    __weak AViewController *weakSelf = self; //避免循环引用
    [vc passBlock:^(NSString *text) {
        weakSelf.anotherTextLabel.text = text;
    }];
}
```

在这个例子中,调用B的方法,将Block中包裹的变量传递给A,在A中对Block进行操作处理这个变量。

其他注意事项


无论哪种类型的回调,都应该注意避免强引用循环。常见的强引用循环的发生情况,创建的对象和回调对象之间相互拥有,导致两个对象都无法释放。
因此在构建回调方法的时候,应该遵守以下规则:

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
BViewController *vc = [[BViewController alloc]init];
__weak AViewController *weakSelf = self; //避免循环引用
[vc passBlock:^(NSString *text) {
    weakSelf.anotherTextLabel.text = text;
}];
BViewController *vc = [[BViewController alloc]init];
__weak AViewController *weakSelf = self; //避免循环引用
[vc passBlock:^(NSString *text) {
    weakSelf.anotherTextLabel.text = text;
    AViewController *innerSelf = weakSelf; //局部强引用
    NSLog(@"假如AViewController 存在name这个属性的话,它的值为:%@", innderSelf.name);
}];

参考资料


上一篇下一篇

猜你喜欢

热点阅读