iOS如何在頁面銷燬時取消網路請求
一,說明
轉載自http://blog.csdn.net/u010124617
大家都知道,當一個網路請求發出去之後,如果不管不顧,有可能出現以下情況:
進入某個頁面,做了某種操作(退出頁面、切換某個tab等等)導致之前的請求變成無用請求,這時候有可能出現雖然頁面已經銷燬了,但是網路請求還在外面飛的情況,如果放任不管,那麼這個請求既浪費流量,又浪費效能,尤其是在網路比較差時,一個超時的無用請求更讓人不爽。這時候,我們最好的辦法是cancel掉這些無用的請求。
二,問題描述
傳統的cancel方式是這樣的:
1.在類裡面需要持有請求物件
@property (strong/weak, nonatomic) XXRequest *xxrequest1;
屬性具體用strong還是weak取決於你的網路層設計,有些網路層request是完全的臨時變數,出了方法就直接銷燬的需要用strong,有些設計則具有自持有的特性,請求結束前不會銷燬的可以用weak。
2.在請求發起的地方,賦值請求
xxrequest1 = xxx;
self.xxrequest1 = xxrequest1;
[xxrequest1 start];
3.在需要銷燬的地方,一般是本類的dealloc裡面
[self.xxrequest1 cancel];
可以看到為了cancel一個request,我們的請求物件到處都是,如果再來幾個請求,那處理起來就更噁心了。。
有沒有什麼方式可以讓我們省心省力呢?
三,解決
目標:
我們希望可以控制一部分請求,在頁面銷燬、manager釋放等時機,自動的cancel掉我們發出去的請求,而不需要我們手動去寫上面這種到處都是的程式碼
方案:
監聽類的dealloc方法呼叫,當dealloc執行時,順帶著執行下request的cancel方法
很快,我們就發現了問題:
ARC下不允許hook類的dealloc方法,所以hook是不行的。那還有別的方式可以知道一個類被dealloc了嗎?
其實我們可以採用一些變通的方案得到,我們知道associated繫結的屬性,是可以根據繫結時的設定,在dealloc時自動釋放的,所以我們可以利用這一點做到監聽dealloc呼叫:
構建一箇中間類A,該類在銷燬執行dealloc時,順便執行請求的cancel方法
通過associate繫結的方式,將銷燬類繫結到任意執行類B上
這樣,當執行類B銷燬時,銷燬內部的associate的屬性時,我們就可以得到相應的執行時機。
下面給出核心程式碼:
建立用於cancel請求的類:
@interface YRWeakRequest : NSObject
@property (weak, nonatomic) id request;
@end
@implementation YRWeakRequest
@end
@interface YRDeallocRequests : NSObject
@property (strong, nonatomic) NSMutableArray<YRWeakRequest*> *weakRequests;
@property (strong, nonatomic) NSLock *lock;
@end
@implementation YRDeallocRequests
- (instancetype)init{
if (self = [super init]) {
_weakRequests = [NSMutableArray arrayWithCapacity:20];
_lock = [[NSLock alloc]init];
}
return self;
}
- (void)addRequest:(YRWeakRequest*)request{
if (!request||!request.request) {
return;
}
[_lock lock];
[self.weakRequests addObject:request];
[_lock unlock];
}
- (void)clearDeallocRequest{
[_lock lock];
NSInteger count = self.weakRequests.count;
for (NSInteger i=count-1; i>0; i--) {
YRWeakRequest *weakRequest = self.weakRequests[i];
if (!weakRequest.request) {
[self.weakRequests removeObject:weakRequest];
}
}
[_lock unlock];
}
- (void)dealloc{
for (YRWeakRequest *weakRequest in _weakRequests) {
[weakRequest.request cancel];
}
}
@end
3.對任意類繫結該中間類
@implementation NSObject (YRRequest)
- (YRDeallocRequests *)deallocRequests{
YRDeallocRequests *requests = objc_getAssociatedObject(self, _cmd);
if (!requests) {
requests = [[YRDeallocRequests alloc]init];
objc_setAssociatedObject(self, _cmd, requests, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return requests;
}
- (void)autoCancelRequestOnDealloc:(id)request{
[[self deallocRequests] clearDeallocRequest];
YRWeakRequest *weakRequest = [[YRWeakRequest alloc] init];
weakRequest.request = request;
[[self deallocRequests] addRequest:weakRequest];
}
@end
4.對外暴露的標頭檔案
@interface NSObject (YRRequest)
/*!
* @brief add request to auto cancel when obj dealloc
* @note will call request's cancel method , so the request must have cancel method..
*/
- (void)autoCancelRequestOnDealloc:(id)request;
@end
使用方式
怎麼樣,看標頭檔案是不是覺得很簡單,使用方式就很簡單了,
比如說我們需要在某個VC裡,釋放時自動cancel網路請求:
//請求發起的地方:
xxrequest1 = xxx;
[xxrequest1 start];
[self autoCancelRequestOnDealloc:xxrequest1];
好了,從此不再擔心該類銷燬時請求亂飛了。
四,備註:
1.我的實現類裡面,預設呼叫的是cancel方法,所以理論上,所有帶有cancel方法的request都可以直接用這個方法呼叫(如AFNetworking、NSURLSessionTask等等)
2.有些人會說,我是用自己的網路層,自己封裝的requset發起的請求,不呼叫cancel,自己封裝的物件也會銷燬的;我要提醒的是,有可能你自己封裝的物件銷燬了,但是其下層,無論對接的是AF還是系統的,又或者是其他的請求庫,一定是具有自持有性質的,如果不這麼說,風險在於資料返回前底層的請求就會銷燬掉,一般不會有人這麼設計的。
3.例子中我繫結的是self,其實還可以繫結到任意物件上,比如某個類的內部屬性等等,這樣可以根據業務需求進一步控制請求的cancel時機