Erebor 筆記

第二版的みんなのシストレ在8月份上線之後,iOS客戶端可謂是問題不斷,故事應該是從RPC請求成功後菊花消不掉說起。先來說說我廠的iOS網絡請求框架,這個框架個人認爲也是有很多賣點的,由於我們與服務器通信是由NSStream流支持的,和AFNetworking不同,我們這個底層的NSStream流存在很多的狀態,根據這些狀態可以保證流從建立到斷開再到建立這樣一個無縫的操作,也就是說如果當前正在建立流的時候需要斷開,在流成功建立後會立即斷開,反過來也是,另一個標杆項是當前的流正在建立的時候,可以讓發送到RPC層的請求等到流建立成功之後再發送,這樣在TCP鏈接成功後不會損失這期間發送的消息。

在發送請求的時候,會有一個ProgressManager來控制從RPC層回來的消息或者錯誤信息,用戶看到的就是一個大菊花罩在屏幕上(不管調用多少次,都只有一個菊花,我們通過其它手段控制菊花的消失),這時用戶無法進行其它操作,每次調用ProgressManager的時候都需要判斷流的狀態,需要的話會與服務器建立連接,之後發送請求、等待消息、錯誤處理。

第一個嚴重的問題就是在項目上線的時候,發現這個菊花消不掉,經過調查發現是底層RPC的狀態切換存在問題,導致reading線程已經掛掉,而writing線程依然在等待reading線程建立連接。這種問題是比較噁心的,不但不崩潰,還需要用戶眼睜睜的看着菊花然後手動將程序退出,當然在這期間可能會有很多在等待回包的線程。

第一個問題算是修好了,那麼原來那些沒有發出去的消息,現在可就都能發出去了,所以Flurry後臺看到了幾個崩潰的信息,我們系統的midapi日誌也發現了一堆的OVERLOAD(單位時間內請求數量過高)警告。

崩潰的問題都是出現在多線程上,很多屬性沒有加Atomic修飾符,導致線程間的資源競爭,而後一一修復。

對於OVERLOAD其實是解決了好多遍,直到現在還有這個問題,我們程序內有一個拉取客戶資金信息的statement請求,由於需要計算取引可能目安和顯示,並且statement是隨着持倉、匯率實時變化的,我們做了一個定時器,每隔一定的時間去請求,一開始不知道爲啥OVERLOAD總報在這個statement請求上,但是通過日誌觀察都是用戶頻繁的切換tab造成的,又由於statement請求是每個VC最後發送的,所以只能斷定該警告是由於用戶無聊導致的。

萬事都是橫看成嶺側成峯啊,我們的請求都是在viewWillAppear發送的,一開始的時候,iOS的VC需要該statement信息的頁面都會起一個timer來拉取這個數據,但是發現當用戶進行頁面切換的速度太快,也就是說系統只調用了viewWillAppear,再點擊tabbar的按鈕頁面就被切換到其它頁面了,這時viewDidAppear就沒有觸發,當然在viewDidDisappear中取消timer的方法也就沒有執行,所以一個程序中可能同時存在多個statement的timer在拉取數據。之後通過一個單例來拉取這個statement就放心了,這個看似很不錯的解決方法,直到重新設計了MVC。

重新設計MVC是參考了安卓版的程序,主要的目的就是要保證model的完整性,也就是說假如一個VC的model需要請求三個接口,model的完整性指的是這三個請求的數據都成功返回後才更新VC,若其中任意一個請求失敗,都不能更新界面,這樣是爲了避免用戶選擇了USD/JPY但是匯率確是EUR/JPY的,這就要用到copy機制,只要VC需要重新訂閱信息,就需要將當前VC的model拷貝一份,然後設置請求需要的信息,再發送,如果這個copy的model所有請求成功,需要覆蓋原來的model,失敗的話只是提示一下,並不更新頁面。

那麼接着上面的問題繼續說,用於獲取statement這個單例本身也是一個model,並且屬於一個VC,這樣的話,單例就copy出來好多份(這個當時沒有注意到),後來讓這個單例繼承自NSObject就修改好了。再一次上線。

這次上線發現Flurry要麼就不上報crash,也麼就是沒法符號化的crash文件,OVERLOAD的警告依然存在(這次不是關於statement的了),多了頂一個警告,socket鏈接成功後沒有進行doConnect(一個鏈接的請求)的操作,導致某些接口不正常(尚未修復)。

Flurry的問題在DEV環境下測試崩潰的時候也沒有上報,UAT環境下也不上報,後來負責人諮詢Flurry的工作人員,人家給了個新版的SDK,需要升級一下,經過測試,crash信息的確上報了。

這次的OVERLOAD是關於拉取Product信息的,一秒內發送了好多次,在丟包、延遲高的網絡環境下可以重現。由於外匯軟件沒有Product就像是沒有發動機的車一樣,Product定義了交易量、註文類型的基本信息,所以設計的時候就考慮到其重要性,所有的後續操作都需要在成功拉取了product信息之後才能進行,所以在拉取Product信息的時候做了個半閉半開的環,如果請求失敗就繼續請求,但是在請求之前還要先判斷網絡的鏈接狀況,stream流建立成功後也要發送一次loadProduct操作,如果頻繁的建立鏈接就會產生雪崩效應,不僅如此,程序內如果從服務器獲取數據失敗會給出提示框,提示框有一個按鈕,點擊按鈕後會執行相關的操作,對於loadProduct這個相關操作就是在拉取一次product信息,偏偏樹欲靜而風不止,該提示框只能有一個,當前如果窗口中有一個提示框的話,下一個提示框dismiss掉當前的提示框,並將之前提示框要執行的相關操作保存在數組中,當點擊按鈕的時候會依次執行數組中的block。經過調查發現,在流報錯的時候會意外的將connectFuture置成nil,而這個connectFuture其實是與NSStream流的狀態綁定的,當鏈接建立成功後或者斷開鏈接的時候,才能置成nil,而不是在報錯的時候,除此之外,將保存提示框block的數組做了調整,在針對類似Product這類請求的時候,將block放入字典中,以類型作爲key,這樣就排除發送多次product的請求。再次上線新版本。

天有不測風雲,Flurry後臺一下報了將近十多個crash,其中一個還是程序內沒有注意到的多線程問題,其餘的都是崩潰在了Flurry SDK內部方法上,經過調查發現,在Flurry的GitHub上,關於新版本的issue中很多人都在討論這個問題,YAhoo的Flurry小組說大概會在11月15日修正,但是等不了,只能將Flurry版本降回原來的版本,發現竟然又能上傳崩潰信息了(此處省略一萬字吧。。。),再上一版。

又發現OVERLOAD的警告了,還是那個熟悉的警告,但已不是它的背影而已,這次是由於loadModel造成的,程序內進行loadModel的操作比較多,VC註冊了一個從後臺回到前臺的通知以及連接建立成功的通知,chart頁面額外還有一個Product拉取成功的通知,都要進行loadModel操作。

09-55-57.jpg

由於進行loadModel時,model都是要copy的,所以無法從model的狀態判斷當前是否正在經進行loadModel操作,從上面的圖中可以看到,從後臺切換回前臺或者鏈接建立的reset操作,loadModel與product的過程其實是相同的,所以將上圖改爲下圖(簡易版):

09-59-40.jpg

在loadProduct操作成功後在進行loadModel來作爲其reset的機制,這樣的話從後臺切換回前臺的時候網絡條件好的情況下就只調用一次,接下來就是要控制reset方法內請求的調用次數了,這就比較好辦了通過開關或者其他方式都很簡單。

但是新問題又來了,(還是網絡狀況不好的情況下)我們程序的啓動過程如下:

1. 創建一個SplashVC。

2. 將rootViewController設爲SplashVC,並檢查版本信息

3. 實例化tabbar,將rootViewController指向TarbarController

4. 對tabbar的select 頁面加載數據。

正常情況下,凡是用ProgressManager打包的請求頁面都會有大菊花,用戶是不能有其它操作的,但是問題其實出在了第2、3步,由於大菊花是加在window上的,請求都是在viewWillAppear發送的,也就是說菊花先加到window上了,後來纔有的viewDidAppear,所以根本看不到菊花,因爲菊花被蓋在SplashVC.view下面了,包括後續的操作(之前說過ProgressManager的菊花多次請求只加在window上一次)用戶都自由了,直到某次請求執行完,菊花會hide,下次再加載菊花的時候,菊花纔會覆蓋在window的最上層,沒有了菊花的保護,用戶就會像那首滄浪之歌一樣,在那瞭望無邊的草原上搖擺了。這也就解釋了之前爲什麼服務器會報loadChart的警告,該警告的意思就是沒有傳入(或者傳入了0)productID,服務器不知道該給客戶端那個貨幣對的chart數據。可選的兩個方案:

1. 執行完2-3步的時候,需要將rootViewController的view sendback.

2. 在progressManager的菊花上加一個tag,再執行2-3步的時候判斷一下即可。

。。。待續
附帶:弱網、丟包環境配置信息


IMG_0009.PNG
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章