block和delegate是iOS開發者常常用到的技術,也經常出如今各類面試題裏,你常常聽到他們之間的對比。html
個人態度是每一個成熟的技術並無明顯的優劣,不該該用誰好誰劣來評判他們,而應該看誰更適合應用場景,在合適的場合選擇合適的技術。ios
本篇文章將討論在 網絡層調用和回調 這個場景下的技術選擇。git
本文涉及代碼github
一個常見的Block回調,一般是業務代碼調用請求,而後在回調中得到返回的數據,而後執行業務邏輯,以下:面試
// 業務層代碼
- (void)blockDemo {
[self.service requestWithParms:nil WithResult:^(id data, NSError *error) {
// 處理業務邏輯代碼
// ...
NSLog(@"Result from block:%@", data);
}];
}
複製代碼
之因此會考慮這個問題,是由於用戶使用時經常會剛進入一個頁面,就馬上點返回,此時網絡請求剛發出去,數據返回有可能還沒回來,那麼這個網絡請求會怎麼樣呢?json
當前頁面controller可以被pop出棧,釋放掉嗎?畢竟網絡請求還沒結束數組
咱們一一來驗證安全
如何驗證內存釋放已經釋放,很簡單,首先我寫的網絡請求並非真的網絡請求,他只會延時5s返回一個假的數據,用來方便模擬網絡請求bash
- (void)requestWithParms:(NSDictionary *)parms WithResult:(ResultBlock)result {
int delay = NET_DELAY; // 默認是5s,能夠傳參數改變
if ([parms valueForKey:@"delayTime"] != nil) {
delay = (int)[parms valueForKey:@"delayTime"];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSString *resultData = @"This is a Mock Data!!";
NSLog(@"Network Finish:%@",resultData);
if (result) {
result(resultData, nil);
}
});
}
複製代碼
再在viewcontroller的dealloc
方法裏打印log,就能知道dealloc
是否被調用,若是調用說明能夠釋放網絡
- (void)dealloc {
NSLog(@"NextPageViewController has been dealloc!")
}
複製代碼
這樣驗證起來就是很簡單,只須要執行blockDemo
方法,而後馬上點返回,退出當前vc,等5秒後若是看結果
結果是vc能夠釋放的
2018-03-25 19:36:07.345523+0800 NetworkCallback[4580:3600834] NextPageViewController has been dealloc!
複製代碼
咱們再進一步想一想,Block有個最大的特色是能夠訪問當前的做用域,咱們隨便建立一個數組,重複上面操做,是否可以銷燬
- (void)blockDemo {
NSArray *outsideArray = @[@1, @2, @3];
[self.service requestWithParms:nil WithResult:^(id data, NSError *error) {
// 處理業務邏輯
// ...
NSLog(@"Result from block:%@", data);
NSLog(@"outsideArray :%@", outsideArray);
}];
}
複製代碼
打印結果:
2018-03-25 19:55:40.997535+0800 NetworkCallback[4970:3641450] NextPageViewController has been dealloc!
2018-03-25 19:55:44.831721+0800 NetworkCallback[4970:3641450] outsideArray :(
1,
2,
3
)
複製代碼
神奇不?!
注意,vc先銷燬了,可是5s後,這個臨時變量居然尚未銷燬。那麼這個變量存儲在哪裏呢?留個懸念
考慮一下,若是你在Block代碼裏訪問了一個超大的文件,這個文件必然是保存內存的,而後此時你趕上了網絡慢,接口很久沒有返回,那麼這個超大的文件就會一直佔用內存
繼續,若是我在Block中訪問self呢?此時的self就是當前的controller,這時候能夠銷燬嗎?
- (void)blockDemo {
NSArray *outsideArray = @[@1, @2, @3];
[self.service requestWithParms:nil WithResult:^(id data, NSError *error) {
// 處理業務邏輯
// ...
NSLog(@"Result from block:%@", data);
NSLog(@"outsideArray :%@", outsideArray);
NSLog(@"self :%@", self);
}];
}
複製代碼
打印結果:
2018-03-25 20:04:21.012135+0800 NetworkCallback[5224:3659292] Network Finish:This is a Mock Data!!
2018-03-25 20:04:21.012476+0800 NetworkCallback[5224:3659292] Result from block:This is a Mock Data!!
2018-03-25 20:04:21.013186+0800 NetworkCallback[5224:3659292] outsideArray :(
1,
2,
3
)
2018-03-25 20:04:21.013675+0800 NetworkCallback[5224:3659292] self :<NextPageViewController: 0x7f993662cf20>
2018-03-25 20:04:21.013858+0800 NetworkCallback[5224:3659292] NextPageViewController has been dealloc!
複製代碼
看到了嗎?dealloc是最後打印出來的,也就是說Block不返回,controller就釋放不了了!
看起來是在Block內訪問誰,誰就沒法釋放啊!
有人會想這是否是就是循環引用呢?
請你們回憶一下:Block循環引用是self強引用Block,Block裏面再強引用self,這裏Block確實強引用了self,可是self並無強引用Block,這個Block是一個參數傳給了NetService,跟self並沒有關係
並且是循環引用的話,那麼vc會一直釋放不掉,但看上面的log,實際上是能夠釋放的,只是釋放的時機被延後了
可是,不管如何,咱們試試換成 weakself
會怎麼樣呢? 打印結果:
2018-03-25 20:07:10.944557+0800 NetworkCallback[5294:3665537] NextPageViewController has been dealloc!
2018-03-25 20:07:15.228961+0800 NetworkCallback[5294:3665537] Network Finish:This is a Mock Data!!
2018-03-25 20:07:15.230836+0800 NetworkCallback[5294:3665537] Result from block:This is a Mock Data!!
2018-03-25 20:07:15.231074+0800 NetworkCallback[5294:3665537] outsideArray :(
1,
2,
3
)
2018-03-25 20:07:15.231190+0800 NetworkCallback[5294:3665537] weakSelf :(null)
複製代碼
沒問題,果真換成weakself就解決了
爲何在Block內訪問誰,誰就沒法釋放呢?爲何用weakself就解決了呢?
Block看起來像一個函數,其實在objectice-c中,它是個對象,之因此Block能夠捕獲外部變量,正是由於它是個對象,他有本身的屬性,他用屬性強引用了外部變量,致使外部變量(就是上面的self和outsideArray)的引用計數不爲0,也就不能釋放了
當Block中訪問weakself的時候,強引用並無指向self,而是指向weakself,因此self能夠被釋放
weakself
,來防止vc被持有,而延遲釋放這是基於上面的考慮,咱們已經知道要用weakself
來保證controller被及時釋放,也能夠在上面log中看到weakself變成了nil,此時有可能致使crash,由於咱們正在操做一個nil對象 想象一個業務場景:分頁請求,你拉取了前面幾頁,好比page=3,而後去拉下一頁數據,此時網絡請求還沒有返回,用戶就退出當前頁面,此時
其實第一個問題上面的log已經回答了,log之因此被打印出來,其實就是Block中的代碼被執行了嘛。
也就是說即便controller已經銷燬,Block中的代碼仍是會被執行
第二個問題,執行了會有什麼危險
- (void)blockDemo {
__weak typeof(self) weakSelf = self;
NSArray *outsideArray = @[@1, @2, @3];
[self.service requestWithParms:nil WithResult:^(id data, NSError *error) {
// 處理業務邏輯
// ...
[data addObject:weakSelf.pageArray]; // weakSelf是nil
}];
}
複製代碼
所以,你必須當心翼翼,寫上的保護代碼
- (void)blockDemo {
__weak typeof(self) weakSelf = self;
NSArray *outsideArray = @[@1, @2, @3];
[self.service requestWithParms:nil WithResult:^(id data, NSError *error) {
// 保護代碼
if (weakSelf == nil) {
return;
}
// 處理業務邏輯
// ...
}];
}
複製代碼
通過上面的驗證,看起來好像Block有挺多麻煩了,那麼delegate怎麼樣呢?咱們也來試一試
首先是模擬網絡請求,而後經過delegate回調
- (void)requestWithParms:(NSDictionary *)parms {
int delay = NET_DELAY;
if ([parms valueForKey:@"delayTime"] != nil) {
delay = (int)[parms valueForKey:@"delayTime"];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 判斷成功
// 判斷失敗
NSString *resultData = @"This is a Mock Data!!";
NSLog(@"Network Finish:%@",resultData);
// ***這裏加了判斷***
if (self.delegate && [self.delegate respondsToSelector:@selector(networkFinishWithSuccess:AndError:)]) {
[self.delegate networkFinishWithSuccess:resultData AndError:nil];
}
});
}
複製代碼
注意我用***標註的註釋,這裏有個delegate的判斷,這裏保證了Block考慮點2不會出現,由於當delegate爲nil的時候,絕對不會執行delegate的方法.
演示一下,代碼以下
#pragma mark - Delegate request
- (void)delegateDemo {
self.service.delegate = self;
[self.service requestWithParms:nil];
}
#pragma mark - NetWorkDelegate
- (void)networkFinishWithSuccess:(id)data AndError:(NSError *)error {
NSLog(@"Result from Delegate:%@", data);
}
複製代碼
一樣的,一進入Nextpage就馬上點返回,等5s看看代碼會不會執行
2018-03-25 21:08:23.422103+0800 NetworkCallback[6399:3784145] NextPageViewController has been dealloc!
複製代碼
只有一條log,說明內存釋放沒有問題,並且在回調前對delegate的判斷,使得咱們很是方便的得知業務層是否還存在了,而若是用Block來實現就很麻煩,在網絡回調前是沒法得知的,必定要在Block裏面加判斷代碼
總結:
那麼難道delegate就沒有缺點了嗎?
以前的demo中只有一個業務層,工程中絕對不會只有一個,而NetService的delegate只能指向一個對象,豈不是隻有一個請求可以拿到回調,這豈不是滑天下之大稽?
固然不能這樣,若是使用delegate,就必須對每一個請求封裝成一個對象,而不能統一的用一個NetService
@interface RequestAPI : NSObject
@property (nonatomic, weak, nullable) id<NetWorkDelegate> delegate;
/**
模擬Delegate請求方法
@param parms 請求參數
*/
- (void)requestWithParms:(NSDictionary *)parms;
複製代碼
但是每一個對象都去實現一篇請求邏輯豈不是很傻?!
因此底層仍是調用NetService
#import "RequestAPI.h"
@implementation RequestAPI
- (void)requestWithParms:(NSDictionary *)parms {
__weak typeof(self) weakSelf = self;
[[NetService alloc] requestWithParms:parms WithResult:^(id _Nonnull data, NSError * _Nonnull error) {
if (weakSelf && [weakSelf respondsToSelector:@selector(networkFinishWithSuccess:AndError:)]) {
[self.delegate networkFinishWithSuccess:resultData AndError:nil];
}
}];
}
複製代碼
因此其實,使用delegate和Block的結合使用,由此咱們能夠看出