「 iOS知識小集 」2018 · 第 35 期

原文連接ios

上週公衆號發佈的如下文章:git

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

  • 爲 Fabric MOD 一個卡頓檢測功能
  • 關於定位的一個小知識點
  • iOS 獲取設備型號最新總結
  • Storyboard 中的約束優先級
  • UIWindow 的顯示特性與常見操做方法小結

爲 Fabric MOD 一個卡頓檢測功能

做者:hite和落雁數據庫

卡頓檢測系統,用於檢測 App 的主線程運行狀況。在追求 N 個 9 奔潰以外,卡頓也是咱們極其重要運行指標。windows

很遺憾,世界上最好的免費 APM 平臺 Fabric 卻沒有。而國內的 bugly、網易雲捕等,都提供了相似的功能。以下圖是雲捕的卡頓功能。後端

提及來卡頓檢測,技術原理很簡單,下面是來自 bugly 的 QA 裏的描述數組

iOS 卡頓檢查的依據是監控主線程 Runloop 的執行,觀察執行耗時是否超過預約閥值(默認閥值爲3000ms) 在監控到卡頓時會當即記錄線程堆棧到本地,在App從後臺切換到前臺時,執行上報。
複製代碼

卡頓檢測系統,這個大任務,能夠分解爲兩部分:卡頓的檢測 + 卡頓的展現和管理。緩存

卡頓的收集

卡頓的收集,有現成的代碼,核心代碼可查看,gist gist.github.com/hite/1a7ee4…bash

檢測到以後,須要獲取當前時刻的堆棧,全部線程的堆棧(其實只須要主線程就夠了)。服務器

OK,拿來主義,一個頗有名 PLCrashReporter。我本身集成測試,在個人老古董機器,iPhone 6 跑,卡頓是能檢測到,可是整個軟件基本不可用,整個界面全卡住了。PLCrashReporter 生成日誌代碼以下圖所示。

性能很是差,徹底不可用。

拿不到堆棧信息,沒法展現,因此只能採用造輪子的方式。根據戴銘 blog 裏的例子,我改造了下,如 gist gist.github.com/hite/1a7ee4… 所示。咱們收集到了全部堆棧的棧頂地址;接下來咱們須要將這些棧信息符號化。

很容易想到的方案是傳到本身的服務器上,用 Mac 環境處理堆棧的符號化,轉換爲可讀的堆棧數據——代價太大,並且還不經濟。

在瀏覽了 fabric 的各項 API 後,我發現有一個很討好的接口,recordCustomExceptionName

符號化堆棧,卡頓管理

fabric 提供了 recordCustomExceptionName 接口,接口簽名以下圖所示。

咱們利用這個接口,將第一步收集的堆棧數據傳給 fabric,讓 fabric 給咱們符號化,並且 fabric 卡頓日誌還可以聚合、分類、分組、跟蹤。crash 日誌的那一套均可以用上, fabric 用戶對此是熟悉的。核心代碼以下圖所示。

至此,咱們用不多的代價就作好了一個卡頓檢測的系統,而且和奔潰功能一塊兒使用,集中處理 APM 各項指標。

這個方案在月活幾十萬的 App 應用了快半年了。用戶一點都沒有感覺到有卡頓系統,對現有系統影響很小。以下圖所示。

全部的數據都在 crashlytics 裏,選擇 non-fatal,便可,從統計下來的數據來看,卡頓主要體驗在;

  1. 讀寫文件
  2. 讀寫數據庫
  3. 處理圖片
  4. 動畫渲染
  5. 在非主線程讀一些主線程才能用的屬性(奇怪吧?)

基本上和咱們猜測是一致的,接下來就須要跟蹤和處理這些卡頓。

目前這個系統有兩個缺陷:

  1. 檢查卡頓自己的 runloop 也被認爲是卡頓
  2. 由於 recordCustomExceptionName 接口的限制,全部線程的棧都被合併到一個棧,但不影響核心卡頓代碼的閱讀。

關於定位的一個小知識點

做者: 高老師很忙

今天分享一個輕鬆的小知識點~~~

搜索了網上關於 iOS 定位的文章,不少在 locationManager(_:didUpdateLocations:) 收到回調就執行了 stopUpdatingLocation(),以下圖:

然而在一些狀況之下,這樣寫是有隱患的(以下圖),

在某次運行的時候(並非每次出現),在 21 點 16 分返回了一個 21 點 09 分的點,這是由於 CoreLocation 可能會返回一個緩存的值給咱們,因此咱們使用的時候應該判斷一下時間戳(以下圖),這樣能夠減小定位誤差。

參考連接:O網頁連接

iOS 獲取設備型號最新總結

做者: KANGZUBIN

在開發中,咱們常常須要獲取設備的型號(如 iPhone X,iPhone 8 Plus 等)以進行數據統計,或者作不一樣的適配。但蘋果並無提供相應的系統 API 讓咱們直接取得當前設備的型號。

其中,UIDevice 有一個屬性 model 只是用於獲取 iOS 設備的類型,如 iPhone,iPod touch,iPad 等;而其另外一個屬性 name 表示當前設備的名稱,由用戶在設置》通用》關於》名稱中設定,如 My iPhone,xxx 的 iPhone 等。然而,咱們沒法根據這兩個值得到具體的型號。

不過,每一種 iOS 設備型號都有對應的一個或多個硬件編碼/標識符,稱爲 device model 或者叫 machine name,以前的小集介紹過,咱們能夠經過以下圖中的代碼來獲取。

因此,一般的作法是,先獲取設備的 device model 值,再手動映射爲具體的設備型號(或者直接把device model 值傳給後端,讓後端去作映射,這樣的好處是能夠隨時兼容新設備)。

例如:去年發佈的第一代 iPhone X 對應的 device mode 爲 iPhone10,3 和 iPhone10,6,而今年最新發布 iPhone XS 對應 iPhone11,2,iPhone XS Max 對應 iPhone11,4 和 iPhone11,6,iPhone XR 對應 iPhone11,8,完整的 device mode 數據參考 Wiki:www.theiphonewiki.com/wiki/Models

綜上,咱們能夠先獲取 device model 值,記爲 platform,而後進行對比判斷,轉換成具體的設備型號,實現代碼以下圖所示。

備註:圖中代碼只給了對 iPhone 設備型號的判斷,而完整的包括 iPad 和 iPod touch 型號我已經放在 GitHub Gist 上,你們能夠參考,詳見這裏:gist.github.com/kangzubin/5…

參考連接:

Storyboard 中的約束優先級

**做者:**這個湯圓沒有餡

在 Masonary 中也能夠設置約束的優先級,如make.left.equalTo(weakSelf.view.mas_left).offset(20).priority(250) 中的 priority。

在 Storyboard 中也能夠,舉個🌰:父視圖上有 imgView 和兩個 label,現要求兩個 label 的寬度隨內容且不超出,另必須保證紅色 label 中的內容顯示完整。以下圖。

storyboard 拖控件就不說了,直接從約束開始。

imgView: left、right、top、height、width 綠色label:left、center-y、right、height 紅色label:left、center-y、right、height

這個時候 storyboard 會報錯,由於兩個 label 的寬度沒法定位。以下圖。

提示說,下降紅色 label 的水平方向壓縮阻力(即容易被壓縮)以確保在其餘視圖以前能夠被裁剪。點擊 Change Priority,改變約束優先級。

如上圖,咱們能夠看 Size Inspector 中,紅色 label 水平方向壓縮阻力由750降爲了749,說明在水平方向上,綠色 label 展現的優先級要高於紅色 label。固然這和咱們一開始的需求反了,待會兒再改。咱們先看看 Size Inspector 中的說明。

  • Content Hugging Priority:拉伸阻力,即抗拉伸。值越大,越不容易被拉伸。
  • Content Compression Resistance Priority:壓縮阻力,即抗壓縮。值越大,越不容易被壓縮。
  • Intrinsic Size:控件未設置寬高約束時用的。
  • Ambiguity:解決衝突時是否須要驗證。

Priority 的值默認分爲三個等級 Required(1000)、High(750)、Low(250),其實能夠輸入任意其餘數字。

好,回到需求,只要把紅色 label 的水平方向壓縮阻力優先級的值改爲任意大於綠色 label壓縮阻力的值便可。若是紅色 label 的內容太多,那就會把綠色 label 給擠沒掉。以下圖。

UIWindow 的顯示特性與常見操做方法小結

**做者:**陳滿iOS

UIWindow 的顯示特性

一、相同 windowLevel 下,調整 UIWindow 顯示層的基本方法

  1. 顯示相關屬性:hidden
  • 若是僅僅想顯示一個UIWindow
customWindow.hidden = NO;
複製代碼

雖然設置本身的 hidden 便可顯示出來,但上述方法並不會"自動"影響以前顯示的 UIWindow 對象的 hidden 屬性。若是,以前 UIWindow 的 hidden = NO,設置新 UIWindow 的 hidden 將舊 UIWindow 覆蓋後,舊 UIWindow 的 hidden 屬性依舊爲 NO。

  • 若是僅僅想隱藏一個 UIWindow
customWindow.hidden = YES;
複製代碼

若是你沒有專門設置過 hidden 屬性,系統默認爲 YES。上述代碼會將 UIWindow 絕對隱藏,無論有沒其餘 UIWindow 覆蓋。當也沒有其它非隱藏的 UIWindow 的時候,APP 屏幕徹底黑屏。

  • 若是想顯示一個UIWindow,同時設置爲keyWindow,並將其顯示在同一windowLevel的其它任何UIWindow之上
- (void)makeKeyAndVisible
複製代碼

上述方法真的會將其顯示在同一windowLevel的其它任何UIWindow之上!顯示最上層的UIWindow以最後執行過該代碼的UIWindow爲準。

  1. 顯示相關方法:makeKeyAndVisible 的做用
[self.window makeKeyAndVisible];
複製代碼

其執行效果包括 但不限於 執行了以下代碼(由於還會覆蓋同 level 的全部 window):

[self.window makeKeyWindow];
self.window.hidden = NO;
複製代碼

講真,makeKeyAndVisible 真的會自動改變 hidden 屬性值爲 NO。

  1. UIWindow 對象的 hidden 屬性默認值
  • 默認值:true

若是你僅僅建立一個 UIWindow,而又不專門設置 hidden 屬性(或者makeKeyAndVisible),系統默認分配的默認值爲true。

  1. 誤區:關於 keyWindow 的混淆易錯點

設置 keyWindow 與否並不影響視圖層級顯示,僅來接收鍵盤及其它非觸摸事件。若是沒有專門設置過 keyWindow 的 hiden 爲 NO,並且也沒有其它非隱藏的 UIWindow,那麼APP會黑屏。

  • 若是僅僅設置爲keyWindow
- (void)makeKeyWindow
複製代碼
  • 若是僅僅解除爲keyWindow
- (void)resignKeyWindow
複製代碼

app 的 keyWindow 與是否在最上層顯示沒有任何關係。好比,你若是想經過 [[UIApplication sharedApplication] keyWindow] 獲取正在顯示的 UIWindow 是極其不許確 的。有時候經過這個代碼獲取的若是真的是正在顯示的 UIWindow,僅僅是由於碰巧而已。

  1. 警戒點:有多個 hidden 屬性 =NOUIWindow,該顯示誰?

如上所見,makeKeyAndVisible 與 hidden 的 setter 方法都可以改變 hidden 的值,但有個問題,通過屢次調整,可能有多個 UIWindow 的 hidden 都爲 NO,那麼應該顯示誰?

  • 對於 hidden 的 setter 方法,最終顯示的以最後執行過 .hidden=NO 的 UIWindow 爲準,且執行 .hidden=NO 以前 hidden 的值爲 YES。(hidden若是是從NO改成NO的不 算 最後 改變UIWindow的顯示狀態)
  • 對於 makeKeyAndVisible 方法,最終顯示的以最後 執行過 makeKeyAndVisible 的 UIWindow 爲準。
  • 對於前後分別用 makeKeyAndVisible 方法和 hidden 的 setter 方法,仍是前後分別用 hidden 的 setter 方法和 makeKeyAndVisible 方法,結局一樣以最後改變顯示狀態的 UIWindow 爲準。

二、基於 windowLevel,調整 UIWindow 顯示層的拓展方法

先去 UIWindow.h 裏面看看 UIWindowLevel 的定義:

例如,在手勢相關類中調整自定義的 UIWindow 層級

[self.window makeKeyAndVisible]; 
_window.windowLevel = UIWindowLevelAlert;
複製代碼
  • 打印表明 UIWindowLevelAlert 層級的數據值
(lldb) po self.window.windowLevel
2000
複製代碼
  • 同理,打印表明UIWindowLevelStatusBar層級的數據值
(lldb) po self.window.windowLevel
1000
複製代碼
  • 同理,打印表明UIWindowLevelNormal層級的數據值
(lldb) po self.window.windowLevel
0
複製代碼

小結:

  1. windowLevel 數值越大的顯示在窗口棧的越上面
  2. 顯示層的優先級 爲: UIWindowLevelAlert > UIWindowLevelStatusBar > UIWindowLevelNormal
  3. 系統給 UIWindow 默認的 windowLevel 爲UIWindowLevelNormal

UIWindow常見操做方法總結

一、獲取 App 全部 window 的 windows 數組

[[UIApplication sharedApplication] windows]
複製代碼

例如,第三方加載動畫框架 KVNProcess 中 KVNProgress.m 文件會有一段這樣的代碼,以下圖1所示

二、keyWindow

[[UIApplication sharedApplication] keyWindow]
複製代碼

例如,第三方下拉菜單框架 FFDropDownMenu 的 FFDropDownMenuView.m 文件中有這樣一段代碼:

UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
[keyWindow addSubview:self];
複製代碼

這段代碼的目的是添加到最上層 UIWindow,但實際操做是把本身的視圖添加到 keyWindow 上。其實,若是咱們在編寫代碼時嚴謹地保證 keyWindow 是顯示在最上層的 UIWindow,這樣寫沒有問題。但若是:本身或者其它第三方框架曾經調高過其它 UIWindow 屬性 windowLevel,或者有同級 windowLevel 的其它 UIWindow 後來改變過顯示狀態(如 .hidden=NO,makeKeyAndVisible 等),可能會致使下拉菜單的彈出視圖沒法顯示(被覆蓋)。

三、獲取 AppDelegate 單例的 window 屬性 專門獲取 AppDelegate.m 文件中的 window 屬性,不包含其它其定義的 window

[[[UIApplication sharedApplication] delegate] window]
複製代碼

關注咱們

歡迎關注咱們的公衆號:iOS-Tips,也歡迎加入咱們的羣組討論問題。能夠公衆號留言 iosflutter 等關鍵詞獲取入羣方式。

推薦閱讀

相關文章
相關標籤/搜索