總結:iOS中多線程的經典崩潰

来源:https://www.cnblogs.com/chengxyyh/archive/2020/06/22/13176602.html
-Advertisement-
Play Games

前言 iOS崩潰是讓iOS開發人員比較頭痛的事情,app崩潰了,說明代碼寫的有問題,這時如何快速定位到崩潰的地方很重要。調試階段是比較容易找到出問題的地方的,但是已經上線的app並分析崩潰報告就比較麻煩了。 本文將給大家總結介紹關於iOS中多線程的一些經典崩潰,下麵話不多說了,來一起看看詳細的介紹吧 ...


前言

iOS崩潰是讓iOS開發人員比較頭痛的事情,app崩潰了,說明代碼寫的有問題,這時如何快速定位到崩潰的地方很重要。調試階段是比較容易找到出問題的地方的,但是已經上線的app並分析崩潰報告就比較麻煩了。

本文將給大家總結介紹關於iOS中多線程的一些經典崩潰,下麵話不多說了,來一起看看詳細的介紹吧。

Block 回調的崩潰

在MRC環境下,使用Block 來設置下載成功的圖片。當self釋放後,weakSelf變成野指針,接著就悲劇了

__block ViewController *weakSelf = self;
[self.imageView imageWithUrl:@"" completedBlock:^(UIImage *image, NSError *error) {
NSLog(@"%@",weakSelf.imageView.description);
}];

多線程下Setter 的崩潰

Getter & Setter 寫多了,在單線程的情況下,是沒有問題的。但是在多線程的情況下,可能會崩潰。因為[_imageView release]; 這段代碼可能會被執行兩次,oops!

UIKit 不是線程,所以在不是主線程的地方調用UIKit 的東西,有可能在開發階段完全沒問題,直接免測。但是一到線上,崩潰系統可能都是你的崩潰日誌。Holy shit!

解決辦法:通過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 組合的時候要小心。

  • afterDelay會增加receiver的引用計數,cancel則會對應減一
  • 如果在receiver的引用計數只剩下1 (僅為delay)時,調用cancel之後會立即銷毀receiver,後續再調用receiver的方法就會crash

__weak typeof(self) weakSelf = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (!weakSelf)
{
//NSLog(@"self被銷毀");
return;
}
[self doOther];

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家的支持。

點擊此處,立即與iOS大牛交流學習


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 基礎環境 準備3台虛擬機 配置無密碼登錄 配置方法:https://ipooli.com/2020/04/linux_host/ 並且做好主機映射。 下載Flink https://www.apache.org/dyn/closer.lua/flink/flink-1.10.1/flink-1.10 ...
  • 1. redis-cli命令行遠程連接redis服務 redis-cli -h host -p port -a password host:遠程redis伺服器host port:遠程redis服務埠 password:遠程redis服務密碼 如:下圖所示,redis-cli -h 172.16. ...
  • 1.對查詢進行優化,應儘量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。2.應儘量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。3.應儘量避免在 where 子句中對欄位進行 null 值判斷,否則將導致引擎放棄使用索引而進 ...
  • /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE *  ...
  • Flutter給我們提供了很多而且很好用的內置動畫,這些動畫僅僅需要簡單的幾行代碼就可以實現一些不錯的效果,Flutter的動畫分為補間動畫和基於物理的動畫,基於物理的動畫我們先不說。 補間動畫很簡單,Android裡面也有補間動畫,就是給UI設置初始的狀態和結束狀態,經過我們定義的一段時間,系統去... ...
  • 上篇: 跳槽季“iOS開發”救救自己,別再這樣寫簡歷了 簡歷中需要註意的問題!! HR每天要收到500+簡歷還不止,首先就是簡歷的過濾。就相當於翻牌子。廢話不多說下麵講重點: 簡歷拼寫錯誤:(❌)單詞拼接錯了就不提了,直接pass, 好感度馬上降為零。 比如:githup/CNDS/Foudatio ...
  • 簡介 KVC(Key-value coding)鍵值編碼,顧名思義。額,簡單來說,是可以通過對象屬性名稱(Key)直接給屬性值(value)編碼(coding)“編碼”可以理解為“賦值”。這樣可以免去我們調用getter和setter方法,從而簡化我們的代碼,也可以用來修改系統控制項內部屬性(這個黑魔 ...
  • 前言 最近HR給了我一份簡歷,剛看到簡歷的第一眼,31歲? 讓我有點意外,實際上,現在開發趨向於年輕化,大部分都是90後、95後,畢竟,軟體開發不像硬體開發一樣,年限越高,相對來說越吃香。 31歲,iOS開發工程師,工作經歷7年,5年左右都在外包公司,2年左右在創業公司。 經常能在網上聽到一些某某公 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...