前言git
iOS崩潰是讓iOS開發人員比較頭痛的事情,app崩潰了,說明代碼寫的有問題,這時如何快速定位到崩潰的地方很重要。調試階段是比較容易找到出問題的地方的,可是已經上線的app並分析崩潰報告就比較麻煩了。github
本文將給你們總結介紹關於iOS中多線程的一些經典崩潰,下面話很少說了,來一塊兒看看詳細的介紹吧。面試
Block 回調的崩潰數組
在MRC環境下,使用Block 來設置下載成功的圖片。當self釋放後,weakSelf變成野指針,接着就悲劇了多線程
__block ViewController *weakSelf = self;
[self.imageView imageWithUrl:@"" completedBlock:^(UIImage *image, NSError *error) {
NSLog(@"%@",weakSelf.imageView.description);
}];併發
多線程下Setter 的崩潰app
Getter & Setter 寫多了,在單線程的狀況下,是沒有問題的。可是在多線程的狀況下,可能會崩潰。由於[_imageView release]; 這段代碼可能會被執行兩次,oops!async
UIKit 不是線程,因此在不是主線程的地方調用UIKit 的東西,有可能在開發階段徹底沒問題,直接免測。可是一到線上,崩潰系統可能都是你的崩潰日誌。Holy shit!oop
解決辦法:經過hook 住setNeedsLayout,setNeedsDisplay,setNeedsDisplayInRect來檢查當前調用的線程是不是主線程。學習
-(void)setImageView:(UIImageView *)imageView
{
if (![_imageView isEqual:imageView])
{
[_imageView release];
_imageView = [imageView retain];
}
}
更多Setter 類型的崩潰
property 的屬性,寫的最多的就是nonatomic,通常狀況下也是沒有問題的!
@interface ViewController ()
@property (strong,nonatomic) NSMutableArray *array;
@end
跑一下下面這段代碼,你會看到:
malloc: error for object 0x7913d6d0: pointer being freed was not allocated
for (int i = 0; i < 100; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.array = [[NSMutableArray alloc] init];
});
}
緣由就是:對象被重複relaese 了。查看一下runtime 源碼
解決辦法:屬性聲明爲atomic.
一個更爲常見的例子:
if(handler == nil)
{
hander = [[Handler alloc] init];
}
return handler;
若是A,B兩個線程同時訪問到if語句, 此時handler == nil
條件知足, 兩個線程都走到下一句初始化實例.
此時A線程先完成初始化並賦值(這個實例咱們叫它a), 而後繼續日後走到其餘邏輯.而這時候, B線程開始作初始化並賦值(這個實例咱們叫它b), handler將指向B線程初始化出來的對象. 而A初始化出來的實例a由於引用計數減小1(減小到0)而被釋放. 但在A線程中, 代碼還會嘗試訪問a所在的地址, 這個地址裏的內容由於被釋放而變得沒法預測, 從而致使野指針.
問題還有一個很關鍵的點, 在一個對象的某個方法的調用過程當中, 這個對象的引用計數並不會增長, 到致使它若是被釋放, 後續的執行過程當中對這個對象的訪問就可能會致使野指針[1].
Exception Type: SIGSEGV
Exception Codes: SEGV_ACCERR at 0x12345678
Triggered by Thread: 1
簡單加個鎖就能夠解決問題了:
@synchronized(self){
if(handler == nil)
{
hander = [[Handler alloc] init];
}
}
return handler;
多線程下對變量的存取
if (self.xxx) {
[self.dict setObject:@"ah" forKey:self.xxx];
}
你們第一眼看到這樣的代碼,是否是會認爲是正確的?由於在設置key的時候已經提早進行了self.xxx爲非nil的判斷,只有非nil得狀況下才會執行後續的指令。可是,如上代碼只有在單線程的前提下才是正確的。
假設咱們將上述代碼目前執行的線程爲Thread A,當咱們執行完if (self.xxx)
的語句以後,此時CPU將執行權切換給了Thread B,而這個時候Thread B中調用了一句self.xxx = nil
。 使用局部變量能夠解決這個問題
__strong id val = self.xxx;
if (val) {
[self.dict setObject:@"ah" forKey:val];
}
這樣,不管多少線程嘗試對self.xxx進行修改,本質上的val都會保持現有的狀態,符合非nil的判斷。
做爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個個人iOS交流羣:519832104 無論你是小白仍是大牛歡迎入駐,分享經驗,討論技術,你們一塊兒交流學習成長!
另附上一份各好友收集的大廠面試題,須要iOS開發學習資料、面試真題,能夠添加iOS開發進階交流羣,進羣可自行下載!
dispatch_group 的崩潰
dispatch_group_enter 和 leave 必須是匹配的,否則就會crash . 在多資源下載的時候,每每須要使用多線程併發下載,所有下載完以後通知用戶。開始下載,dispatch_group_enter ,下載完成dispatch_group_leave 。 很是簡單的流程,可是當代碼複雜到必定程度或者是使用了一些第三方庫的時候,就很大可能出問題。
dispatch_group_t serviceGroup = dispatch_group_create();
dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{
NSLog(@"Finish downloading :%@", downloadUrls);
});
// t 是一個包含一堆字符串的數組
[downloadUrls enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
dispatch_group_enter(serviceGroup);
SDWebImageCompletionWithFinishedBlock completion =
^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
dispatch_group_leave(serviceGroup);
NSLog(@"idx:%zd",idx);
};
[[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString: downloadUrls[idx]] options:SDWebImageLowPriority progress:nil completed:completion];
}];
使用多線程進行併發下載,直到全部圖片都下載完成(能夠失敗)進行回調,其中圖片下載使用的是SDWebImage.發生崩潰的場景是:有10 張圖片,分開兩次下載(A & B)。其中在B組裏面有一張圖片和A組下載的圖片重複了。假設A組下載對應GroupA ,B組GroupB
下面截取SDWebImage源碼:
dispatch_barrier_sync(self.barrierQueue, ^{
SDWebImageDownloaderOperation *operation = self.URLOperations[url];
if (!operation) {
operation = createCallback();
// 注意這行
self.URLOperations[url] = operation;
__weak SDWebImageDownloaderOperation *woperation = operation;
operation.completionBlock = ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
if (self.URLOperations[url] == soperation) {
[self.URLOperations removeObjectForKey:url];
};
};
}
// 注意這行
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}
SDWebImage的下載器會根據URL作下載任務對應NSOperation映射,相同的URL會映射到同一個未執行的NSOperation。當A組圖片下載完成後,相同的url 回調是 GroupB 而不是Group A。此時Group B的計數爲1 。當B 組圖片所有下載完後,結束計數爲 5+1 。由於enter 的次數爲5 ,leave 的次數爲6 ,所以會崩潰!
最後一個持有者釋放後的崩潰
對象A被 manager 持有,在A中調用[Manager removeObjectA]
。A對象的retainCount -1
, 當retainCount 等於零時,對象A已經開始釋放了。在調用removeObjectA 後,緊接着調用[self doSomething]
,就會崩潰。
-(void)finishEditing
{
[Manager removeObject:self];
[self doSomething];
}
這種狀況通常會發生在數組或者字典包含對象,並且是對象的最後持有者。當在對象處理很差,就會有上面的崩潰。還有一種狀況就是,當數組或者字典裏面的對象已經被釋放了,當遍歷數組或者取字典裏面的值發生崩潰。這種狀況,會讓人很崩潰,由於有時候堆棧是這樣的:
Thread 0 Crashed:
0 libobjc.A.dylib 0x00000001816ec160 _objc_release :16 (in libobjc.A.dylib)
1 libobjc.A.dylib 0x00000001816edae8 __ZN12_GLOBAL__N_119AutoreleasePoolPage3popEPv :508 (in libobjc.A.dylib)
2 CoreFoundation 0x0000000181f4c9fc __CFAutoreleasePoolPop :28 (in CoreFoundation)
3 CoreFoundation 0x0000000182022bc0 ___CFRunLoopRun :1636 (in CoreFoundation)
4 CoreFoundation 0x0000000181f4cc50 _CFRunLoopRunSpecific :384 (in CoreFoundation)
5 GraphicsServices 0x0000000183834088 _GSEventRunModal :180 (in GraphicsServices)
6 UIKit 0x0000000187236088 _UIApplicationMain :204 (in UIKit)
7 Tmall4iPhone 0x00000001000b7ae4 main main.m:50 (in Tmall4iPhone)
8 libdyld.dylib 0x0000000181aea8b8 _start :4 (in libdyld.dylib)
產生這種堆棧可能的場景是:
釋放Dictionary的時候,某個值(value)由於被其餘代碼提早釋放變成野指針, 此時再次被釋放觸發Crash. 若是能夠在每一個Dictionary釋放的時候, 把全部的key/value打出來, 若是某個key/value恰好被打出來以後, crash就發生了, 那麼掛就掛在剛被打出來的key/value上.
對象的釋放線程要和它處理事情的線程一致
對象A在主線程監聽Notification事件,若是這個對象被其它線程釋放了。此刻,若是對象A 正在執行notification 相關的操做,再訪問對象相關資源就野指針了,發生crash.
performSelector:withObject:afterDelay:
調用此方法,若是不是在主線程,那麼必需要確保當前線程的ruuloop是存在的,performSelector_xxx_afterDelay 依賴runlopp才能執行。另外使用 performSelector:withObject:afterDelay:
和 cancelPreviousPerformRequestsWithTarget
組合的時候要當心。
__weak typeof(self) weakSelf = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (!weakSelf)
{
//NSLog(@"self被銷燬");
return;
}
[self doOther];
總結
以上就是這篇文章的所有內容了,但願本文的內容對你們的學習或者工做具備必定的參考學習價值,若是有疑問你們能夠留言交流,謝謝你們的支持。