開源專案Running Life 原始碼分析(一)

發表於2016-09-14

專案簡介

基於HeathKit和高德地圖開發健康跑步App,實現實時繪畫運動軌跡、健康資料管理功能。
做這個專案出於兩個原因:
1、喜歡跑步(也為了減減肥);
2、喜歡運用自己的知識實際,裡面有我自己寫的一些開源元件( 技術有限,設計得不好的地方,大家多多指導);

執行效果如下:

開源專案Running Life 原始碼分析(一)
圖片標題

專案目錄

111fd8fe6f59cef14dde334b328e2f7667
title

Config目錄:介面配置檔案、巨集定義和標頭檔案配置檔案;
AppINit目錄:關於App的啟動設定,如第三方SDK初始化、介面初始化、HeathKit初始化配置;
Module目錄:業務模組,由以下這幾個模組組成:公共模組、跑步模組、記錄模組、個人模組、設定模組、登陸註冊模組;
Resource目錄:圖片資源和字型資源;
RunKit目錄:一些類的擴充、工具類、網路層方案、持久化儲存層方案;
Vendor目錄:一些不支援Cocoapod第三方庫;
Pod:支援Cocoapod第三方庫;

業務層架構

124197db2f9bc8d42034917e1a760f479a

MVVM架構(使用Facebook的KVOController實現view和viewModel的繫結,專案往ReactCocoa遷移中)

viewModel如何設計?

viewModel負責從原始資料來源獲取原始資料,運用對應的資料處理邏輯,轉化為view層顯示的資料。他不引入UIKit相關類,所以他與UI無關,也方便我們進行單元測試。實際上,它就是一層function core,理想上對於相同的輸入會匯出相同的結果。所以viewmodel得設計主要包含三部分內容:輸入、輸出、命令,簡化成函式表達就是y = f(x),f函式指的是命令,x是輸入,y就是輸出,這裡要注意輸出對外界來說只是一個只讀屬性。示例如下:

viewModel與view如何繫結?

繫結的目的就是為了解決view與viewModel通訊的問題。MVVM天然最好的繫結機制就是Facebook的ReactCocoa,它是函式式響應式程式設計思想的一個體現,它的核心就是響應資料的變化、統一非同步程式設計模型,繫結的具體做法就是view層通過訂閱viewModel上面的訊號,先模擬處理一遍,這裡模擬的意思是先從腦海裡過一遍邏輯,實際不響應,當有訊號發過來的時候才實際觸發。
但是他需要一定的學習成本,學習成本較大,本人也在不斷學習當中,所有我們換種方式來實現這種響應機制。想一下,cocoa中是不是有提供這種監聽-響應的機制,沒錯,就是KVO,但是原生KVO寫起來會噁心死人,所有我們可以藉助Facebook提供一個KVO框架(kvoController)來實現優雅的繫結。(Facebook真是為了iOS的開發做出很多貢獻,開源了那麼多好用的工具)。
繫結方式就是view層 kvo viewModel層的readonly屬性,一旦屬性變化就觸發響應的處理邏輯。示例如下:

功能實現

專案搭好條條框框,現在來分析具體的功能實現。本專案有兩個功能,一個是跑步,另外一個就是記錄,每個大功能點下又分幾個小功能點,功能的示意圖如下:

13ba9d97fde99ee5b4539fea48dde26af8

跑步

這裡主要分析跑步過程的具體邏輯,介面如下:

14dd0fedfe024ccec3fca75b7f517a618b

原始碼在這個檔案:”NewRunViewModel.m”

跑步資料來源

跑步資料來源定位,這裡定位SDK選擇高德SDK,雖然原生也是高德地圖,但經過測試發現原生的定位很不準,我也不知道具體原因是什麼。
定義一個定位管理器,設定好相應的配置引數,因為為了跑步資料的精確度,所以將定位的準確度設定為最好,呼叫 [self.locationManager startUpdatingLocation]開啟持續定位,具體實現如下:

定位成功後會不斷的回撥AMapLocationManagerDelegate的- (void)amapLocationManager:(AMapLocationManager )manager didUpdateLocation:(CLLocation )location方法,並不是所有定位資料都是有效,需要對資料進行過濾,過濾的依據就是horizontalAccuracy和時間偏差。horizontalAccuracy表示水平準確度,這麼理解,它是以定位點為圓心的半徑,返回的值越小,證明準確度越好,如果是負數,則表示corelocation定位失敗,我們知道GPS訊號會受地域的影響,有時強,有時弱,設定30是一箇中和的做法,因為我們不能保證每次定位回來的資料都是絕對精確,如果設定得太小,可能過濾得到的資料很少,太大就會誤差太大。howRecent用於計算定位結果與當前時間偏差,如果偏差超過2秒就過濾,這個2秒也是一箇中和值。過濾完資料就可以計算跑步的距離,儲存在_distance這個全域性變數中。

獲取資料之後怎麼實時重新整理UI呢?
我的做法是在NewRunViewController開啟一個定時器,時間間隔是1s,每隔1秒往VM傳運動時間,運動時間相當於函式的自變數,經過VM處理後,它會給C發資料改變的訊號,訊號相對於函式的因變數。實現如下:

智慧判斷跑步狀態

通過CMMotionManager(是蘋果的運動管理器框架,可以獲取裝置加速計、陀螺儀的即時資料)來智慧判斷跑步狀態,以決定是否繼續記錄。這個功能點體現在當使用者運動幅度變小的時候,小到一定程度的時候,app就判斷使用者處於休息階段,當這個階段持續超過8秒就暫停跑步記錄,進入以下狀態:

16ac737cab4b028e368e98c3d09cb46d9b

但使用者又開始運動的時候,運動幅度到達一定程度的時候,有開啟跑步狀態。
它的實現原理是通過陀螺儀來實現(暫時還沒適配舊版本手機,因為iphone5以下沒有陀螺儀),一般我們跑步的時候,手機拿在手上或者放在褲袋裡,所以y軸和z軸偏移最大也最頻繁,所以通過判斷y軸和z軸的加速度,如果他們的加速度小於2,則使用者不處於跑步狀態,這個2的值是自己試出來- -,如果大家有更好的依據歡迎到github issue我。具體實現如下:

運動軌跡

我將運動軌跡的繪畫邏輯分離到MapViewController中,裡面也有一個定位管理物件,定位成功也會不斷的回撥,相比NewRunController回撥的處理,這裡的處理多了對地圖的處理,通過兩個座標確定一條線,並把線新增到地圖上,程式碼如下:

通過mapView的一個delegate方法設定軌跡的相關屬性

儲存跑步記錄

資料儲存在本地資料庫中,出於學習的目的,我這邊持久層選擇了CoreData,它是蘋果推薦的持久層儲存框架,底層是sqlite,做了物件導向的封裝。上手有點難度,需要一定的學習成本,關於CoreData的具體使用,大家自行Google或baidu,在這裡就不展開將。我們通過.xcdatamodeld可以十分方便地建立我們的實體物件,該專案主要有兩個實體物件:跑步記錄、實時位置資料,兩者是有關聯的,一次跑步資料關聯著一系列實時位置資料。

17d79039cbfa3031670b4ced810070c3e3
1834843d89124bc5fe82600075074e58ae

當時在設計資料儲存方案的時候,遇到這樣一個問題:
如果使用者沒登入就發起跑步,跑步結束後資料插入到資料庫,這些資料是沒有使用者認領的。當使用者登陸的時候,這部分無使用者態的資料該如何處理。當使用者退出登陸的時候,原有記錄的資料是儲存還是清除?儲存又該如何處理呢?
後來我參考了Nike的Running的處理邏輯,一旦登陸使用者,這些無使用者態的資料就被登陸使用者認領,退出登入資料儲存在本地。
既然處理邏輯想好了,這麼資料儲存方案要如何讓設計呢?
大家可以看我基於CoreData封裝的CoreDataManager:

為了讓大家更好地瞭解這個方案,我普及一點點CoreData的知識,CoreData框架包含三層內容:
1、底層資料庫;
2、持久化儲存助手,作為業務層與持久層的協調物件,負責從資料庫獲取資料並返回適合的資料給業務層:
3、管理上下文物件,參與具體的業務互動;
一個資料庫對應一個上下文物件,所以我的方案設計了兩個上下文物件,一個對應著存放臨時資料的資料庫,另一個對應存放使用者資料的資料。tempManagedObjectContext主要作用是為了獲取臨時資料用於合併資料庫,平時業務互動直接用managedObjectContext就行,因為底層會根據當前活躍的資料庫切換相應的上下文物件。切換資料庫的實現原理:

用一個static變數存放資料庫的名字,資料庫的命名規則是以使用者的賬戶名的MD5雜湊值作為使用者的資料庫名。因為切換了資料庫,上下文物件改變了,持久化儲存助手也改變,因為兩個都是懶載入,置為nil,到時會重新呼叫他們的getter方法,getter方法內部根據對應的DBNAME建立相應的物件。
切換資料庫的應用場景有三個:app初始化的時候、登陸的時候、退出登陸的時候。

跑步結果

效果如下:

1980f75c13c3d879de09e4ade0e434350c
這邊有個功能點就是根據不同速度繪畫不同顏色的運動軌跡。
實現原理:建立一個MKPolyline(地圖軌跡類)的派生類MultiColorPolyline,該類多了一個屬性color,用來記錄當前軌跡的顏色。將普通的軌跡轉化為帶顏色的軌跡實現邏輯放在MathController這個轉換的工具類中,具體程式碼如下:

遍歷獲取最大速度和最小速度,根據速度與最大速度和最小速度比較,設定一個比例,根據比例調配相應的顏色,顏色的計算演算法如上,就不展開講了。

小結

今天分析了大體框架和跑步模組一些細節的實現,關於記錄模組的分析我打算放在第二篇來分析,先做下預告,內容主要有三個:
實現view的複用機制解決記憶體暴漲問題、貝塞爾曲線與動畫實現一個優雅的資料展示介面、HeathKit框架的使用。
專案地址:github.com/caixindong/Running-Life—iOS,有問題歡迎大家提出討論,大家覺得不錯,就賞個star。

相關文章