[iOS]轉:iOS最佳實踐

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

  跟隨iOS開發技術發展的潮流,我將持續維護本文檔的中文版,若是你喜歡這個譯本,也請給個 Star 鼓勵下~~react

  本文檔的英文原版在這裏,感謝Futurice團隊卓越的工做,爲咱們提供這麼優質的文檔。android

知識是人類進步的階梯ios

翻譯,喵 ~~git

# iOS開發的最佳實踐

就像一個軟件項目同樣,這份文檔若是咱們不持續維護就會逐漸失效,咱們鼓勵你們參與到這個項目中來---僅需提交一個 issue 或發送一份 pull requestgithub

對其餘移動平臺感興趣?個人Andriod開發最佳實踐以及Windows App開發最佳實踐可能會幫到你哦。web

爲何寫這個文檔

iOS開發要上手比較困難,由於不管是 Objective-C 仍是 Swift 在別處都沒有普遍被應用,iOS 這個平臺彷佛對一切都有一套不一樣的叫法。當你嘗試在真機上跑程序時不免會磕磕碰碰。這份持續更新的文檔就是你的救星!不管你是Cocoa王國的新手,或是老練到只想知道"最佳作法"是什麼,這份文檔都值得一讀。固然,內容僅供參考,你有理由採起不一樣的作法只要你願意!objective-c

目錄

若是你想閱讀指定的小節,能夠經過目錄直接跳轉數據庫

  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 有一些缺點,但它如今還算是至關實用的!express

要安裝 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 一、選中 group
  • Step 二、打開右邊欄的 File Inspector
  • Step 三、點擊小小的灰色文件夾 icon
  • Step 四、在工程目錄下建立一個新的子文件夾,名稱與 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 住他們的引用並在必要的時候關閉或激活他們。這主要用於在你想要系統執行批量更新以獲取更好性能的時候, 執行 UIViewupdateConstraints (或者它對應的 UIViewControllerupdateViewContraints )。但這樣作的代價是你須要調用 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 只會暴露出一套圖片的名字,省略了每張圖片實際的文件名。這樣相似 button_large@2x.png這類文件的命名空間僅限於 asset 內部,很好地避免了 asset 的命名衝突。然而,命名 asset 時遵循一些原則可讓生活更輕鬆:

IconCheckmarkHighlighted.png // Universal, non-Retina
IconCheckmarkHighlighted@2x.png // Universal, Retina
IconCheckmarkHighlighted~iPhone.png // iPhone, non-Retina
IconCheckmarkHighlighted@2x~iPhone.png // iPone, Retina
IconCheckmarkHighlighted-568@2x~iPhone.png // iPhone, Retina, 4-inch
IconCheckmarkHighlighted~iPad.png // iPad, non-Retina
IconCheckmarkhighlighted@2x~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 風格指南會很是有用,儘管有些部分可能只對特定公司有效或比較主觀。

安全

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

數據存儲

若是你的 app 須要存儲敏感數據,好比用戶名、密碼、認證 "Token" 或者一些我的的用戶信息,你須要將它們保存在本地且不容許從 App 外部進行讀取。毫不能用 NSUserDefaults或別的存放在閃存的 plist 文件,也不能用 CoreData 來作,由於他們沒有加密!絕大多數相似的狀況,iOS KeyChain是你的救星。若是不習慣直接使用 C 的 APIs,你可使用像 SSKeyChain或者 UICKeyChainStore 這樣的一些封裝。

在保存文件和密碼時,確保正確而謹慎地選擇恰當的安全等級。若是在設備鎖定時(好比後臺任務)你還須要訪問文件,使用 "accessible after first unlock" 選項便可。其餘的狀況下,你應該要求設備在解鎖以後才能訪問數據。僅在須要使用敏感數據時纔讀取。

網絡

確保任什麼時候候與服務端的 HTTP 通訊都是 TLS 加密的。爲避免中間人攻擊竊聽你的加密數據,你能夠設置證書約束(certificate pinning),像 AFNetworkingAlamofire這種流行的網絡庫都支持這樣通訊。

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 方面, RevealSpark 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結合本身的後臺實現這個功能,但推薦使用已有的第三方服務,好比下面這些:

設置好這些以後,每次發佈都要確保保存了 Xcode archive(.xcarchive).Archive 裏包含編譯出的二進制文件以及 Debug symbol( dSYM ),你須要這些數據來解析這個版本 App 的崩潰報告。

編譯構建

編譯配置

即便最簡單的 App 也有不一樣的構建方式。 Xcode 提供的最基本的區別是DebugRelease模式。後者的編譯時優化要強不少,代價是損失了 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!

相關文章
相關標籤/搜索