iOS開發筆記— 資料庫、Crash、記憶體問題分析

ios8988發表於2018-09-12

前言

分享iOS開發中遇到的問題,和相關的一些思考,本次內容包括:UIKit的iOS11問題、資料庫問題定位、線上Crash處理、記憶體問題分析。

正文

1、iOS 11的UITabbar的高度異常

問題描述:iOS 11+iPhone,在橫豎屏切換的場景下,UITabbarViewController的底部欄UITabbar會出現高度異常。

問題定位:經過除錯發現,從豎屏到橫屏的時候,系統會改變UITabbar的高度;而我們的底部欄高度是自定義的值,故而會導致系統修改後的高度與自定義值不相同的情況。

解決方案,KVO:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {        if (self.tabBar.height != KSTabBarHeight) {            self.tabBar.height = KSTabBarHeight;            self.tabBar.bottom = SCREEN_HEIGHT;        }

Stackoverflow的類似情況

2、CoreData資料庫升級時間長

問題描述:App在升級的時候會對CoreData資料庫進行一次遷移,而某些使用者反饋升級時間長達數分鐘。

問題定位:CoreData資料庫遷移使用的是系統提供的自動遷移,經過本地測試,確實存在資料庫較大的情況下,升級時間較長的問題。

那麼如何確定資料庫是哪些表是瓶頸?

使用者的資料庫比較大,不可能進行整個資料庫上傳操作;而CoreData並不支援獲取某個表的大小。

可以採取一種方案:使用者上報資料庫每張表的行數,本地通過工具求出每張表的平均值,用以估算每張表的大小。

找到可以匯出沙盒本地沙盒的App活躍使用者(比如說運營、產品),用sqlite3_analyzer對資料庫進行分析,得到每張表大小,再除以行數,得到每張表每行的平均值。

(不能通過行數直接判斷資料庫大小,因為表的列數不確定;也不能通過列大小*行數得到表體積,因為某些欄位為空)

修復方案:

  • 對瓶頸的表進行行數和體積雙重控制;

  • 對某些行數較多但表體積小的表建索引;

引用:

sqlite資料庫分析

sqlite3_analyzer安裝

Appropriate Uses For SQLite

sqlite索引

Customizing the Migration Process

3、objc_msgSend的Crash分析

問題描述:objc_msgSend是常見的一種Crash,這次的堆疊如下

這類由UIKit引起的Crash通常是在回撥業務層時,對應的target已經被釋放,於是在objc_msgSend的時候就會發生Crash。

暫存器和模組載入地址

問題定位:在本例中,檢視上圖知道,lr暫存器的地址是在第一個模組的載入區間內,以此作為線索。

用以下指定,進行手工符號化:

atos -o XXX arm64 0x000000010134d36c -l 0x1000fc000(XXX是二進位制名字)

最終定位到問題,具體的程式碼類似:

[self.delegete remove];self.data = ...

在這種情況下,self.delegate在remove掉之後self之後,self已經被釋放,下面的self.data再進行賦值操作,就會出現異常情況。

解決方案:把  [self.delegete remove]; 放到最後一行。

後記:ios-Swift/Object C開發 稽核上架交流群 869685378 歡迎各位大牛來分享交流 IOS,馬甲包,低要求,內容開發沒有限制,報酬豐厚,實力誠信 Q:782675105 

該問題只出現在iOS 8。在iOS 11的機型上,通過除錯我們可以獲取到self.data=…這一行在執行時,關於self的記憶體引用情況:

autoreleasepool和thread都會持有self,保證self在本次執行過程中不釋放。故此猜測該問題蘋果已經發現,並且在iOS 8後續的版本已經修復。

4、記憶體相關問題

實際場景涉及到業務,所以抽象成程式碼來進行分析。

場景1

下面這段程式碼是否能夠正常執行?

如果可以,結果是什麼?

如果不可以,是為什麼?

- (void)viewDidLoad {    [super viewDidLoad];    SInt16* buffer = NULL;    SInt16* p = &buffer[15];    NSLog(@"tmp %d", p);}

場景2

下面這段程式碼是否能夠正常執行?

如果可以,結果是什麼?

如果不可以,是為什麼?

- (void)viewDidLoad {    [super viewDidLoad];        char *pBuf = malloc(5);    memcpy(pBuf, "aaabbbcccddddeeefff", 10);       puts(pBuf);}

分析:

場景1:

此處有兩個trick:

1、p是否能夠正常賦值,以及賦何值;

2、指標型別是SInt16*, 計算地址要注意;

[] 是下標運算子,根據運算元和偏移量,獲取指定地址的值;

在此題之中,buffer[15]等於*(buffer + 15);

&buffer[15] 等於&(*(buffer + 15));

& 是取址運算子,返回運算元的記憶體地址;

&buffer[15] = &(*(buffer + 15)) = buffer + 15

所以p = &buffer[15] = buffer+15 = 0+15*sizeof(SInt16) = 30

故而答案是能正常執行,結果是”tmp 30″。

場景2:

申請了一塊較大的記憶體,在memcpy的時候,偶然情況下會出現越界的情況。但是因為堆記憶體空間到棧記憶體空間的距離不固定,不一定會出現crash的情況。

上面的題目本質是堆記憶體訪問越界。

故而上述程式碼大多數情況下輸出aaabbbcccd,少數情況下不可預知。

總結

2018年的忙碌情況超過我想象,長時間不更新iOS開發筆記讓我都忘了還有這個專題所在。

我有個習慣,開發中遇到問題,超過十分鐘還沒解決的時候,就會記錄下來,這樣是開發筆記專題的雛形。

而在加入新公司的第二個年頭,我慢慢已經在iOS上的收穫越來越少。

從筆記的新增情況來看,就可以發現:每天大多數是重複性勞動!

嘗試看過一些iOS相關的書籍,但總感覺收穫不大。

今年我選擇把更多的業餘學習時間分配給Metal,詳見Metal入門教程總結。

 落影loyinglin

相關文章