本文由 戴倉薯(也就是我?。?翻譯,dopcn 校稿。首發于伯樂在線
原文鏈接:https://github.com/futurice/ios-good-practices
本文翻譯在 github 上跟隨原文持續維護并更新~
這份文檔就像軟件項目一樣,如果我們不維護它就會逐漸腐壞。歡迎大家跟我們一起來維護它——只需提交 issue 或者發 pull request 即可!
對其他移動平臺感興趣?也許我們的《Android 開發最佳實踐》以及《Windows App 開發 最佳實踐》能滿足你。
為什么要寫這篇文檔?
iOS 開發在上手時可能會有些令人生畏。無論是 Objective-C 還是 Swift 在別處都沒有廣泛的應用,iOS 這個平臺幾乎對一切都有一套不同的叫法,而嘗試把你的代碼跑在真機上的過程難免磕磕碰碰。這份持續更新的文檔就是來幫你的,無論你是 Cocoa 王國的新手,或是想知道“最佳做法”是什么,都可以一讀。下文僅供參考,如果你有理由采取不同的做法,不用顧慮,只管做吧!
上手
Xcode
Xcode 是絕大部分 iOS 開發者選擇的 IDE,也是唯一一個蘋果官方支持的 IDE。也有一些其他選擇,最著名的可能要數 AppCode 了。但除非你已經對 iOS 游刃有余,否則還是用 Xcode 吧。盡管 Xcode 有一些缺點,它現在還算是相當實用的!
要安裝 Xcode,只需在 Mac 的 App Score 上下載即可。它自帶最新版的 SDK 和 iOS 模擬器,其他版本可以在 Preferences > Downloads 處安裝。
建立工程
開始一個新的 iOS 項目時,一個常見的問題是:用代碼寫界面還是用 Storyboard、xib 畫界面。在現有的應用里,這兩種做法都占有一席之地。你需要考慮以下幾點:
用代碼寫界面有哪些好處?
- Storyboard 的 XML 結構很復雜,所以如果用 Storyboard ,合并代碼時很容易沖突,比起用代碼寫的界面要麻煩許多。
- It's easier to structure and reuse views in code, thereby keeping your codebase DRY.
- 用代碼寫界面時,構建和重用 view 更加方便,因此能保持你的 codebase 遵循DRY 原則。
- 所有的信息都集中在一處。如果用 Interface Builder,你還得到處點開各種檢查器,才能找到你要設置的屬性。
用 Storyboard 畫界面有哪些好處?
- 對技術不太熟悉的人也可以畫 Storyboard,調整顏色、layout 約束,為項目做出直接貢獻。不過,要做這些需要工程已經建好,并且也要了解一些基本知識。
- 開發迭代會更快,因為不需要 build 工程就能預覽到做出的改動。
- 在 Xcode 6 中,在 Storyboard 里終于能看到自定義的字體和 UI 控件樣式了。這讓你在設計時能更好地了解界面的最終外觀。
- 從 iOS 8 開始,你可以用Size Classes來設計同時支持各種屏幕尺寸的界面,省去了很多重復工作。
gitignore 文件
要為一個項目添加版本控制,最好第一步就弄一個恰當的.gitignore
文件。這樣一來,不需要的文件(例如用戶設置、臨時文件等等)就不會進入 repository 了。幸運的是,Github 幫我們同時準備好了 Objective-C 版 和 Swift 版。
CocoaPods
如果你準備在工程里引入外部依賴(例如第三方庫),CocoaPods提供了快速而便捷的集成方法。安裝方法如下:
sudo gem install cocoapods
To get started, move inside your iOS project folder and run
開始的第一步是進入你的工程目錄,然后運行
pod init
這樣會創建一個 Podfile,在這里集中管理所有的依賴。把你的依賴添加到 Profile 里,然后運行
pod install
來安裝這些庫,并且把它們和你自己的工程一起放進一個 workspace 里。在 commit 的時候,一般推薦把依賴在你的 repo 里安裝好之后再 commit,最好不要讓每個開發者 checkout 之后還要自己跑一遍pod install
。
要注意,從此以后,打開工程的時候就要打開.xcworkspace
文件了,不要再打開.xcproject
,否則代碼編譯不通過。下面這條命令
pod update
會把所有的 pod 都更新到 Podfile 允許的最新版本。你可以使用一系列的符號來準確指定你對版本的要求。
工程結構
既然把這些數以百計的源文件都保存在同一目錄下,根據工程結構來建立一個目錄結構是個好主意。例如,你可以使用以下的結構:
├─ Models
├─ Views
├─ Controllers
├─ Stores
├─ Helpers
首先,在 Xcode 的 Project Navigator(左邊欄)里,把這些目錄建立為 group(小小的黃色“文件夾”),建在與工程的同名的 group 下。然后,把每一個 group 與工程路徑下實際的文件夾鏈接起來,方法是選中 group,打開右邊欄的 File Inspector,點擊小小的灰色文件夾 icon,然后在工程目錄下創建一個新的子文件夾,名稱與 group 相同。
本地化
從最開始就要把所有的文案放在本地化文件里。這不僅有利于翻譯,也能讓你更快地找到面向用戶的文字。你可以在 build scheme 里添加一個 launch 參數,指定在某種語言下啟動 app,例如:
-AppleLanguages (Finnish)
對于更復雜的翻譯,比如與名詞的數量有關的復數形式(如 "1 person" 對應 "3 people"),你應該使用.stringsdict
格式來替換普通的localizable.strings
文件。只要你能習慣這種奇特的語法,你就擁有了一個強大的工具,可以根據需要(例如俄語或阿拉伯語的規則)把名詞變為“一個”、“一些”、“少數”和“許多”等復數形式。
更多關于本地化的信息,請參考 2012 年 2 月 HelsinkiOS 大會上的這些幻燈片。其中的大部分演講至少到 2014 年 10 月為止仍然是不過時的。
常量
把整個 app 范圍的常量定義在一個Constants.h
文件里,然后在 prefix header 里加入這個文件。
相比使用 #define
定義的預處理宏,使用真正的常量更好:
static CGFloat const XYZBrandingFontSizeSmall = 12.0f;
static NSString * const XYZAwesomenessDeliveredNotificationName = @"foo";
真正的常量是類型安全的,擁有更明確的作用域,不能在后續的代碼中重新定義也不能取消定義,并且在 debugger 中可用。
分支策略
App 發布的時候把 release 代碼從原有的分支上隔離出來,并且加上適當的 tag,是很好的做法,對于向公眾分發(比如通過 App Store)的 app 這一點尤其重要。同時,涉及到大量 commit 的 feature 應該在獨立的分支上完成。git-flow
是一個幫助你遵守這些原則的工具。它只是在 Git 的分支和 tag 命令上簡單加了一層包裝,就可以幫助維護一套適當的分支結構,對于團隊協作尤為有用。所有的開發都應該在 feature 對應的分支上完成(小改動在develop
分支上),給 release 打上 app 版本的 tag,然后 commit 到 master 分支時只能用下面這條命令:
git flow release finish <version>
Common Libraries
常用的庫
一般來說,在工程里添加外部依賴要謹慎。當然,眼下某個第三方庫能漂亮地解決你的問題,但或許不久之后就陷入了維護的泥淖,最后隨著下一版 OS 的發布全線崩潰。另一種情況是,原先只能通過引用外部庫來實現的 feature,突然官方 API 也支持了。在設計良好的項目里,把第三方庫替換為官方的實現花不了多少功夫,但在將來會大有裨益。永遠要優先考慮用蘋果官方的框架(也是最好的框架)來解決問題!
因此,這一章有意寫得比較簡短。下面介紹的第三方庫主要用來減少模板代碼(例如 Auto Layout)或者用來解決復雜的、需要大量測試的問題,例如計算日期。隨著你對 iOS 越來越精通,務必要四處看看它們的源碼,熟悉它們所使用的底層框架。你會發現做好這些就能減輕許多重擔了。
AFNetworking
大約 99.95% 的 iOS 開發者都使用這個網絡庫。盡管NSURLSession
已經非常強大了,但一旦涉及到實際管理請求隊列時,AFNetworking
仍然立于不敗之地,而現代的 app 基本都會有這個需求。
DateTools
一條常識是,不要自己寫日期計算。幸運的是,有 DateTools 這樣一個基于 MIT 協議、充分測試過的第三方庫,基本能滿足所有日期方面的要求。
Auto Layout 相關的庫
如果你習慣用代碼寫 view,你很可能用過這兩種詭異的語法——常規的NSLayoutConstraint
工廠,以及所謂的可視化語言。前者極其冗長,而后者是基于字符串的,完全躲過了編譯檢查。
而 Masonry 的解決方法是:引入自己定義的 DSL 來創建、更新和替換約束。Swift 有一個類似的庫 Cartography,是建立在這門語言強大的運算符重載基礎上的。保守一些的庫有 FLKAutoLayout,它對原生 API 加了一層整潔而不奇異的包裝。
架構
-
Model-View-Controller-Store (MVCS)
這是蘋果默認的架構(MVC)上增加了一個 Store 層,用來吐出 Model,處理網絡請求、緩存等。
每個 Store 暴露給 view controller 的或者是
RACSignal
,或者是返回值為void
、參數帶有自定義的 completion block 的方法。
- Model-View-ViewModel (MVVM)
-
View-Interactor-Presenter-Entity-Routing (VIPER)
- 相當特別的架構,大型項目可能值得參考,尤其是即使用 MVVM 還是比較凌亂,以及對需要重點考慮可測試性的情況。
"通知" 模型
以下是組件之間互發通知的一些常見手段:
- Delegation: (一對一) 蘋果官方經常用這個模式(有些人認為用得太泛濫了)。主要用于回傳,比如從模態框回傳數據。
- Callback blocks: (一對一) 耦合更松,同時能讓相關聯的代碼在一起。并且,消息發出者數量很多時比 delegation 更方便。
- Notification Center: (一對多) 可能是一個對象給多個觀察者發出“通知”時最常用的方法。耦合非常松,甚至可以把通知發到全局,不需要對調度者的引用。
- Key-Value Observing (KVO): (一對多) 不需要被觀測的對象主動“發出通知”,只需要被觀測的鍵(屬性)支持 Key-Value Coding (KVC) 。這種模式比較含混,而且標準 API 比較繁復,所以一般不推薦使用。
- Signals: (一對多) 這是ReactiveCocoa的核心,它允許結合關鍵內容的鏈式調用,用這種方法逃離回調深淵(嵌套過多的回調)。
Models
要確保你的 model 是不可變的,它們用來把遠程 API 的語義和類型轉換為 app 適用的語義和類型。Github 的 Mantle 是個不錯的選擇。
Views
使用 Auto Layout 布局時,要記得在 View 類里加上:
+ (BOOL)requiresConstraintBasedLayout
{
return YES;
}
不然,系統可能不會如期調用-updateConstraints
,而導致奇怪的 bug。
Controllers
要使用依賴注入,也就是說,應該把 Controller 需要的數據用參數傳進來,而不要把所有狀態信息都保存在單例里。后者僅當這些狀態 的確 是全局的情況下才適用。
+ [[FooDetailsViewController alloc] initWithFoo:(Foo *)foo];
網絡請求
傳統方法:使用自定義回調 block
// GigStore.h
typedef void (^FetchGigsBlock)(NSArray *gigs, NSError *error);
- (void)fetchGigsForArtist:(Artist *)artist completion:(FetchGigsBlock)completion
// GigsViewController.m
[[GigStore sharedStore] fetchGigsForArtist:artist completion:^(NSArray *gigs, NSError *error) {
if (!error) {
// Do something with gigs
}
else {
// :(
}
];
這樣雖可行,但是如果要發起幾個鏈式請求,很容易導致回調深淵。
Reactive 的方法:使用 RAC signal
如果你身陷回調深淵,可以看看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 之前做一些修改、過濾等處理。
Assets
使用 Asset catalogs 是管理工程中視覺素材的最好方法。這里既可以添加 iPhone 和 iPad 共用的素材,也可以添加針對特定設備(4寸屏 iPhone,iPhone Retina,iPad 等等)的素材,并且會根據名稱來自動提供恰當的素材。教會你的設計師(們)怎么在這里添加并 commit 素材,可以幫你節省許多時間,再也不用把素材從郵件或者別的什么渠道導進代碼庫里了。同時,這樣做也可以讓他們即刻看到自己的改動,可以根據需要進行迭代。
使用位圖
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 // iPhone, Retina
IconCheckmarkHighlighted-568h@2x~iphone.png // iPhone, Retina, 4-inch
IconCheckmarkHighlighted~ipad.png // iPad, non-Retina
IconCheckmarkHighlighted@2x~ipad.png // iPad, Retina
其中的-568h
、@2x
、~iphone
以及~ipad
這些標示符本身不是必需的,但是如果在文件名里加上它們,把文件拖動到 asset 時就能自動落到正確的“格子”上,因此能避免難以察覺的錯誤拖放。
使用矢量圖
你可以把設計師設計的原始的矢量圖 (PDFs)放進 asset catalog,讓 Xcode 來自動生成位圖。這樣能減少工程的復雜度(減少文件個數)。
編碼風格
命名
Apple 非常注意在 API 中保持命名一致性,有時候有點過于冗長了。做 Cocoa 開發時要遵循Apple的命名規范,這樣能讓加入項目的新人輕松許多。
以下是幾條看了就能用上的基本規則:
以 動詞 開頭的方法表示它執行的操作會造成一些影響,但是不返回任何值。
- (void)loadView;
- (void)startAnimating;
相反的是,以 名詞 開頭的方法返回一個對象,但不會造成額外的影響。
- (UINavigationItem *)navigationItem;
+ (UILabel *)labelWithText:(NSString *)text;
盡可能地區分這兩種方法會有很多好處,也就是說,如果一個方法是處理數據的,就不要讓它造成額外的影響,反過來也一樣。這樣可以讓造成影響的代碼塊保持緊湊,因此可以幫助理解代碼,并且有利于 debug。
代碼結構
Pragma marks是給方法分組很好的方法,特別是在 view controller 中。下面是一個在 view controller 中常見的結構:
#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, copy, readonly) Foo *foo;
@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 Interaction
- (void)foobarButtonTapped;
#pragma mark - XYZFoobarDelegate
- (void)foobar:(Foobar *)foobar didSomethingWithFoo:(Foo *)foo;
#pragma mark - Internal Helpers
- (NSString *)displayNameForFoo:(Foo *)foo;
@end
最重要的是要讓這些分塊標記在工程里所有的類里保持一致。
External Style Guides
其他風格指南
Futurice(作者所在的公司)并沒有公司范圍的編碼風格指南。不過,仔細研究一下其他開發社區的 Objective-C 風格指南會非常有用,盡管有些部分可能是只對特定公司有效或者比較主觀的。
診斷
編譯警告
建議你盡量把編譯警告都打開,并且像對待 error 一樣對待 warning。這份幻燈片 論證了這一點?;脽羝锿瑫r還講了如何在特定文件里或者特定的代碼段里忽略特定的 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 配置開啟 “Analyze during ‘Build’” 。(真的,一定要這樣做——你不會記得手動跑分析器的。)
- 把 build setting 里的 “Mode of Analysis for ‘Analyze’” 設為 Shallow (faster)
- 把 build setting 里的 “Mode of Analysis for ‘Build’” 設為 Deep
Faux Pas
由我們的員工 Ali Rantakari 創作的 Faux Pas 是一個出色的靜態 error 檢測工具。它能分析你的代碼庫,找出你全然不知的錯誤。在發布任何 iOS(或 Mac)app 之前務必要運行它一次!
(Note: all Futurice employees get a free license to this — just ask Ali.)
(注意:所有 Futurice 的員工都能得到一份免費的許可——只要問 Ali 要就行了。)
Debugging
當 app crash 的時候,默認情況下 Xcode 并不會進入 debugger。要想進入 debugger,添加一個 Exception Breakpoint(點擊 Xcode 的 Debug Navigator 底部的“+”號),遇到 exception 的時候就會暫停執行。在大部分情況下,你都能看到導致 exception 的那行代碼。這種方法會捕捉到任何 exception,包括已經做了處理的 exception。如果 Xcode 常常會停在正常的 exception(比如第三方庫里的)上,選擇 Edit Breakpoint 然后在 Exception 下拉框選擇 Objective-C 可以減輕這種情況。
在 view 的 debug 方面,Reveal 和 Spark Inspector 是兩個強大的可視化檢查器,可以節約你大量的時間,尤其是用 Auto Layout 時想知道消失的視圖去哪兒了的情況。Xcode 也免費提供了一個類似的東西,不過只支持 iOS 8+,并且略有些不夠完善。
評估
Xcode 自帶一套評估工具,叫做 Instruments。它包含眾多的評估內存使用、CPU、網絡連接、圖像等方面的工具。它本身是個龐然大物,但一個比較簡單直接的用途是用 Allocations instrument 來檢測內存泄露。只需在 Xcode 中選擇 Product > Profile ,選擇 Allocations instrument,點擊 Record 按鈕,然后從 Allocation Summary 中過濾出一些有用的字符串,比如 app 里你自己寫的類的類名前綴。在 Persistant 一欄中的計數顯示了每個對象有多少個實例。如果某個類的實例個數一直胡亂增長,就說明有內存泄露。
另外值得注意的是 Instrument 有一個 Automation 工具,用來把 UI 交互錄制為 JavaScript 文件并且重放。UI Auto Monkey 是一個腳本,它借助 Automation 在你的 app 上隨機點擊、清掃、旋轉,對壓力測試/浸泡測試可能會有幫助。
統計
強烈推薦在你的 app 里加上一個統計框架,它能幫助你看到用戶實際上是怎么用你的 app 的。X 功能有價值嗎?按鈕 Y 太難找到了嗎?要回答這些問題,可以把點擊事件、計時以及其他可測的信息發送到一個能收集并可視化這些信息的服務,比如Google Tag Manager。Google Tag Manager 比 Google Analytics 更靈活一些,它在 app 和 Analytics 之間插了一個數據層,因此不須更新 app 就可以通過 web service 更改數據邏輯。
一種很好的做法是加一個輕量的輔助 class,比如 XYZAnalyticsHelper
,用來把 app 內部的 model 和數據格式(XYZModel,NSTimeInterval 等)翻譯成以字符串為主的數據層。
- (void)pushAddItemEventWithItem:(XYZItem *)item editMode:(XYZEditMode)editMode
{
NSString *editModeString = [self nameForEditMode:editMode];
[self pushToDataLayer:@{
@"event": "addItem",
@"itemIdentifier": item.identifier,
@"editMode": editModeString
}];
}
這樣有一個額外的好處,就是可以在需要時清除掉整個統計框架,而 app 其余的部分不會受任何影響。
Crash Logs
崩潰日志
首先應該讓 app 把崩潰日志發送到某個服務器上,這樣你才能看得到??梢宰约簩崿F這個功能(用PLCrashReporter結合自己的后臺),但推薦使用已有的服務,比如下面這些:
設置好這些之后,要確保每次發布都要 保存 Xcode archive (.xcarchive
) 。Archive 里包含編譯出的二進制文件以及 debug symbol(dSYM
),你需要這些數據來解析這個版本 app 的崩潰報告。
編譯構建
編譯配置
即使最簡單的 app 也有不同的構建方式。Xcode 提供的最基本的區別是 debug 和 release 模式。后者的編譯時優化要強很多,代價是損失了 debug 的可能性。蘋果建議你開發時使用 debug 模式,提交到 App Store 的包用 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
Target 的概念比 project 低一個級別,也就是說,一個 project 可以有數個 target,這些 target 的設置可以覆蓋 project 的設置。粗略地說,每個 target 對應代碼庫里的“一個 app”。舉個例子,你可能針對不同國家的 App Store 有不同的 app(都是從同一個代碼庫編譯出來的)。每個 app 都需要開發/更新/release 的編譯配置,因此用編譯配置來處理會比 target 更好一些。一個 app 只有一個 target 完全不足為奇。
Schemes
Scheme 告訴 Xcode 在 Run、Test、Profile、Analyze 和 Archive 時分別應該干什么?;旧?,以上每個操作的 scheme 對應一個 target 和一套編譯配置。你也可以傳遞啟動參數,比如 app 運行的語言(對于測試本地化很方便?。┗蛘咴O置一些 debug 用的診斷 flag。
Scheme 的推薦命名方式是 MyApp (<Language>) [Environment]
:
MyApp (English) [Development]
MyApp (German) [Development]
MyApp [Testing]
MyApp [Staging]
MyApp [App Store]
對于大部分環境其中的語言是不需要的,因為 app 有可能通過 Xcode 之外的途徑安裝,比如 TestFlight,這樣啟動參數就會被忽略。這種情況下,只能手動設置設備語言來測試本地化。
部署
把應用安裝到 iOS 設備上可算不上簡單直接。盡管如此,在這里會介紹幾個核心概念;理解這些概念,會對你的部署有很大的幫助。
簽名
只要你想把應用跑在真實的設備上(相對于模擬器而言),你就需要在編譯時用一個蘋果頒發的 證書 來簽名。每個證書對應一對公鑰/私鑰,私鑰保存在你的 Mac 的鑰匙串中。證書有兩種:
- 開發證書: 團隊里的每個開發者都可以有自己的開發證書,是通過請求獲得的。Xcode 可以自動完成這項工作,不過最好還是不要點擊那個神奇的“Fix issue”按鈕,而是自己做一遍來理解這個過程到底做了什么。要把開發環境打的包安裝到設備上就需要開發證書。
- 分發證書: 可以有多個,不過最好還是限制為每個組織一個,然后通過內部渠道分享它相關聯的密鑰。要發布到 App Store 或者企業的內部“app store”,就需要這個證書。
Provisioning
除了證書之外,還有 provisioning profiles ,它就是關聯證書和設備的一環。它同樣有兩種,分別用于開發和分發這兩種不同目的:
Development provisioning profile: 它包括被授權安裝、運行 app 的設備列表。同時它與一個或多個開發證書相關聯,每個開發證書對應一個可以使用這個 profile 的開發者。這種 profile 可以與特定 app 綁定,但是對于開發的用途,大部分用通配的 profile 即可,App ID 以星號(*)結尾。
Distribution provisioning profile: 有 3 種分發的途徑,每種都有一種不同的使用情景。每個 distribution profile 與一個分發證書相關聯,證書過期即失效。
* __Ad-Hoc:__ 與開發證書相同,它包含可以安裝 app 的設備白名單。這種 profile 可以用來在每年最多 100 個設備上做 beta 測試。想要更為順暢的體驗,增加至 1000 個不同的用戶,你可以使用蘋果新推出的[TestFlight][testflight]服務。Supertop 上對它的優勢和問題有[一個很好的總結][testflight-discussion]。
* __App Store:__ 這種 profile 沒有設備列表,因為任何人都可以通過蘋果的官方分發渠道安裝 app。發布到 App Store 會需要這種 profile。
* __Enterprise:__ 如同 App Store 類型一樣,沒有設備白名單,任何人都可以通過企業的內部“app store”來安裝 app。
要把所有的證書和 profile 同步到你的機器上,到 Xcode 的 Preferences 里的 Accounts,在這里添加你的 Apple ID,然后雙擊 team 名稱。底部有一個刷新按鈕,但有時需要重啟 Xcode 才能正常刷新。
Debugging Provisioning
有時候你需要 debug 一個 provisioning 問題。例如,Xcode 可能拒絕把包安裝到設備上,因為設備不在(development 或 ad-hoc 的)profile 的設備列表上。在這種情況下,你可以使用 Craig Hockenberry 優秀的Provisioning插件,定位到~/Library/MobileDevice/Provisioning Profiles
,選擇.mobileprovision
文件然后按空格鍵,啟動 Finder 的快速搜索功能。它會展示出非常豐富的信息,包括設備、授權、證書 和 App ID 等。
上傳
iTunes Connect 是蘋果 App Store 上 app 的管理平臺。要上傳一個包,Xcode 6 需要用一個開發者賬號的 Apple ID 來簽名。這里如果你有多個開發者賬號,想要分別上傳他們的 app,可能遇到一些麻煩,因為不知為何 一個特定的 Apple ID 只能與一個 iTunes Connect 賬號相關聯 。一個替代方法是,為每個 iTunes Connect 賬號都創建一個新的 Apple ID,然后使用 Application Loader 代替 Xcode 來上傳包。這樣就把打包簽名與上傳 .app
文件的過程解耦了。
上傳包之后,保持耐心,可能一個小時后這個版本的 app 才會出現在 Builds 一欄。當它出現以后,你可以把它與 app 的版本信息鏈接起來,然后提交審核。
App內購買(IAP)
驗證 app 內購買的收據時,請記得進行以下檢查:
- 真偽性: 購買收據確實來自蘋果;
- 完整性: 收據沒有被篡改;
- 應用匹配: 收據里的 bundle ID 符合你的 app 的 bundle ID;
- 產品匹配: 收據里的 product ID 符合你預期的 product ID;
- 最新性: 你之前沒有見過相同的收據 ID
設計你的 IAP 系統時,盡量把售賣的內容存儲在 server 端,然后僅當收到有效的、通過以上所有檢查的收據后,才把內容提供給 client 端。這樣的設計阻礙了常規的盜版機制,并且——既然驗證是在 server 端進行的——你可以利用蘋果的 HTTP 收據驗證服務,就不用自己解析收據的 PKCS #7
/ ASN.1
格式了。
關于這個話題的更多信息,可以參考Futurice blog: 在你的 iOS app 里驗證 app 內購買。