liuzq(前端開發工程師)
本文是繼上一篇《前端學習之iOS開發(一)》的續集,上一篇屬於iOS開發的入門篇,主要內容爲前端學習iOS開發的優勢和對比學習。本文的內容爲介紹在新的項目《有道口語大師》中的實際應用,主要包括第三方類庫管理工具pods、sqlite、文件操作、網絡請求、引導動畫等在項目中的實踐。
一、第三方類庫管理工具CocoaPods
CocoaPods作爲iOS開發的第三方類庫管理工具,可以很好的解決第三方類庫的檢索、安裝、更新等操作,功能與nodejs中的npm類似。
CocoaPods的安裝和使用如下:
1、安裝好Ruby環境
2、下載和安裝命令(速度較慢):sudo gem install cocoapods
3、在與.xcodeproj同級目錄下建立Podfile文件,文件內容如下:
文件中的內容用於指示下載哪些類庫及指定類庫版本號,例如,下載版本2.2.4的AFNetworking,不寫版本的時候默認下載最新的版本,文件的意義同npm中的package.json。
使用 pod install 下載類庫,使用該命令後會根據Podfile中的列表下載需要的類庫。
類庫下載過程中截圖:
4、類庫下載結束後會有如下提示
.xcworkspace文件是使用pod install後生成的新文件,在我們項目的根目錄下,接下來就是使用.xcworkspace文件來打開我們的項目了,而不是使用.xcodeproj。雙擊.xcworkspace打開項目文件後會看到如下的目錄結構:
其中紅色部分就是我們通過pods下載的類庫了,以AFNetworking類庫爲例,可以通過#import “AFNetworking.h”引入下載後的類庫。
另外CocoaPods支持搜索命令,例如在終端中輸入命令‘pod search AFNetworking’,便可以獲得AFNetworking類庫的各版本信息,這一點和命令‘npm search ’功能相同。
5、 CocoaPods使用總結,例如Podfile中的列表,大家可以看到下載了很多類庫,但如果下載後的類庫都添加到svn版本庫中,這無疑會使我們的項目過於龐大,並且不利於其他同事checkout和管理,但我們可以不將下載後的類庫添加到svn版本庫中,只需添加PodFile到版本庫中,這樣需要checkout項目的同事就可以根據PodFile瞭解項目中使用的類庫並通過pod命令下載依賴的類庫。
關於CocoaPods的詳細介紹和其他命令可以參見:https://github.com/CocoaPods/CocoaPods/wiki和http://cocoapods.org/。
最後前端開發者是不是覺得CocoaPods與npm有異曲同工之妙呢。
二、文件管理
1、NSBundle和NSDocumentDirectory
對於App有兩個存儲文件的目錄結構,分別爲:NSBundle和NSDocumentDirectory。
NSBundle目錄中包含了程序會使用到的資源(圖像、音頻、視頻、json文件等),這些資源都是隨app第一次安裝時進入到用戶的手機中,並且存貯在app的安裝包中。NSBundle目錄是隻讀目錄,也就是說在用戶安裝NSBundle後,裏面的資源文件就不可修改了。所以如果資源文件是存儲在NSBundle目錄中,app上線後就算修改一個圖片資源也要發佈新的app版本。
NSDocumentDirectory是用戶手機中應用程序沙盒中的目錄,可用於存儲在用戶安裝app後動態生成的文件,例如數據庫文件,用戶頭像,從網絡下載的其他資源等。下圖爲iOS模擬器中看到的《有道口語大師》應用程序沙盒中的目錄結構:
其中data文件夾下存儲的是語音評分的模型文件,lessonData文件夾下用於存儲app中使用的數據包(json文件、圖片、音頻),OperationGifImages用於存儲app中運營活動的圖片文件,pandaDB用於存儲用戶數據庫和頭像,pandaResources用於存儲即時更新的圖片資源,從文件夾的描述上來看,大家應該明白NSDocumentDirectory目錄與NSBundle目錄的區別在於,前者用於存儲動態更新的文件,因爲前者是可以讀寫的,所以數據庫文件和即時更新的文件都可以在這裏存儲。
在應用程序沙盒中除了Documents目錄,還有Library目錄,及tmp目錄,但這兩個目錄都只適合臨時存儲。另外在向Documents目錄下存儲文件的時候一定要建立文件夾不要讓文件散落到目錄下,不然提交app的時候會被appleStore駁回。
下面簡單介紹下如何獲取到NSBundle和Documents目錄,由於都是在api中有詳細的講解,這裏只是想讓初學者有個簡單的瞭解:
(1)、獲取NSBundle目錄下的名爲lessonData的壓縮文件:
(2)、獲得Documents根目錄:
2、文件操作
這裏不會重點講文件的增刪改查,對於增刪改查操作只是一套api而已,讀者可自行深入學習。這裏主要講爲什麼要將NSBundle中的部分資源文件拷貝到NSDocumentDirectory目錄。這樣可以讓讀者設計app的時候有更多的考慮。先看下我們的需求,在用戶隨app安裝的時候會在NSBundle目錄下存儲一個lessonData.zip文件,該文件中包含json、圖片、音頻等資源文件。而且這些資源文件在app上線後會經常替換的,顯然存在NSBundle目錄下是不能達到即時更新的效果,所以我們要在app安裝過程中將lessonData.zip文件解壓到NSDocumentDirectory,這樣做的好處是當json文件或者圖片資源等有更新的時候可以通過網絡請求立即更新NSDocumentDirectory中對應的文件。代碼如下:
其中ZipArchive爲用於解壓的類庫,可參見https://github.com/mattconnolly/ZipArchive。這裏有個需要注意的地方是調用UnzipOpenFile打開文件之後一定要調用CloseZipFile2關閉文件,否則會引起內存泄露。
對於文件的增刪改查可以通過下面的代碼稍作了解,其中NSFileManager是Foundation庫中負責處理文件的類。
(1)判斷文件是否存在,不存在時創建:
(2)判斷文件是否存在,存在時刪除:
(3)循環拷貝NSBundle目錄下文件到NSDocumentDirectory目錄:
圖中最後一條語句便是拷貝方法,但需要在拷貝前先判斷目標文件是否已經在目標目錄,如果文件已存在時進行拷貝會引發錯誤。
(4)要獲取json文件中的內容可以通過如下方法:
三、網絡請求(AFNetworking)
對於網絡請求前端開發者應該很瞭解了,前端中可以使用ajax來實現異步請求。這裏使用的是AFNetworking庫。AFNetworking 是ios和mac os x下的網絡框架,它是構建在Foundation URL Loading System之上的,封裝了網絡的抽象層,可以方便的使用,AFNetworking是一個模塊化架構,擁有豐富的api。支持當前網絡連接情況判斷、GET、POST、POST Multi-Part格式的表單文件上傳、下載及斷點續傳等操作。
分別用ajax和AFNetworking庫實現POST請求的對比:
ajax:
AFNetworking:
可以看出使用方法上與ajax並無太多區別,只不過AFNetworking的方法名稱等過於複雜,並在成功和失敗的處理函數上使用的是object-c特有的代碼塊方式(block)。
這裏着重講下項目中利用AFNetworking完成下載數據壓縮包的例子:
上面gif圖中展示的邊下載邊顯示下載進度的效果就是使用AFNetworking實現的,並且在下載前可以利用AFNetworking中的AFNetworkReachabilityManager先判斷網絡情況,然後在利用AFDownloadRequestOperation實現下載及獲取下載進度的功能。
判斷網絡情況:
下載並獲取下載進度:
下載處理的使用和ajax方法也很類似,只是多了一個用於獲取下載進度的setProgressiveDownloadProgressBlock方法設置。
AFNetworking詳細使用可參見:https://github.com/AFNetworking/AFNetworking
四、FMDatabase
在iOS開發中我們一般使用SQLite數據庫,SQLite是一款輕型的數據庫,是遵守ACID的關係型數據庫管理系統,它的設計目標是嵌入式的,而且目前已經在很多嵌入式產品中使用了它,它佔用資源非常的低,在嵌入式設備中,可能只需要幾百K的內存就夠了。對於這種不需要存儲在web服務器上的,被稱爲“SQLite”的文件型數據庫目前已被廣泛使用,前端開發者應該並不陌生,因爲HTML5的本地存儲中使用的也是SQLite數據庫。即使沒有使用過也不必擔心,對於iOS開發中使用的SQLite還是比較簡單的,基本的sql語句就可以滿足了 ,所以這裏不需要有太多的數據庫使用經驗,對於一般項目來講,大學的知識也就足夠了。iOS中原生的SQLite API在使用上非常不便。於是,就出現了一系列將SQLite API進行封裝的庫,例如FMDB、PlausibleDatabase、sqlitepersistentobjects等,FMDB是一款簡潔、易用的封裝庫,在《有道口語大師》項目中也是使用了FMDB。這裏進行簡單的介紹。
在下載FMDB文件後,工程中必須導入如下文件,並使用 libsqlite3.dylib 依賴包。
FMDB目錄結構:
對於FMDB目錄中的這三個文件做下功能描述:
FMDatabase : 創建SQLite數據庫
FMResultSet :獲取執行sql查詢後的結果集
FMDatabaseQueue :在多個線程執行查詢和更新時會使用這個類。
例如創建數據庫:db = [FMDatabase databaseWithPath:database_path];其中database_path可以是第二小節講到的應用程序沙盒中documents下的一個路徑。
打開和關閉數據庫:[db open]、[db close]
創建表:
可以使用FMResultSet對象的resultDictionary將結果集轉爲Dictionary:
通過intForColumn、stringForColumn、longForColumn等方法獲取某一字段的值:
在使用過程中一定要注意[db open]和[db close]的配對使用,否則會造成內存泄露引起app崩潰。尤其注意某些數據庫操作函數的嵌套使用。例如:
這段代碼存在兩個問題,在saveMissionScore:(NSString*)mid score:(NSNumber*)score方法中已經執行了[db open],但在unlockMissionId方法中又執行了[db open],另外在unlockMissionId中還執行了[db close],在unlockMissionId方法執行完畢時數據庫已經處於關閉狀態了,這時又去執行了sql操作,前面這兩點都會引起數據庫操作異常從而引起app崩潰。所以一定要確保[db open]和[db close]的配對使用。
開發者可以使用SQLite Professional、火狐瀏覽器的SQLite Manager等工具進行sql語句的驗證及查看數據庫文件。
對於數據庫的增刪改查語句這裏就不展開介紹了,可以通過https://github.com/ccgus/fmdb和http://www.sqlite.org/inmemorydb.html去查看。
五、引導動畫
目前很多app在啓動後都會有一個引導動畫,很多比較複雜和絢麗的動畫會在一開始就吸引住用戶。這一節主要是想通過app啓動時的一個引導動畫來學習iOS開發中的UIScrollView和動畫的實現過程。先看下《有道口語大師》啓動的引導動畫:
實現分析:
1、對於UIScrollView的理解,前端人員可以理解爲一個未設置overflow:hidden的div,當內容溢出容器後可以滾動顯示出溢出的內容。
2、當我們想要知道div的滾動距離的時候肯定要監聽div的scroll事件,在iOS的這個例子中當實現了UIScrollView的UIScrollViewDelegate協議後,我們就可以監聽到UIScrollView的一切動作,包括橫向、縱向滾動,及滾動的開始和結束。
3、通過如下設置便可實現UIScrollView的分頁,假設設置分爲3頁:
其中guideControl是動畫下面顯示的分頁器。
4、在前面3點介紹的iOS相關內容之後,剩下的就是我們前端擅長的動畫操作了,只要實現UIScrollViewDelegate的scrollViewDidScroll:(UIScrollView *)scrollView方法,就可以監聽到滾動的長度,該長度通過方法中的scrollView.contentOffset.x獲得,然後通過該值來做相應的動畫變化。其中涉及到的動畫效果有CGAffineTransformMakeScale(縮放)、CGAffineTransformTranslate(移動)、CGAffineTransformRotate(旋轉),這幾個動畫效果在CSS3中也有對應的實現方式,分別爲:transform:scale(0.8,1)、translate(50px,50px)、transform:rotate(45deg),所以對於前端開發者並不難理解這些動畫實現,接下來我們是不是也可以在webApp中實現一些native可以做的動畫效果呢!
由於動畫的代碼還挺多,這裏不貼出來了,可以看下如何通過scrollViewDidScroll:(UIScrollView *)scrollView方法獲取到橫向滾動距離,代碼如下:
5、值得一說的是第二針的實現,讀者仔細看第二針有很多小元素圍繞着手機圖片,如果這些小元素都拆分開再通過改變位置的方法實現顯然不是最好的解決方案,代碼會很複雜。那最後選擇的方案是用一張大圖,大圖中只包含圍繞着手機的小元素,並且將圖片通過CGAffineTransformScale放大到8倍(這裏的8倍是試出來的,主要是放大到足夠大後保證小元素不會在屏幕上顯示出來),滾動過程中再通過滾動的距離和圖片放大後的寬度比例縮小圖片,直到縮小到0。
六、使用GCD實現多線程(Grand Central Dispatch)
這個小節主要講項目中一個例子,運用GCD實現異步操作。關於GCD的概念如下:
Grand Central Dispatch或者GCD,是一套低層API,提供了一種新的方法來進行併發程序編寫。從基本功能上講,GCD有點像NSOperationQueue,他們都允許程序將任務切分爲多個單一任務然後提交至工作隊列來併發地或者串行地執行。
與HTML5中的Web Workers目的相同,我們使用多線程大多數目的都是爲了將某些耗時較長的處理交給後臺線程去運行。例如下面這段js代碼:
當num的值爲100億(不同的瀏覽器中有所差別)以上的值時,瀏覽器會彈出一個腳本運行時間過長的對話框,從而不得不終止當前計算。但如果將上述代碼放在一個單獨的js文件中,並通過Web Workers去執行,並在執行完畢後通過postMessage方法將執行結果發送給主線程,這樣無論num爲多大的值都可以正常計算並且不會影響用戶的其他操作了。代碼如下:
新建一個用於計算的sum.js:
在主線程中創建子線程並調用sum.js計算:
通過上面的對比例子之後,我們看下《有道口語大師》中實現清理app中的音頻和圖片資源的功能,並顯示清理進度,實現效果如下:
讀者可以根據下列代碼及註釋去理解,採用GCD的原因主要是刪除文件過程是個耗時的操作,會阻塞UI線程,所以要創建一個異步線程在後臺執行刪除操作,再刪除操作執行完畢後回到UI主線程更新刪除進度,最後當刪除操作全部完成後回到主線程中更新tableview中的被刪除行。
其中dispatch_async爲開啓一個異步操作,第一個參數是指定一個gcd隊列,第二個參數是分配一個處理事物的代碼塊到該隊列,dispatch_get_global_queue(0, 0),指用了全局隊列,一般來說系統本身會有3個隊列:global_queue,current_queue,以及main_queue。 其中第一個參數0的值也是DISPATCH_QUEUE_PRIORITY_DEFAULT值,即可以寫成:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),除此之外還有其他數值:DISPATCH_QUEUE_PRIORITY_HIGH,DISPATCH_QUEUE_PRIORITY_LOW,通過變量名成就可以理解出是設置異步隊列的優先級,優先級的定義如下:
通過以上例子,最終我可以將代碼簡化爲如下圖所示的結構:
關於iOS中的多線程,除了GCD,讀者還可以瞭解下使用NSThread、NSOperation和NSOperationQueue來實現多線程。
七、鏈式調用
前端開發者應該都很熟悉和喜歡使用jQuery的鏈式寫法,例如如下代碼:
在iOS開發中我們也可以通過返回self的方式實現鏈式調用,例如新建一個YDDictQuerier類:
現在就可以像使用jQuery一樣採用鏈式寫法了,代碼如下:
八、內存監測與優化
本小節是講在app啓動後,如何通過Xcode隨着不同的操作及app使用時間的增長來觀察app所佔用的內存。在前端開發中也有內存監測工具,例如chrome瀏覽器爲開發者內置的內存跟蹤工具,可以通過開發者工具的Profiles標籤調出,該工具可以拍下當前JS的heap快照,並可以看到closure、array、object等的內存使用情況。在前端開發中例如:閉包上下文綁定後沒有釋放,定時器的處理函數沒有及時釋放(沒有調用clearInterval方法)等都會引起內存泄露。在iOS開發中多數情況下也是因爲沒有及時釋放不使用的內存引起app崩潰或由於內存佔用過高導致app卡頓。當監測到app執行某一操作或到某一時間點的時候所佔用的內存居高不下,並持續增長的話,基本確定app是有內存佔用問題的。如下圖在xcode6中提供的app內存監測界面:
圖中顯示的是當我們進入第四節中講到的app引導動畫界面後的內存變化情況,還記得在引導頁面我們引入了一張很大的圖片吧,由於這張圖片的原因所以內存會變的很高,起初遇到這個問題,首先想到的就是在離開引導頁面的時候刪除圖片引用就可以恢復內存使用量到較低的狀態,但並沒用達到預期效果,原因是雖然此時內存會居高不下,但這種由於引入大圖片引起的內存過高是由於在引入圖片後圖片會緩存到內存中,這時即使使用代碼強制刪除圖片引用,緩存中的圖片也不會立即釋放的,但稍後iOS系統會在圖片不使用的階段自動釋放掉。但如果內存居高不下,持續增長的時候,開發者可以在對應的頁面或執行的某個操作處視具體問題來查看是否有強引用、或某些耗時操作等需要優化,常出現的內存過高問題一般是在某一個controller或者view組件被移除或不需要引用的時候對應的dealloc方法由於強引用或其他原因沒有執行引起的,可以通過在dealloc方法中設置斷點或者打印log的方式查看相應的controller或view是否被釋放,如果是強引用引起的視情況改爲弱引用,或將耗時、耗內存的操作用上一節講到的異步線程處理。
到這裏本篇文章就結束了,主要介紹的是實現思路和類庫的使用,並通過與前端開發進行對比學習加深理解和記憶,但並沒有詳細到代碼級別,感興趣的同學可以自己邊看api邊寫demo來進一步學習。