這份文件就像軟體專案一樣,如果我們不維護它就會逐漸腐壞。歡迎大家跟我們一起來維護它——只需提交 issue 或者發 pull request 即可!
對其他移動平臺感興趣?也許我們的《安卓開發最佳實踐》和《Windows客戶端最佳實踐》能滿足你。
為什麼要寫這篇文件?
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提供了快速而便捷的整合方法。安裝方法如下:
1 |
sudo gem install cocoapods |
To get started, move inside your iOS project folder and run
開始的第一步是進入你的工程目錄,然後執行
1 |
pod init |
這樣會建立一個 Podfile,在這裡集中管理所有的依賴。把你的依賴新增到 Profile 裡,然後執行
1 |
pod install |
來安裝這些庫,並且把它們和你自己的工程一起放進一個 workspace 裡。在 commit 的時候,一般推薦把依賴在你的 repo 裡安裝好之後再 commit,最好不要讓每個開發者 checkout 之後還要自己跑一遍pod install
。
要注意,從此以後,開啟工程的時候就要開啟.xcworkspace
檔案了,不要再開啟.xcproject
,否則程式碼編譯不通過。下面這條命令
1 |
pod update |
會把所有的 pod 都更新到 Podfile 允許的最新版本。你可以使用一系列的符號來準確指定你對版本的要求。
工程結構
既然把這些數以百計的原始檔都儲存在同一目錄下,根據工程結構來建立一個目錄結構是個好主意。例如,你可以使用以下的結構:
1 2 3 4 5 |
├─ Models ├─ Views ├─ Controllers ├─ Stores ├─ Helpers |
首先,在 Xcode 的 Project Navigator(左邊欄)裡,把這些目錄建立為 group(小小的黃色“資料夾”),建在與工程的同名的 group 下。然後,把每一個 group 與工程路徑下實際的資料夾連結起來,方法是選中 group,開啟右邊欄的 File Inspector,點選小小的灰色資料夾 icon,然後在工程目錄下建立一個新的子資料夾,名稱與 group 相同。
本地化
從最開始就要把所有的文案放在本地化檔案裡。這不僅有利於翻譯,也能讓你更快地找到面向使用者的文字。你可以在 build scheme 裡新增一個 launch 引數,指定在某種語言下啟動 app,例如:
1 |
-AppleLanguages (Finnish) |
對於更復雜的翻譯,比如與名詞的數量有關的複數形式(如 “1 person” 對應 “3 people”),你應該使用.stringsdict
格式來替換普通的localizable.strings
檔案。只要你能習慣這種奇特的語法,你就擁有了一個強大的工具,可以根據需要(例如俄語或阿拉伯語的規則)把名詞變為“一個”、“一些”、“少數”和“許多”等複數形式。
更多關於本地化的資訊,請參考 2012 年 2 月 HelsinkiOS 大會上的這些幻燈片。其中的大部分演講至少到 2014 年 10 月為止仍然是不過時的。
常量
把整個 app 範圍的常量定義在一個Constants.h
檔案裡,然後在 prefix header 里加入這個檔案。
相比使用 #define
定義的預處理巨集,使用真正的常量更好:
1 2 |
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 分支時只能用下面這條命令:
1 |
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 類里加上:
1 2 3 4 |
+ (BOOL)requiresConstraintBasedLayout { return YES; } |
不然,系統可能不會如期呼叫-updateConstraints
,而導致奇怪的 bug。
Controllers
要使用依賴注入,也就是說,應該把 Controller 需要的資料用引數傳進來,而不要把所有狀態資訊都儲存在單例裡。後者僅當這些狀態 的確 是全域性的情況下才適用。
1 |
+ [[FooDetailsViewController alloc] initWithFoo:(Foo *)foo]; |
網路請求
傳統方法:使用自定義回撥 block
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// 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 的概念)。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 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 時遵循一些原則可以讓生活更輕鬆:
1 2 3 4 5 6 7 |
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的命名規範,這樣能讓加入專案的新人輕鬆許多。
以下是幾條看了就能用上的基本規則:
以 動詞 開頭的方法表示它執行的操作會造成一些影響,但是不返回任何值。
1 |
- (void)loadView; - (void)startAnimating; |
相反的是,以 名詞 開頭的方法返回一個物件,但不會造成額外的影響。
1 |
- (UINavigationItem *)navigationItem; + (UILabel *)labelWithText:(NSString *)text; |
儘可能地區分這兩種方法會有很多好處,也就是說,如果一個方法是處理資料的,就不要讓它造成額外的影響,反過來也一樣。這樣可以讓造成影響的程式碼塊保持緊湊,因此可以幫助理解程式碼,並且有利於 debug。
程式碼結構
Pragma marks是給方法分組很好的方法,特別是在 view controller 中。下面是一個在 view controller 中常見的結構:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#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。這份幻燈片 論證了這一點。幻燈片裡同時還講了如何在特定檔案裡或者特定的程式碼段裡忽略特定的 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 等)翻譯成以字串為主的資料層。
1 2 3 4 5 6 7 8 9 10 |
- (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 把崩潰日誌傳送到某個伺服器上,這樣你才能看得到。可以自己實現這個功能(用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
其他編譯配置檔案,幫助避免重複:
1 2 3 |
- 如果你有一些所有配置通用的設定,新增一個 `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 執行的語言(對於測試本地化很方便!)或者設定一些 debug 用的診斷 flag。
Scheme 的推薦命名方式是 MyApp (<Language>) [Environment]
:
1 2 3 4 5 |
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 與一個分發證照相關聯,證照過期即失效。
1 2 3 4 5 |
* __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 內購買。
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!
任選一種支付方式