iOS回調(diào)方法總結(jié)(超詳細(xì)總結(jié))

聲明:未經(jīng)許可,禁止轉(zhuǎn)載。

整個(gè)項(xiàng)目的Gihub地址:https://github.com/LeeLom/CallBackDemo


回調(diào)(callback)就是將一段可執(zhí)行的代碼和一個(gè)特定的事件綁定起來(lái),當(dāng)特定的時(shí)間發(fā)生時(shí),就會(huì)執(zhí)行這段代碼。
在Objective-C中,有四種途徑可以試下回調(diào):

iOS回調(diào)方式.png
  • 目標(biāo)-動(dòng)作對(duì)(Targe-Action): 在程序開始等待前,要求“當(dāng)事件發(fā)生時(shí),向指定的對(duì)象發(fā)送某個(gè)特定的消息”。這里接受消息的對(duì)象是目標(biāo)(target),消息的選擇器(selector)是動(dòng)作(action).
  • 輔助對(duì)象(Helper Objects): 在程序開始等待前,要求“當(dāng)事件發(fā)生時(shí),向遵守相應(yīng)協(xié)議的輔助對(duì)象發(fā)送消息”。Delegate 和 DataSource是我們常見(jiàn)的輔助對(duì)象
  • 通知(Notification): 某個(gè)對(duì)象正在等待某些特定的通知。當(dāng)其中的某個(gè)通知出現(xiàn)時(shí),向指定的對(duì)象發(fā)送特定的消息。當(dāng)事件發(fā)生時(shí),相關(guān)的對(duì)象會(huì)向通知中心發(fā)布通知,然后再有通知中心將通知轉(zhuǎn)發(fā)給正在等待該通知的對(duì)象
  • Blocks: 在程序開始等待前,聲明一個(gè)Block對(duì)象,當(dāng)事件發(fā)生時(shí),執(zhí)行這段Block對(duì)象。

在iOS開發(fā)中最常使用的就是輔助對(duì)象和Blocks. 下面將會(huì)通過(guò)四個(gè)例子來(lái)看一下這四種回調(diào)方式都是怎么實(shí)現(xiàn)的。

目標(biāo)-動(dòng)作對(duì) (Target-Action)


  • 創(chuàng)建一個(gè)NSRunLoop對(duì)象和NSTimer對(duì)象的程序。

這個(gè)程序每隔2秒,NSTimer就會(huì)像其目標(biāo)發(fā)送指定的動(dòng)作消息。此外,在創(chuàng)建一個(gè)Logger類,這個(gè)類的實(shí)例將被設(shè)置為NSTimer對(duì)象的目標(biāo)。

//1. 目標(biāo)-動(dòng)作對(duì)
// 創(chuàng)建一個(gè)Logger的實(shí)例logger
Logger *logger = [[Logger alloc]init];
// 每隔2秒,NSTimer對(duì)象會(huì)向其Target對(duì)象logger,發(fā)送指定的消息updateLastTime:
__unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0
                                                  target:logger
                                                selector:@selector(updateLastTime:)
                                                userInfo:nil
                                                 repeats:YES];

輸出結(jié)果:

WX20170913-105013@2x.png

Target: logger
Action: logger對(duì)象的updateLastTime方法

  • 在程序中常見(jiàn)的按鈕點(diǎn)擊事件也是這種類型。首先,我們用代碼創(chuàng)建了一個(gè)按鈕btn,然后為這個(gè)按鈕添加他的目標(biāo)為當(dāng)前的AppDelegate(這里僅僅是為了舉例,一般我們都是用在ViewController當(dāng)中),對(duì)應(yīng)的Action為:btnClick。
// 創(chuàng)建一個(gè)按鈕
UIButton *btn = [[UIButton alloc]init];
// 為按鈕添加事件
[btn addTarget:self
        action:@selector(btnClick)
forControlEvents:UIControlEventTouchUpInside];
- (void)btnClick {
    NSLog(@"按鈕點(diǎn)擊事件");
}

從這種目標(biāo)-動(dòng)作的回調(diào)方式我們可以發(fā)現(xiàn),NSTimer它只負(fù)責(zé)一件事情updateLastTime,btn它只負(fù)責(zé)btnClick。也就是說(shuō),對(duì)于只做一件事情的對(duì)象,我們可以是使用目標(biāo)動(dòng)作對(duì)。

輔助對(duì)象 (Delegate/Datasource)


  • 輔助對(duì)象是在iOS開發(fā)中相當(dāng)常見(jiàn)的。比如我們經(jīng)常使用的UITableView這個(gè)空間,相信大家都使用過(guò)其中的UITableViewDelegate以及UITableViewDataSource
self.tableView.delegate = self;
self.tableView.dataSource = self;

上面的兩行代碼,我們?cè)谀硞€(gè)ViewController當(dāng)中使用的話,意味著我們將ViewController設(shè)置成為了tableView的輔助對(duì)象。當(dāng)tableView需要更新或者是響應(yīng)某些特定的事件時(shí),就會(huì)向該ViewController發(fā)送消息。
具體發(fā)送哪些消息就看我們?cè)趺磳?shí)現(xiàn)的了,比如我們點(diǎn)擊某行需要響應(yīng)點(diǎn)擊事件時(shí),我們就需要實(shí)現(xiàn)下面這個(gè)方法:

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

關(guān)于UITableView的使用網(wǎng)上有大量的資料,在這里就不再重復(fù)了。我們?cè)谶@個(gè)部分只要明確一點(diǎn),UITableView它的回調(diào)方式是通過(guò)這種委托對(duì)象來(lái)實(shí)現(xiàn)的,而委托的對(duì)象通常是使用它的ViewController,我們需要委托對(duì)象為UITableView完成什么事情,就需要在委托對(duì)象ViewController中實(shí)現(xiàn)相應(yīng)的協(xié)議Protocol(也即delegatedatasource)。

  • 下面,我們通過(guò)一個(gè)網(wǎng)絡(luò)異步下載的例子,進(jìn)一步加深了解這種輔助對(duì)象的回調(diào)。

我們使用NSURLConnection從服務(wù)器獲取數(shù)據(jù)時(shí),通常都是通過(guò)異步方式完成的,NSURLConnection通常不會(huì)一次就發(fā)送全部數(shù)據(jù),而是多次的發(fā)送塊狀數(shù)據(jù)。也就是說(shuō),我們需要在程序中不斷的響應(yīng)接受數(shù)據(jù)的事件。

因此,我們需要一個(gè)對(duì)象來(lái)幫助NSURLConnection完成這些操作。繼續(xù)前面的例子,我們使用Logger類的實(shí)例來(lái)完成。因?yàn)橐瓿?code>NSURLConnection的操作,所以Logger當(dāng)中要實(shí)現(xiàn)它的協(xié)議,在這個(gè)簡(jiǎn)單的例子中,我們只需要實(shí)現(xiàn)NSURLConnection的三個(gè)協(xié)議方法就好。

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

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

//2. 輔助對(duì)象
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設(shè)置為了NSURLConnection的輔助對(duì)象,因此網(wǎng)絡(luò)下載相關(guān)的信息都會(huì)在輔助對(duì)象logger中進(jìn)行響應(yīng)。
輸出的結(jié)果如下:

WX20170913-113155@2x.png

從上面的UITableViewNSURLConnection的例子中我們可以發(fā)現(xiàn),輔助對(duì)象和目標(biāo)動(dòng)作對(duì)的實(shí)現(xiàn)邏輯非常相似,如果吧目標(biāo)理解為輔助對(duì)象,動(dòng)作理解為協(xié)議的話,二者幾乎是一一對(duì)應(yīng)的。但是二者的區(qū)別主要在于:當(dāng)要向一個(gè)對(duì)象發(fā)送多個(gè)回調(diào)的時(shí)候,通常選擇符合相應(yīng)協(xié)議的輔助對(duì)象;如果要向一個(gè)對(duì)象發(fā)送一個(gè)回調(diào)是,通常使用目標(biāo)動(dòng)作對(duì)。

輔助對(duì)象也常被成為委托對(duì)象delegate和數(shù)據(jù)源datasource。

通知 Notifications


上面所說(shuō)的目標(biāo)動(dòng)作對(duì)和輔助對(duì)象都是向一個(gè)對(duì)象發(fā)送消息,如果要向多個(gè)對(duì)象發(fā)送消息,那么我們就需要使用通知這種方式了。

  • 例子1: 我們使用電腦的時(shí)候發(fā)現(xiàn),當(dāng)改變系統(tǒng)的失去設(shè)置時(shí),程序中的很多對(duì)象都可以知道這一變化。之所以能夠?qū)崿F(xiàn),是因?yàn)檫@些對(duì)象都可以通過(guò)通知中心將自己注冊(cè)成為觀察者Observer。當(dāng)系統(tǒng)是時(shí)區(qū)發(fā)生改變的時(shí)候,會(huì)像通知中心發(fā)布NSSystemTimeZondeDidChangeNotification通知,然后通知中心將該通知轉(zhuǎn)發(fā)給所有注冊(cè)了該Name的觀察者。

同樣的,我們繼續(xù)在Logger這個(gè)類中繼續(xù)進(jìn)行操作。這次,我們Logger的實(shí)例注冊(cè)為觀察者,讓它能夠在系統(tǒng)的失去發(fā)生變化的時(shí)候收到相應(yīng)的通知。

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

(這個(gè)例子需要在My Mac中執(zhí)行,才能看到效果)

WX20170913-115850.png

  • 例子2:在這個(gè)例子中,我們新建了兩個(gè)對(duì)象notiAnotiB來(lái)接收同一個(gè)名為reveiveNotification的通知,并且各自都會(huì)做出相應(yīng)的響應(yīng)。
    具體的步驟是:
    • 分別新建notiAnotiB,并且都將二者注冊(cè)為接收reveiveNotification通知的觀察者
    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
  • 通知中心發(fā)出名為reveiveNotification的通知的通知。
[[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotification"
                                                object:nil];

這樣,notiA和notiB都會(huì)接收到這個(gè)通知,并且做出響應(yīng),如圖:


image.png

因此,在程序中如果需要出發(fā)多個(gè)(其他對(duì)象中)的回調(diào)對(duì)象時(shí),可以使用通知的方式來(lái)完成。

Blocks


上述的委托機(jī)制(Delegate)和通過(guò)機(jī)制(notification)已經(jīng)能夠很好的幫助程序在特定事件發(fā)生時(shí)調(diào)用制定的方法。但是他們都存在一個(gè)缺點(diǎn):回調(diào)的設(shè)置代碼和回調(diào)方法的具體實(shí)現(xiàn)通常都間隔很遠(yuǎn),甚至出現(xiàn)在不同的文件中。
為了克服這個(gè)確定,我們可以通過(guò)Block對(duì)象,將回調(diào)相關(guān)的代碼寫在同一個(gè)代碼段中。

  • 例子1. 我們?cè)趦蓚€(gè)ViewController中進(jìn)行傳值。 BViewController中有一個(gè)UITextField,用戶輸入相應(yīng)的值,我們?cè)?code>AViewController中進(jìn)行顯示。

在梳理Block回調(diào)之前,我們先要明確一點(diǎn):
誰(shuí)要傳值誰(shuí)就定義含有參數(shù)的Block, 誰(shuí)要調(diào)用誰(shuí)就執(zhí)行這個(gè)Block
明確了這一點(diǎn)后,根據(jù)我們例子1中的需求,我們需要將BViewController中用戶的輸入傳遞給AViewController。因此BViewController需要定義一個(gè)Block, 然后在AViewController中進(jìn)行相應(yīng)的操作。
BViewController.h文件中:定義CallBackBlock

#import <UIKit/UIKit.h>
typedef void(^CallBackBlock)(NSString *text); // 定義帶有參數(shù)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文件中:對(duì)BViewController傳遞過(guò)來(lái)的字符串進(jìn)行顯示

- (IBAction)getValueFromB:(id)sender {
   BViewController *vc = [[BViewController alloc]init];
   __weak AViewController *weakSelf = self; //避免循環(huán)引用
   vc.callBackBlock = ^(NSString *text) {
       weakSelf.textLabel.text = text;
   };
   [self.navigationController pushViewController:vc animated:YES];
}
  • 例子2:功能同例子1.
    其實(shí)剛看例子1的時(shí)候花了一些時(shí)間,總覺(jué)得哪里怪怪的,其實(shí)Block回調(diào)一種更常見(jiàn)的構(gòu)建方法如下。
    BViewController.h文件中:

    // 另一種Block回調(diào)的實(shí)現(xiàn)方式
    - (void)passBlock:(CallBackBlock)block;
    

BViewController.m文件中:

```
// 另一種實(shí)現(xiàn)方式
- (void)passBlock:(CallBackBlock)block {
    block(@"這是另外一種方式的...");
}
```

AViewController.m文件中

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

在這個(gè)例子中,調(diào)用B的方法,將Block中包裹的變量傳遞給A,在A中對(duì)Block進(jìn)行操作處理這個(gè)變量。

其他注意事項(xiàng)


無(wú)論哪種類型的回調(diào),都應(yīng)該注意避免強(qiáng)引用循環(huán)。常見(jiàn)的強(qiáng)引用循環(huán)的發(fā)生情況,創(chuàng)建的對(duì)象和回調(diào)對(duì)象之間相互擁有,導(dǎo)致兩個(gè)對(duì)象都無(wú)法釋放。
因此在構(gòu)建回調(diào)方法的時(shí)候,應(yīng)該遵守以下規(guī)則:

  • 通知中心不擁有觀察者。如果某個(gè)對(duì)象注冊(cè)成為觀察者,那么通常應(yīng)該在釋放該對(duì)象時(shí)將其移出通知中心。
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
  • 對(duì)象不擁有委托對(duì)象和數(shù)據(jù)源方法。如果某個(gè)新創(chuàng)建的對(duì)象是一個(gè)對(duì)象的委托對(duì)象或數(shù)據(jù)源方法,那么該對(duì)象應(yīng)該在其dealloc方法中取消相應(yīng)的關(guān)聯(lián)。
  • 對(duì)象不擁有目標(biāo)。如果某個(gè)新創(chuàng)建的對(duì)象是另一個(gè)對(duì)象的目標(biāo),那么該對(duì)象應(yīng)該再起dealloc方法中將相應(yīng)的目標(biāo)指針賦為nil.
  • Block對(duì)象中使用self, 應(yīng)該使用weak指針避免強(qiáng)引用循環(huán)。
BViewController *vc = [[BViewController alloc]init];
__weak AViewController *weakSelf = self; //避免循環(huán)引用
[vc passBlock:^(NSString *text) {
    weakSelf.anotherTextLabel.text = text;
}];
  • Block對(duì)象中使用實(shí)例變量時(shí),應(yīng)該使用局部強(qiáng)引用。不要直接存取實(shí)例變量,使用存取方法。
BViewController *vc = [[BViewController alloc]init];
__weak AViewController *weakSelf = self; //避免循環(huán)引用
[vc passBlock:^(NSString *text) {
    weakSelf.anotherTextLabel.text = text;
    AViewController *innerSelf = weakSelf; //局部強(qiáng)引用
    NSLog(@"假如AViewController 存在name這個(gè)屬性的話,它的值為:%@", innderSelf.name);
}];

參考資料


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評(píng)論 19 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,324評(píng)論 25 708
  • iOS網(wǎng)絡(luò)架構(gòu)討論梳理整理中。。。 其實(shí)如果沒(méi)有APIManager這一層是沒(méi)法使用delegate的,畢竟多個(gè)單...
    yhtang閱讀 5,494評(píng)論 1 23
  • ?什么是詢價(jià)單: 詢價(jià)單是采購(gòu)商在阿里巴巴上發(fā)布的采購(gòu)需求,包含采購(gòu)產(chǎn)品的圖文說(shuō)明、采購(gòu)量、采購(gòu)商的聯(lián)系方式、收獲...
    Ali陳李港閱讀 746評(píng)論 0 0
  • 文||木木君 圖||網(wǎng)絡(luò) 相逢是緣 雙魚小姐來(lái)自呼倫貝爾,是我的大學(xué)室友兼閨蜜,典型的東北大妞脾氣。 雙魚小姐讀...
    時(shí)糖Tsugar911閱讀 2,098評(píng)論 0 3

友情鏈接更多精彩內(nèi)容