天天看點

[iOS]轉:iOS最佳實踐

原文位址:http://download.csdn.net/download/fjp1230123/9138833

  跟随iOS開發技術發展的潮流,我将持續維護本文檔的中文版,如果你喜歡這個譯本,也請給個 Star 鼓勵下~~

  本文檔的英文原版在這裡,感謝Futurice團隊卓越的工作,為我們提供這麼優質的文檔。

知識是人類進步的階梯

翻譯,喵 ~~

# iOS開發的最佳實踐

就像一個軟體項目一樣,這份文檔如果我們不持續維護就會逐漸失效,我們鼓勵大家參與到這個項目中來---僅需送出一個 issue 或發送一份 pull request

對其他移動平台感興趣?我的Andriod開發最佳實踐以及Windows App開發最佳實踐可能會幫到你哦。

為什麼寫這個文檔

iOS開發要上手比較困難,因為無論是 Objective-C 還是 Swift 在别處都沒有廣泛被應用,iOS 這個平台似乎對一切都有一套不同的叫法。當你嘗試在真機上跑程式時難免會磕磕碰碰。這份持續更新的文檔就是你的救星!無論你是Cocoa王國的新手,或是老練到隻想知道"最佳做法"是什麼,這份文檔都值得一讀。當然,内容僅供參考,你有理由采取不同的做法隻要你願意!

目錄

如果你想閱讀指定的小節,可以通過目錄直接跳轉

  1. 開始吧
  2. 常用庫
  3. Architecture 架構
  4. 網絡請求
  5. Stores 存儲
  6. Assets 資源
  7. 編碼風格
  8. Security 安全
  9. 診斷
  10. Analytics 統計分析
  11. 編譯建構
  12. Deployment部署
  13. App内購(IAP)
  14. License
  15. More Ideas(計劃)
  16. 譯者

開始吧!

Xcode

Xcode是絕大多數 iOS 開發者選擇的 IDE,也是 Apple 唯一一個官方支援的 IDE. 也有一些其他的選擇,最著名的可能就是 AppCode了。但除非你已經對 iOS 遊刃有餘,否則還是用 Xcode 吧,盡管 Xcode 有一些缺點,但它現在還算是相當實用的!

要安裝 Xcode ,隻需在 Mac 的 AppStore 上下載下傳即可。它自帶最新版的 SDK 和 iOS 模拟器,其他版本可以在 Preferences > Downloads處安裝。

建立工程

開始一個新的 iOS 項目時,一個常見的問題是:界面用代碼寫還是用 Storyboard、xib來畫?現有的 App 中兩種方式都占有相當的市場。就此我們需要考慮以下幾點:

用代碼寫界面有啥好處?

  • Storyboard 的 XML 結構很複雜,是以如果用 Storyboard ,合并代碼時很容易沖突,比起用代碼來寫,麻煩許多。
  • 用代碼更容易建構和複用視圖,進而使你的代碼庫更容易遵循 (Don't Repeat Yourself)DRY原則
  • 用代碼可以讓所有的資訊都集中在一處。但使用 Interface Builder 你得到處找對應的檢查器,作死地各種點,才能找到你要設定的屬性!
  • 代碼和 UI 通過 Storyboard 進行耦合可能會導緻崩潰,比如一個 IBoutlet 或者 IBAction 沒有被正确設定時。類似這種問題編譯器可沒法檢測。

用 Storyboard 畫界面有啥好處?

  • 對技術不太熟悉的人也可以畫:調整顔色、布局限制等等,為項目作出直接貢獻。不過做這些時,需要工程已經建好并了解一些基本的知識。
  • 開發疊代更快,因為不需要 build 工程就能預覽到作出的改動,所見及所得:
    • 自定義字型和 UI 控件在 Storyboard 中所見即所得 。這讓你在設計時能更好地了解界面的最終外觀。
    • 使用SizeClasses(iOS 8開始有效),Interface Builder (界面生成器)為你提供了一個實時的布局預覽你所選擇的裝置,包括 iPad 的分屏多任務處理。

為什麼不同時使用兩者?

為了結合兩者之間的優點,你也可以采用一種混合的方法:使用 Storyboard 繪制最初的設計,對于時不時要做出改變修修補補來說非常合适,你甚至可以邀請設計師參與到這個過程中來。當 UI 的設計更為成熟和可靠時,你再過度到代碼層進行配置,這有利于互相合作及代碼的維護。

gitignore檔案

要為一個項目添加版本控制,最好第一步就添加一個恰當的

.gitignore

檔案。這樣一來不需要的檔案(如使用者配置、臨時檔案等)就不會進入 repository(版本倉庫)了。可喜的是,Github 已經幫我們準備了 Objective-C版 和 Swift版。

Cocoapods

如果你準備在工程中引入外部依賴(例如第三方庫),Cocoapods提供了快速而便捷的內建方法。安裝方法如下:

sudo gem install cocoapods
           

首先進入你的工程目錄,然後運作

pod init
           

這樣會建立一個 Podfile 檔案,在這裡集中管理所有的依賴。添加你所需要的依賴然後運作

pod install
           

來安裝這些庫,并把它們和你的工程一起放進一個

workspace

裡。在 commit 的時候,推薦把依賴庫在你的 repo 裡安裝好之後再 commit,最好不要讓每個開發者

checkout

後還要自己跑一下

pod install

注意:從此以後要用

.workspace

打開工程,不要再用

.xcproject

打開,否則代碼編譯不通過。

下面這條指令:

pod update
           

會把所有的 pod 都更新到 Podfile 允許的最新版本。你可以通過一系列的 文法來準确指定你對版本的要求。

項目結構:

把這些數以百計的源檔案都儲存在同一目錄下,不根據工程結構來建構一個目錄結構是無法想象的。你可以使用下面的結構:

|- Models
|- Views
|- Controllers
|- Stores
|- Helpers
           

首先,在 Xcode 的 Project Navigator (左邊欄)裡,把這些目錄建立為group (小小的黃色"檔案夾"),建在工程的同名 group 下。然後,把每一個 group 與工程路徑下實際的檔案夾連結起來,方法是:

  • Step 1、選中 group
  • Step 2、打開右邊欄的 File Inspector
  • Step 3、點選小小的灰色檔案夾 icon
  • Step 4、在工程目錄下建立一個新的子檔案夾,名稱與 group 相同。

本地化

一開始就應該把所有的文案放在本地化檔案裡,這不僅有利于翻譯,也能讓你更快地找到面向使用者的文本。你可以在 build scheme 裡添加一個 launch 參數,指定在某種語言下啟動 App,例如:

-AppleLanguages (Finnish)
           

對于更複雜的翻譯,比如與名詞的數量有關的複數形式(如 "1 person" 對應 "3 people"),你應該使用

.stringsdict

格式來替換普通的

localizable.strings

檔案。隻要你能習慣這種奇葩的文法,你就擁有了一個強大的工具,你可以根據需要(如俄語或阿拉伯語的規則)把名詞變為"一個"、"一些"、"少數"和"許多"等複數形式。

更多關于本地化的資訊,請參考2012年2月的Helsink iOS會議的幻燈片。其中大部分演講至少到2014年10月為止仍然不過時!

常量

建立被 prefix header 引入的一個

Constants.h

檔案

不要用宏定義(

#define

),用實際的常量定義

static CGFloat const XYZBrandingFontSizeSmall = 12.0f;
static NSString * const XYZAwesomenessDeliveredNotificationName = @"foo";
           

常量類型安全并有更明确的作用域(在所有沒有引入的檔案中不能使用),不能被重定義,并且可以在調試器中使用。

分支模型

App釋出的時候把 Release 代碼從原有的分支上隔離出來,并且加上适當的tag,是很好的做法,對于向公衆分發(比如通過Appstore)的 app 這一點尤其重要。同時,涉及大量 commit 的 feature 應該在獨立的分支上完成。

git-flow

是一個幫助你遵守這些規則的工具。它隻是在 git 的分支和 tag 指令上簡單加了一層包裝,就可以幫助維護一套适當的分支結構,對于團隊協作尤為有用。所有的開發都應該在 feature 對應的分支上完成(小改動在 develop 分支上完成),給 release 打上 app 版本的 tag,然後 commit 到 master 分支時隻能用下面這條指令:

git flow release fininsh <version>

常用庫

一般來說,在工程裡添加外部依賴要謹慎。當然,眼下某個第三方庫能漂亮地解決你的問題,但或許不久之後就陷入了維護的泥淖,最後随着下一版 OS 的釋出全線崩潰。另一種情況是,原先隻能通過引用外部庫來實作的 feature,突然官方 API 也支援了。在設計良好的項目裡,把第三方庫替換為官方的實作花不了多少功夫,但在将來會大有裨益。永遠要優先考慮用蘋果官方的架構(也是最好的架構)來解決問題!

是以,這一章有意寫得比較簡短。下面介紹的第三方庫主要用來減少模闆代碼(例如 Auto Layout)或者用來解決複雜的、需要大量測試的問題,例如計算日期。随着你對 iOS 越來越精通,務必要四處看看它們的源碼,熟悉它們所使用的底層架構。你會發現做好這些就能減輕許多重擔了。

AFNetworking

99.95% 的 iOS 開發者使用這個庫,當 NSURLSession 自己本身也非常完善的時候, AFNetworking 仍然能憑借很多 App 需求的隊列請求管理能力立于不敗之地。

DateTools 日期工具

總的來說,不要自己計算日期。DateTools 是一個經過徹底測試的開源庫,你可以放心使用它來做這種事情。

Auto Layout 庫

如果你更喜歡用代碼寫界面,你會用過 Apple 難用的

NSLayoutConstraint

的工廠方法或者

Visual Format Language

。前者很啰嗦,後者基于字元串不利于編譯檢查。

masonry 通過他自己的 DSL 來建立、更新和替換限制,利用語言豐富的操作符重載特性較優雅地實作了 AL。Swift 中一個類似的庫是 Cartography。如果更加保守的話, FLKAutoLayout 是一個好的選擇,它為原生API添加了一層簡潔而不奇異的包裝。

架構

  • Model-View-Controller-Store (MVCS)
    • 這是蘋果預設的架構(MVC)上增加了一個 Store 層,用來吐出 Model, 處理網絡請求、緩存等。
    • 每個 Store 暴露給 View Controller 的或是

      RACSignal

      ,或是傳回值為

      void

      ,參數中帶有自定義的

      completion block

      的方法。
  • Model-View-ViewModel(MVVM)
    • MVVM 是為了解決“巨大的 view controller”而生,它把 UIViewController 的子類看做 View 層的一部分, 用 ViewModel 維護所有的狀态來給 ViewController 瘦身。
    • 對于 Cocoa 開發者是一個很新的概念,但正在引起越來越多的關注。想了解更多請參考:Bob Spry 的 fantastic introduction.
  • View-Interactor-Presenter-Entity-Routing (VIPER).
    • 頗為奇特的架構,但項目大到即使使用 MVVM 都會太淩亂并且需要重點考慮項目可測性的情況下值得參考。

"通知"模式

以下是組建之間互發通知的一些常見手段:

  • Delegate (一對一) : Apple 官方經常用的模式(有些人認為用得太泛濫了)。主要用于回傳,比如從模态框回傳資料。
  • Callback blocks(一對一) : 耦合更松,同時能讓相關聯的代碼在一起,并且消息發出者數量很多時比 Delegate 更友善。
  • Notification Center(一對多):可能是最常見的對象發送

    events

    給多個觀察者的方法。耦合性非常松 - 沒有任何對目前派發對象的引用的情況下,通知也能夠在全局範圍内被觀察到。
  • Key-Value-Observing (KVO) : (一對多)。不需要被觀測的對象主動"發出通知",隻需要被觀測的鍵(屬性)支援 Key-Value Coding (KVC)。這種模式比較含混,而且标準API比較繁複,是以一般不推薦使用。
  • Signal : (一對多)。是ReactiveCocoa的核心,它允許結合你的關鍵内容進行鍊式調用,用這種方法逃離回調深淵(嵌套過多的回調)

Models

要確定你的 Model 是不可變的,他們用來把遠端 API 的語義和類型轉換為 App 适用的語義和類型。對Objective-C來說 Github的Mantle是個不錯的選擇。在 Swift 中,你可以使用 structs 而非 classes 來確定其不可變性,并使用一個類似 SwiftyJSON或者 Argo的解析庫來做 JSON - Model 之間的轉換。

Views

今天 Apple 生态系統中豐富的螢幕尺寸及分屏多任務 iPad 的問世,使得裝置和它的構成形式之間的界限越來越模糊。就像今天的網站要預先适配不同的視窗尺寸一樣,你的 App 也應該以一種優雅的方式來處理各種螢幕的尺寸變化。使用者旋轉裝置或者在你的 App 旁邊滑動第二個 iPad App 時(分屏多任務),這種需求簡直是必須的。

你應該使用size classes和 AutoLayout 來申明你的視圖限制,而不是直接操作視圖的 frame。基于這些限制規則,系統将為視圖 計算合适的 frame 并在環境改變時(切換裝置或者分屏展示等)重新計算他們。

Apple 在設定布局限制的推薦方法中推薦在初始化方法中建立并激活你的布局限制.如果你需要動态地改變某些限制,hold 住他們的引用并在必要的時候關閉或激活他們。這主要用于在你想要系統執行批量更新以擷取更好性能的時候, 執行

UIView

updateConstraints

(或者它對應的

UIViewController

updateViewContraints

)。但這樣做的代價是你需要調用

setNeedsUpdateConstraints

方法, 這會增加代碼的複雜性。

如果你在自定義的視圖中重寫

updateConstraints

,你應該明确指出你的視圖支援基于限制的布局:

Swift:

override class func requiresConstraintBasedLayout() -> Bool {
    return true;
}
           

Objective-C:

+ (BOOL)requiresConstraintBasedLayout {
    return YES;
}
           

不然,系統可能不會如期調用

-updateConstraints

,而導緻奇怪的 bug 。這一點上 Edward Huynh 提供的這個部落格有更詳細的解釋。

Controllers

要使用依賴注入,也就是說,應該把 controller 需要的資料用參數傳進來,而非把所有的狀态都保持在單例中。後者僅當這些狀态的确是全局狀态的情況下才适用。

Swift:

let fooViewController = FooViewController(viewModel: fooViewModel)
           

Objective-C

FooViewController *fooViewController = [[FooViewController alloc] initWithViewModel:fooViewModel];
           

盡量避免在 view controller 中引入大量的本可以安全地放在其他地方實作的業務邏輯,這會讓 view Controller 變得十分臃腫。Soroush Khanlou 有一篇 很好的部落格 介紹了如何實作這種機制,而類似 MVVM 這樣的程式架構将 view controller 當 views 對待,是以大大地減少了 view controller 的複雜度。

網絡請求

傳統方法:使用自定義回調 block

//GigStore.h
typedef void (^FetchGigsBlock)(NSArray *gigs, NSError *error);

- (void)fetchGigsForArtist:(Artist *)artist completion:(FetchGigsBlock)completion;

//GigStore.m
[GigStore sharedStore] fetchGigsForArtist:artist completion:^(NSArray *gigs, NSError *error) {
    if(!error) {
        //Do something with gigs
    }
    else {
        // :(
    }
};
           

這樣雖可行,但如果要發起幾個鍊式請求,很容易導緻回調深淵。

Reactive 的方法:使用 RACSignal

如果你身陷回調深淵,可以看看 ReactiveCocoa(RAC).這是一個多功能、多用途的庫,它可以改變整個 App 的寫法。但你也可以僅在适合用它的時候,零散地用一下。

Teehan+lax以及NSHipster很好地介紹了 RAC 概念(以及整個 FRP 的概念)。

//GigStore.h

- (RACSignal *)gigsForArtist:(Artist *)artist;

//GigsViewController.m
[[[GigStore sharedStore] gigsForArtist:artist] subscribeNext:^(NSArray *gigs) {
                            // Do something with gigs
                        } error:^(NSError *error) {
                            // :(
                        }];
           

在這裡我們可以把 gig(演出) 信号與其他信号結合,是以可以在展示 gig 之前做一些修改、過濾等處理。

存儲

作為一個可以"在地面上移動"的移動應用,通常有某種存儲模型把資料儲存在某個地方,如硬碟上、本地資料庫中或者遠端的伺服器上。在把模型對象的任意活動抽象出來的方面,Store 層也非常有用。

抓取資料通常是異步進行的,但它是意味着關閉背景請求還是從硬碟反序列化一個大檔案呢?你的 Store 層的 API 必須通過提供某種延期機制反映出這種情況,就像同步傳回資料将引起線程阻塞那樣。

如果你使用 ReactiveCocoa, 通常會選擇

SignalProducer

作為傳回類型。舉個栗子,擷取某個藝術家的演出資訊将會産生下面這樣 Signature:

Swift + RAC 3:

func fetchGigsForArtist(artist: Artist) -> SignalProducer<[Gig], NSError> {
    //...
}
           

Objective-C + RAC 2:

- (RACSignal *)fetchGigsForArtist:(Artist *)artist {
    //...
}
           

這裡,傳回的

SignalProducer

僅僅是擷取演出清單的一個"配方"。僅當被訂閱者(如:一個 viewModel )啟動時才會執行擷取演出清單的實際的動作,在資料傳回前取消訂閱将會取消該網絡請求。

如果你不想使用信号、"期貨"或類似的機制來代表你未來的資料,你也可以使用正常的 block 回調。但要記住,block 塊嵌套地進行鍊式調用,如在某個網絡請求依賴于另一個的結果的情況下,就會迅速變得非常笨重 --- 這種情況通常被稱為“回調深淵"。

資源

Asset catalogs是管理你所有項目可視化資源的最好方式,他們可以同時管理通用的以及裝置相關的(iPhoen4-inch,iPhone Retina,iPad 等)資源,并且會通過他們的名字自動分組。告訴你的設計師如何添加它們,(Xcode有内建的 Git 用戶端)可以節省很多時間,否則你會很多時間從郵件或者其他管道把它們複制到代碼庫中。同時,這樣也可以讓設計師即刻看到自己的改動,可以根據需求進行疊代。

Using Bitmap Images 使用位圖

Asset catalog 隻會暴露出一套圖檔的名字,省略了每張圖檔實際的檔案名。這樣類似

[email protected]

這類檔案的命名空間僅限于 asset 内部,很好地避免了 asset 的命名沖突。然而,命名 asset 時遵循一些原則可以讓生活更輕松:

IconCheckmarkHighlighted.png // Universal, non-Retina
[email protected] // Universal, Retina
IconCheckmarkHighlighted~iPhone.png // iPhone, non-Retina
[email protected]~iPhone.png // iPone, Retina
[email protected]~iPhone.png // iPhone, Retina, 4-inch
IconCheckmarkHighlighted~iPad.png // iPad, non-Retina
[email protected]~iPad.png // iPad, Retina
           

其中的

-568h

@2x

~iPhone

以及

~iPad

這些标示符本省并不是必需的,但如果在檔案名裡加上它們,把檔案拖動到 asset 時就能自動落到正确的"格子"上,是以能避免難以察覺的錯誤拖放。

Using Vector Images 使用矢量圖

你可以把設計師設計的原始圖矢量圖(PDFs)放進 Asset catalog,讓 Xcode 來自動生成位圖。這樣能減少工程的複雜度(減少檔案的個數)。

編碼風格

命名

Apple 非常注意在 API 中保持命名一緻性,即便是非常冗長的命名也如此。做 cocoa 開發時要遵循 Apple的命名規範, 這樣能讓加入項目的新人輕松許多。

以下是幾條看了就能用上的基本規則:

以動詞開頭的方法,表示它執行的操作會造成一些影響( 譯者注:有時候是函數副作用 ),但是不傳回任何值。

- (void)loadView;

或者

- (void)startAnimating;

以下注釋來自"維基魔杖"

在計算機科學中,函數副作用指當調用函數時,除了傳回函數值之外,還對主調用函數産生附加的影響。例如修改全局變量(函數外的變量)或修改參數。
函數副作用會給程式設計帶來不必要的麻煩,給程式帶來十分難以查找的錯誤,并且降低程式的可讀性。嚴格的函數式語言要求函數必須無副作用。

任何以名字開頭的方法,應該傳回一個對象并且不能造成額外的影響 (即不帶函數副作用)。

- (UINavigationItem *)navigationItem;

+

- (UILabel *)labelWithText:(NSString *)text;

盡可能地區分這兩種方法有很多好處。比如當您轉換資料的時候就不應該造成額外的影響 ( 譯者注:即函數副作用。資料轉換的時候即上面那些使用名字開頭的方法,實際上是一種資料轉換的方法),反過來也一樣(沒有函數副作用的函數應該傳回某個對象,具體可參考嚴格意義上的函數式語言的要求)。這樣的話可以讓具有函數副作用的代碼保持在一個小的比較集中的區域内,可以幫助了解代碼并有利于 Debug.(類似我們的初始化全局變量的方法或者那些設定控制屬性的方法等)

代碼結構

Pragma marks是給方法分組很好的方法,特别是在 ViewController 中。下面是 swift/Objective-C 語言的一個在 viewController 中常見的結構:

Swift MARK 風格:

import someExternalFramework

class FooViewController : UIViewController, FoobarDelegate {
    let foo: Foo
    
    private let fooStringConstant = "FooConstant"
    private let floatConstant     = 1234.5
    
    //MARK: LifeCycle
    
    //Custom initializers go here
    
    //MARK: View LifeCycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // ...
    }
    
    //MARK: Layout
    private func makeViewConstaints() {
        // ...
    }
    
    //MARK: User Interaction
    
    func foobarButtonTapped () {
        // ...
    }
    
    //MARK:FoobarDelegate
    func foobar(foobar: Foobar didSomethingWithFoo foo: Foo) {
        // ...
    }
    
    //MARK: Additional Helpers
    private func displayNameForFoo(foo: Foo) {
        // ...
    }
}
           

Objective-C MARK風格:

#import "someModel.h"
#import "someView.h"
#import "someController.h"
#import "someStore.h"
#import "someHelper.h"
#import <someExternalLibrary/someExternalLibraryHeader.h>

static NSString * const XYZFooStringConstant = @"FoobarConstant";
static CGFloat const XYZFooFloatConstant = 1234.5;

@interface XYZFooViewController () <XYZBarDelegate>
@property (nonatomic, readonly, copy) Foo *foo;
@property (nonatomic, strong) UILabel *label; //譯者加

@end

@implementation XYZFooViewController

#pragma mark - LifeCycle

- (instancetype)initWithFoo:(Foo *)foo;
- (void)dealloc;

#pragma mark - View LifeCycle

- (void)viewDidLoad;
- (void)viewWillAppear:(BOOL)animated;

#pragma mark - Layout

- (void)makeViewConstraints;

#pragma mark - Public Interface

- (void)startFooing;
- (void)stopFooing;

#pragma mark - User Interface

- (void)foobarButtonTapped;

#pragma mark - XYZFoobarDelegate

- (void)foobar:(Foobar *)foobar didSomethingWithFoo:(Foo *)foo;

#pragma mark - Internal Helpers

- (NSString *)displayNameForFoo:(Foo *)foo;

#pragma mark - Setter / Getter (譯者加)

- (UILabel *)label;

@end
           

最重要的是讓這些分塊标記在工程裡所有的類裡保持一緻!

其他的程式設計風格

Futurice(作者所在的公司)并沒有公司範圍的編碼風格指南。不過,仔細研究一下其他開發社群的 Objective-C 風格指南會非常有用,盡管有些部分可能隻對特定公司有效或比較主觀。

  • Github
  • Google
  • The New York Times
  • Ray Wenderlich
  • Sam Soffers
  • Luke Redpath

安全

即使在這樣一個時代,我們信任我們的便攜裝置,讓其攜帶自己最私有的資料,但 app 的安全性仍然是一個經常被忽視的主題。嘗試對資料安全性的設定找到一個良好的權衡,以下有一些簡單的經久耐用的法則。另外,Apple 的 iOS安全指南是一個很好的入門教程。

資料存儲

如果你的 app 需要存儲敏感資料,比如使用者名、密碼、認證 "Token" 或者一些個人的使用者資訊,你需要将它們儲存在本地且不允許從 App 外部進行讀取。絕不能用

NSUserDefaults

或别的存放在閃存的 plist 檔案,也不能用 CoreData 來做,因為他們沒有加密!絕大多數類似的情況,

iOS KeyChain

是你的救星。如果不習慣直接使用 C 的 APIs,你可以使用像 SSKeyChain或者 UICKeyChainStore 這樣的一些封裝。

在儲存檔案和密碼時,確定正确而謹慎地選擇恰當的安全等級。如果在裝置鎖定時(比如背景任務)你還需要通路檔案,使用 "accessible after first unlock" 選項即可。其他的情況下,你應該要求裝置在解鎖之後才能通路資料。僅在需要使用敏感資料時才讀取。

網絡

確定任何時候與服務端的 HTTP 通信都是 TLS 加密的。為避免中間人攻擊竊聽你的加密資料,你可以設定證書限制(certificate pinning),像 AFNetworking或Alamofire這種流行的網絡庫都支援這樣通信。

Logging(日志列印)

釋出你的 app 之前,應特别小心地設定好合适的日志級别。建構的産品(ipa檔案)絕不能(日志)記錄登入密碼、API的Tokens等類似的敏感資訊,因為這很容易導緻将他們洩露給公衆。另一方面,記錄基本的控制流程可以幫你定位使用者所遇到的問題。

UserInterface(使用者界面)

當使用

UITextField

做密碼輸入時,記住設定它們的

secureTextEntry

屬性為

true

,以免明文顯示密碼。同時也應該關閉其"輸入自動校正"的功能,并在任何合适的時刻清空密碼,比如當 app 退到背景時。

當 app 退到背景時,清空剪切闆可以避免密碼或其他敏感資料被洩露。由于 iOS 可能需要你 app 的螢幕截圖,以顯示在 app 切換器中,是以在

applicationDidEnterBackground

方法傳回前,應該確定 UI 上顯示的所有敏感資料被清空。

診斷

編譯警告

建議把編譯警告都打開,并且像對待 error 一樣對待 warning。這份幻燈片論證了這一點。幻燈片裡同時還講到了如何在特定檔案或特定代碼段中忽略特定的 warning。

一句話,在 build setting 的 "Other Warning Flags"中至少要加入以下兩個值:

  • -Wall

    (開啟非常多的額外的 warning)
  • -Wextra

    (開啟許多額外的 warning)

同時打開 build setting 裡的 "Treat warnings as errors"

Clang靜态分析器

Clang 編譯器 (也就是 Xcode 适用的編譯器) 有一個靜态分析器(static analyer),用來執行代碼控制流和資料流的分析,可以發現許多編譯器檢查不出來的問題。

你可以在 Xcode 的 Product ---> Analyze裡手動運作分析器。

分析器可以運作"shallow"和"deep"兩種模式。後者要慢很多,但是有跨方法的控制流分析以及資料流分析,是以能發現更多問題。

建議:

  • 開啟分析器的 全部檢查(方法是在 build setting 的 "Static Analyzer" 部分開啟所有選項)
  • 在 build setting 裡,對 release 的 build 配置開啟 "Analyzer during 'Build'"。(真的,一定要這樣做 --- 你不會記得手動跑分析器的。)
  • 把 build setting 裡的 "Model of Analysis for 'Analyze'"設為 Shallow(faster)
  • 把 build setting 裡的 "Model of Analysis for 'Build'"設為 Deep

Faux Pas

由我們員工Ali Rantakari創作的 Faux Pas 是一個出色的靜态 Error 檢測工具。它能分析你的代碼庫,找出你全然不知的錯誤。在釋出任何iOS (或 Mac)App之前務必要運作它一次!

Debugging

當 App 崩潰時,預設情況下 Xcode 不會進入 Debugger。要想進入 Debugger,需要添加一個 Exception Breakpoint (點選 Xcode 的 Debug Navigator 底部的"+"号),遇到 Exception 時就會暫停執行。在大部分情況下,你都能看到導緻 Exception 的那行代碼。 這種方法會捕捉到任何 Exception,包括已經做了處理的 Exception。如果Xcode經常停在良性的 Exception 上(比如第三方庫),選擇 Edit Breakpoint然後在 Exception 下拉菜單中選擇 Objective-C可以減少這種情況的出現。

在 View 的 Debug 方面, Reveal和Spark Inspector是兩個強大的可視化檢查器,可以節約你大量的時間,尤其是用 AutoLayout 時想知道消失的視圖去哪兒了的情況。 Xcode 也免費提供了一個類似的東西,不過隻支援 iOS8 + ,并且還不夠完善。

Profiling評測

Xcode 自帶一套評測工具 "Instruments"。它包含了衆多的評測工具:評測記憶體使用、CPU、網絡連接配接、圖像等等。它本身是個龐然大物,但一個比較簡單直接的用途是用 Allocations Instrument 來檢測記憶體洩露。隻需在 Xcode 中選擇 Product ---> Profile,選擇 Allocations instrument,點選 Record按鈕,然後從 Allocation Summary中過濾一些有用的字元串,比如 app 中你自己寫的類的類名字首。在 Persistant一欄中的計數顯示了每個對象有多少個執行個體。如果某個類的執行個體個數一直胡亂增長,說明有記憶體洩露。

衆所周知的是 Instruments 有一個 Automation 工具可以把 UI 互動錄制為 JavaScript 檔案并重放。UI Auto Monkey是一個腳本,它可以借助 Automation 在你的 App 上随機點選、清掃、旋轉,這對壓力測試/浸泡測試很有幫助。

要格外注意的是,你在哪裡以何種方式建立了巨耗資源的類。舉個栗子,

NSDateFormatter

建立起來非常耗資源,當快速而連續這麼做時,比如在

tableView:cellForRowAtIndePath:

方法中,會真正減慢 App 的響應速度。你應該建立一個它的 static 執行個體,并在需要格式化日期時直接使用該執行個體。

統計分析

強烈推薦在你的 App 中添加一個統計分析的架構,它能幫助你看到使用者實際上是怎麼用你的 App 的。X 功能有價值嗎?按鈕 Y 太難找到了嗎?要回答這些問題,可以把點選事件、計時以及其他可測的資訊發送到一個能收集并可視化這些資訊的服務上,比如 Google Tag Manager。Google Tag Manager 比 Google Analytics 更靈活一些,它在 App 和 Analytics 之間插了一個資料層,是以不須更新 app 就可以通過 web service 更改資料邏輯。

一種好的做法是加一個輕量的輔助類,比如

XYZAnalyticsHelper

,用來把 App 内部的 model 和資料格式(XYZModel, NSTimeInterval等)翻譯成以字元串為主的資料層。

Swift :

fun pushAddItemEventWithItem(item: Item, editMode: EditMode) {
    let editModeString = nameForEditMode(editMode)
    
    pushToDataLayer([
        "event" : "addItem",
        "itemIdentifier" : item.identifier,
        "editMode" : editModeString
    ])
}
           

Objective-C :

- (void)pushAddItemEventWithItem:(XYZItem *)item editMode:(XYZEditMode)editMode {
    NSString *editModeString = [self nameForEditMode:editMode];
    
    [self pushToDataLayer:@{
        @"event" : @"addItem",
        @"itemIdentifier" : item.identifier,
        @"editMode" : editModeString
    }];
}
           

這樣做有一個額外的好處:在有必要時,可以清除掉整個統計分析架構,而 App 其餘的部分不受任何影響。

CrashLogs崩潰日志

首先應該讓 App 把崩潰日志發送到某個伺服器上,這樣你才能看得到。可以使用 PLCrashReporter結合自己的背景實作這個功能,但推薦使用已有的第三方服務,比如下面這些:

  • Crashlytics
  • HockeyApp
  • Crittercism
  • Splunk MINTexpress

設定好這些之後,每次釋出都要確定儲存了 Xcode archive(.xcarchive).Archive 裡包含編譯出的二進制檔案以及 Debug symbol( dSYM ),你需要這些資料來解析這個版本 App 的崩潰報告。

編譯建構

編譯配置

即使最簡單的 App 也有不同的建構方式。 Xcode 提供的最基本的差別是Debug和Release模式。後者的編譯時優化要強很多,代價是損失了 Debug 的可能性。蘋果建議你開發時使用 Debug模式,送出到 AppStore 的包用 Release模式編譯。預設的模式(在 Xcode 裡的運作/停止按鈕旁邊的下拉菜單可以更改)就是這麼設定的,Run 用 Debug, Archive 用 Release。

不過對于真實的應用,這樣還是過于簡單。你可以--- 不!是應該 --- 有幾套不同的環境,分别用于測試、更新和其他與服務相關的操作。每套環境都可以有自己的 base URL、log 級别、bundle identifier (這樣就可以同時安裝)、provision profile 等。是以,簡單的 Debug/Release 不能滿足需求。你可以在 Xcode 工程設定的 "Info" 一欄裡添加更多的編譯配置。

編譯配置的xcconfig檔案

編譯配置一般是在 Xcode 的界面裡設定的,不過你也可以使用配置檔案(".xcconfig 檔案")來設定。這樣做的好處是:

  • 你可以添加注釋來進行解釋;
  • 你可以

    #include

    其他編譯檔案,幫助避免重複:
    • 如果你有一些所有配置通用的設定,添加一個

      Common.xcconfig

      檔案,然後把它

      #include

      到其他檔案裡;
    • 比如你想要加一個在 "Debug" 基礎上開啟編譯優化的配置,隻需

      #include "MyApp_Debug.xcconfig"

      ,然後覆寫相應的設定
  • 合并和解決沖突更簡單一些

更多關于本話題的資訊,可以參考這些幻燈片。

Targets

Targets 的概念比 project 低一個級别,即一個 project 可以有多個 targets,這些 targets 的設定 可以覆寫它的 project 的設定。粗略地說,每一個 target 對應着代碼庫上下文中的一個 app。舉個栗子,你可能針對不同國家的 Appstore 有不同的 App (都是從同一個代碼庫編譯出來的)。每一個 App 都需要 開發/staging(階段性成果)/釋出 的編譯配置,是以用編譯配置(build configurations)會比 target 更好一些。一個 App 對應隻有一個 target 非常常見。

Schemes

Schemes 告訴 Xcode 在 Run、Test、Profile、Analyze 和 Archive 時分别應該幹什麼。基本上,以上每個操作的 Scheme 對應一個 target 和 一套編譯配置。你也可以傳遞啟動參數,比如 App 運作的語言(對于測試本地化很友善)或者設定一些 Debug 用的診斷标記。

Scheme 推薦的命名方式是

MyApp(<language>) [Environment]

:

MyApp (English) [Development]
MyApp (German) [Development]
MyApp [Testing]
MyApp [Staging]
MyApp [AppStore]
           

大部分環境下,語言是不需要标明的,因為 App 有可能通過 Xcode 之外的途徑安裝,比如 TestFlight,這樣啟動參數就會被忽略,這種情況下,隻能手動設定裝置語言來測試本地化。

部署

将 app 安裝到 iOS 裝置上并不簡單。那麼我們在這裡會介紹幾個核心的概念,了解了這些概念會對你部署 app 有很大幫助。

Signing簽名

隻要你想把應用跑在真機上,你就需要在編譯時用一個 Apple 頒發的 證書來簽名。每一個證書對應一對公鑰/私鑰,私鑰儲存在你Mac的鑰匙串中。證書有兩種:

  • 開發證書:團隊裡的每個開發者都可以通過請求獲得自己的開發證書。Xcode 可以自動完成這項工作,不過最好還是不要點選那個神奇的 "Fix issue"按鈕,而是自己做一遍來了解這個過程到底做了什麼。要把開發環境打的包安裝到裝置上就需要開發證書。
  • 分發證書:可以有多個,不過最好還是限制為每個組織一個,然後通過内部管道分享它相關聯的密鑰。要釋出到 AppStore 或者企業的内部 "Appstore",需要這個證書。

Provisioning(證書)配置

除了證書之外,還有 Provisioning profiles(配置檔案),它是關聯證書與裝置的一環。同樣有兩類,分别用于開發和釋出:

  • Development provisioning profile (開發配置檔案):它包括被授權安裝/運作 App 的裝置清單。同時它與一個或多個開發證書相關聯,每一個開發證書對應一個可以使用這個 profile (配置檔案)的開發者。這種 profile 可以與特定的 App 綁定,但對于開發的用途,大部分用通配的 profile 即可(AppID 以星号

    *

    結尾,比如 "net.senink.*")。
  • Distribution provisioning profile (分發配置檔案):有三種分發途徑,每一種的使用情景都不同。每個 distribution profile 與一個分發證書相關聯,證書過期即失效。
    • Ad-Hoc:與開發證書相同,它包含可以安裝 App 的裝置白名單。這種 profile 可以用來再每年最多100個裝置上做 beta 測試(譯者注:最近 Apple 放寬了限制:同種裝置每年可以各有100個,即iPhone 100 ;iPad 100 ;iPhone touch 100 ...),如果想通過規模更大的測試來改善設計及使用者體驗,可以使用 Apple 新推出的 TestFlight服務。Supertop 上對它的優勢和問題做了很好的總結。
    • AppStore:它沒有包含裝置清單,因為任何人都可以通過 Apple 的官方分發管道安裝。釋出到 Appstore需要這種 profile。
    • Enterprise:和 Appstore 屬于同一類型,沒有裝置白名單,任何人都可以通過企業内部的 "AppStore"來安裝 App。

要把所有的證書和 profile 同步到你的裝置上,在 Xcode 的 Preference 中的 Accounts裡添加你的 Apple ID,然後輕按兩下團隊(team)名稱。底部有一個重新整理按鈕,但有時需要重新開機 Xcode 才能正常重新整理。

DebuggingProvisioning配置檔案的調試

有時你需要 Debug 一個 provisioning 問題。比如,Xcode 可能拒絕把包安裝到裝置上,因為裝置不在(development 或 ad-hoc 的) profile 的裝置清單上。這種情況下,你可以使用 CraigHockenberry 優秀的 Provisioning插件定位到

~/Library/MobileDevice/Provisioning Profiles

中,選擇

.mobileprovision

檔案然後按空格鍵啟動 Finder 的快速搜尋功能,它會展示出非常豐富的資訊,包括:裝置、授權、證書和 App ID 等。

上傳

iTunes Connect是蘋果 AppStore 上的 App 管理平台。上傳一個包,Xcode 需要一個開發者賬戶的 Apple ID 來簽名。如果你有多個開發者賬戶,想要分别上傳他們的 App,可能遇到一些麻煩,因為不知道為什麼 一個特定的 Apple ID隻能與一個 iTunes Connect 賬戶相關聯。替代的方法是,為每個 iTunes Connect 賬戶都建立一個新的 Apple ID,然後使用 Application Loader 代替 Xcode 來上傳包。這樣就把打包簽名與上傳

.ipa

檔案的過程解耦了。

上傳包之後,保持耐心,可能一個小時後這個版本的 App 才會出現在 Builds 一欄,當它出現後,你可以把它與 App 的版本資訊關聯起來,然後送出稽核。

内購

驗證 App 内購的收據時,請記得進行以下檢查:

  • 真僞性: 購買收據是否确實來自 Apple;
  • 完整性: 收據有沒有被篡改;
  • 應用比對: 收據中的 Bundle ID 是否與你的 App 的 Bundle ID 相符;
  • 産品比對: 收據的 product ID 是否與你預期的 product ID 相符;
  • 是否最新: 在這之前有沒有見過相同的收據ID;

設計你的 IAP 系統時,盡量把售賣的内容存儲再 server 端,然後僅當收到有效的、通過以上所有檢查的收據後才把内容提供給 client 端。這樣的設計防止了正常的盜版機制,并且--- 既然驗證是在 server 端進行的 --- 你可以利用 Apple 的 HTTP 收據驗證服務,而不是自己解析收據的

PKCS #7 / ASN.1

格式檔案。

關于這個問題,更多的資訊請參考Futurice blog:Validating in-app purchases in your iOS app

授權

Futurice 署名 - 相同方式共享 4.0 國際許可協定(CC BY 4.0)

計劃

  • 添加常用的編譯警告
  • 添加如何使用Jenkins自動化打包分發
  • 添加一個跟測試相關的小節
  • 添加注意事項

譯者

  KevinHM,喜歡就 Follow 吧,更多精彩将分享給您!

  文檔的翻譯也參考了iOS-good-practices-in-Chinese!

轉載于:https://www.cnblogs.com/skycore/p/4861643.html