「 iOS知識小集 」2018 · 第 21 期(抽獎啦)

知識小集發表於2019-03-01

7 月活動,關注公眾號抽獎

又到了我們每月一次的活動日了。今天我們還是抽獎,送出 10 枚 MWeb for Mac 的兌換碼。MWeb 是一款專業的 Markdown 寫作、記筆記、靜態部落格生成軟體,功能相當強大,相信用過的童鞋都知道。作者 oulvhai,目前是獨立開發者。這裡感謝 oulvhai 為我們提供的兌換碼。

MWeb 相關的介紹大家可以檢視官網,我們這次的兌換碼是非 Mac App Store 版,還請中獎的小夥伴在官網下載試用版,然後啟用即可。

關注公眾號在文章末尾抽獎

「 iOS知識小集 」2018 · 第 21 期(抽獎啦)

上週內容回顧

上週公眾號釋出的以下文章:

本期知識小集的主要內容包括:

  • 一個關於 GCD 分組任務的小 tip
  • 一次記憶體洩漏後的思考
  • UIAlertView 與輸入框結合使用時的一個坑
  • 提高 Shortcuts 除錯效率的小技巧
  • Xcode 斷點除錯時列印變數值報錯的問題(編譯優化相關)
  • Watch OS 4 新 API 解決手錶自動休眠問題

一個關於 GCD 分組任務的小 tip

作者: halohily

在專案中看到一段使用 GCD group 處理的程式碼,簡化下來大概如下圖

「 iOS知識小集 」2018 · 第 21 期(抽獎啦)

dispatch_group_notify 的呼叫放在了 dispatch_group_async 的 block 中,乍一看會有是否產生永久阻塞的疑問,因為子任務完成後的派發任務被放在了一個子任務中。然而其實這是不會阻塞的,程式碼會按編寫人的預期進行執行,即 log1 輸出之後,輸出 log2。這是因為 dispatch_group_notify 的 block 是非同步執行的。

再舉個例子,如下圖,執行結果依次會是:log 1,log 2 ,log 4 ,log 5 ,log 3。

「 iOS知識小集 」2018 · 第 21 期(抽獎啦)

雖然此處結果正確,但這種將 dispatch_group_notify 的呼叫放在某一個子任務的執行塊中的寫法是不被推薦的,它不但反邏輯,而且並不總能保證結果正確。比如如果在此例中,在呼叫了 dispatch_group_notify 的子任務之後,又為該任務組使用 dispatch_group_async 語句新增後續子任務,這時程式碼的執行結果是不確定的。

既然最開始的例子中執行結果是正確的,有的同學會問,如果把 dispatch_group_notify 的呼叫放在所有子任務的最前面,如下圖,是否也能獲得預期的結果呢?答案是否定的,因為在最開始呼叫 dispatch_group_notify 時,子任務數量為0,它的程式碼塊會立即執行。而後為該組派發了多個子任務,當這些子任務都執行完畢後,也並不會再次觸發 dispatch_group_notify 的程式碼塊。

「 iOS知識小集 」2018 · 第 21 期(抽獎啦)

一次記憶體洩漏後的思考

作者: Lefe_x

最近專案中遇到一個記憶體洩漏的問題,SecondViewController 這個類在 pop 後並沒有執行 dealloc 方法,也就沒有被正常被釋放。使用記憶體洩漏工具排查,並沒有發現有迴圈引用的地方,手動查了一下也沒發現異常。正在迷茫的時候,突然看到了一個註冊監聽的地方。實現方式類似下面這樣:

- (void)dealloc {
    [[Manager sharedInstance] removeObserver:self];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    [[Manager sharedInstance] addObserver:self];
}
複製程式碼

看到這裡你應該已經猜到 SecondViewController 為什麼沒被釋放,它被 Manager 持有了,而 Manager 是一個單例,自然 SecondViewController 也不會被釋放,dealloc 方法也不會執行。

這種設計很常見,往往給某個服務註冊監聽,達到類似通知的效果。如果使用陣列儲存監聽者,監聽者將會被陣列持有。有同學可能說,可以在 viewDidAppear 註冊,在 viewWillDisappear 移除,這樣 SecondViewController 就會被釋放。但是,這樣設計很糟糕,我們儘量不去約束呼叫者如何呼叫某個 API。

其實正確的做法是使用一個弱引用容器,我們可以使用 NSHashTable 來儲存監聽者,這樣當監聽者釋放後,將自動從 NSHashTable 中移除,也不需要主動呼叫移除監聽者的方法(也可以呼叫,視情況而定)。下面是一個簡單的實現,你也可以參考 YYTextKeyboardManager 的實現:

_listenerTable = [NSHashTable weakObjectsHashTable];

- (void)addObserver:(NSObject *)obj {
    [self.listenerTable addObject:obj];
}

- (void)removeObserver:(NSObject *)obj {
    [self.listenerTable removeObject:obj];
}
複製程式碼

UIAlertView 與輸入框結合使用時的一個坑

作者: Vong_HUST

相信 UIAlertView 大家應該都很熟悉,但是最近遇到一個坑。

由於歷史原因,專案中還在大量使用 UIAlertView。某天測試過來反饋說,評論框字元長度超過最大長度時,點選傳送,彈出一個 alert 提示,點選確定後,評論框無法在被啟用,也就是沒法彈出鍵盤了。很是怪異,debug 無果,搜了一下 stackoverflow,發現有人遇到過類似的問題,可以點選末尾的參考連結來檢視具體詳情。

他給出的解決方案就是把這種情況下的 UIAlertView 換成 UIAlertController。試了下這種方式,果然是可行的,由於之前 UIAlertView 是不依賴其它檢視層級的,建立後直接 show 就可以了,所以很多地方直接寫在了非檢視控制器類中。在換成 UIAlertController 之後,由於它是繼承自 UIViewController 的,所以必須要有 VC 把它 present 起來。解決方案也很簡單,寫一個 UIViewController 的分類獲取當前頂部可見的 ViewController,然後在上面 present 出 UIAlertController 即可,獲取頂部可見 ViewController 的程式碼隨便一搜就可以找到,這邊就不貼了。

PS:UIAlertController 是 iOS8 以後才提供的,不過相信大家也不用適配 iOS8 之前的系統了吧?。如果還要適配,那就只能做版本區分了。。。

參考

iOS 9 - Keyboard pops up after UIAlertView dismissed

提高Shortcuts除錯效率的小技巧

作者: 高老師很忙

iOS12提供了 Shortcuts 的功能,今天給大家介紹 2 個蘋果提供的提高 Shortcuts 除錯效率的小技巧。

  • Shortcuts 是可以通過 Siri 喚起的,如果每次除錯的時候都要和 Siri 說一次短語,既浪費時間又打擾旁邊正在工作的同事,Xcode 提供了 Siri Intent Query 功能,在除錯 Intents Extension 或者 Intens UI Extension 的 target 時,直接在裡面輸入你要說的短語,就可去省去調戲 Siri 的時間啦。

「 iOS知識小集 」2018 · 第 21 期(抽獎啦)

  • iPhone設定->開發者 裡面提供了 Display Recent Shorts 和 Display Donations on Lock Screen 的開關,可以忽略系統當前的建議和預測,顯示我們需要除錯的 Shortcuts;同時還支援 Force Sync Shortcuts to Watch 的功能,手動去強制同步到 Watch,會節省很多時間哦!

「 iOS知識小集 」2018 · 第 21 期(抽獎啦)

我覺得這2個小技巧還是很實用的,如果有其它小技巧歡迎一起交流!

Xcode 斷點除錯時列印變數值報錯的問題(編譯優化相關)

作者: KANGZUBIN

在日常開發中,我們經常會在 Debug 模式下打斷點進行除錯,並通過 LLDB 的 po 命令在控制檯列印一些變數的值,以方便排查問題。

今天在 Release 模式下編譯執行專案,發現要列印某一變數的值時(po xxx),報如下錯誤:

error: Couldn't materialize: couldn't get the value of variable xxx: no location, value may have been optimized out
error: errored out in DoExecute, couldn't PrepareToExecuteJITExpression
複製程式碼

大致意思是說,xxx 的值不存在,可能已經被編譯優化了。而且在斷點模式下當我們把滑鼠的箭頭移到某一變數上要進行快速瀏覽時,發現它們的值都是 nil

查了一下才發現,原來這與 Xcode 工程的編譯選項 Optimization Level 設定有關,它是指編譯器的優化級別,優化後的程式碼效率比較高,但是可讀性比較差,且編譯時間更長,它有 6 個選項值如下圖:

「 iOS知識小集 」2018 · 第 21 期(抽獎啦)

上述每選項值的詳細說明可以參考《Xcode 中 Optimization Level 的設定》《如何加快編譯速度》兩篇文章,我們這裡不再贅述。

Xcode 工程的 Optimization Level 值在 Debug 模式下預設為 None [-O0],表示編譯器不會嘗試優化程式碼,保證除錯時輸出期望的結果;而在 Release 模式下預設為 Fastest, Smallest[-Os],表示編譯器將執行所有優化,且不會增加程式碼的長度,它是可執行檔案佔用更少記憶體的首選方案。

這也是為什麼我們在 Release 模式下斷點列印變數會報錯,因為編譯器已經給程式碼做了優化,它將不在除錯時記錄變數的值了。

此外,有時候遇到一些線上 Bug 但是在 Debug 除錯時卻無法復現,我猜有可能會跟編譯優化有關,你覺得呢?歡迎留言討論。

Watch OS 4 新 API 解決手錶自動休眠問題

作者: 我是喬忘記瘋狂

Watch OS 4 新的 API,用於解決手腕轉動時手錶螢幕可能自動休眠的問題。比如在 watch 上展示二維碼給各種 POS 掃描,螢幕經常會自動休眠。經測支付寶 watch app 已經使用了此 API,不過官方推薦只針對某些特定介面開啟此功能,而支付寶是整個 watch app 都開啟了此功能。 ​​​​

「 iOS知識小集 」2018 · 第 21 期(抽獎啦)

相關文章