iOS最佳實(shí)踐
譯者注
本文翻譯自 futurice 公司的 iOS Good Practices,譯文在 Github 上進(jìn)行維護(hù)顺囊,同時(shí)在 簡(jiǎn)書(shū) 上進(jìn)行發(fā)布民傻。
本文發(fā)出幾天后發(fā)現(xiàn)網(wǎng)上也有了另外一個(gè)翻譯版本:http://ios.jobbole.com/81830/
原標(biāo)題是iOS Good Practices想幻,應(yīng)該翻譯成 iOS 良好實(shí)踐/優(yōu)秀實(shí)踐的,不過(guò)好拗口弥虐,而且已經(jīng)發(fā)出去了扩灯,暫且就這樣吧。
本文尚未經(jīng)過(guò)大牛審校躯舔,紕漏瑕疵以及語(yǔ)句不順的地方驴剔,以在 Github 提出,請(qǐng)大家多多指正粥庄。
以下是正文
就像軟件一樣丧失,如果我們不持續(xù)改進(jìn)這份文檔,它就會(huì)落伍惜互。我們希望大家都來(lái)幫助我們改進(jìn)它 —— 只要開(kāi)一個(gè) issue 或者發(fā)送一個(gè) pull request!
對(duì)其他平臺(tái)感興趣布讹?看看我們的 Android 開(kāi)發(fā)最佳實(shí)踐 和 Windows App 開(kāi)發(fā)最佳實(shí)踐吧。
為什么閱讀本文檔
跳進(jìn)了 iOS 的坑真是麻煩训堆。無(wú)論是 Swift 還是 Objective-C描验, 都沒(méi)有在其他地方廣泛使用,而且這個(gè)平臺(tái)對(duì)每個(gè)東西都幾乎有它自己的命名方式坑鱼,并且連要在真機(jī)上調(diào)試都充滿了坎坷膘流。無(wú)論你是剛剛?cè)腴T(mén) Cocoa 還是想糾正自己開(kāi)發(fā)習(xí)慣的開(kāi)發(fā)者,都能從本文檔獲益鲁沥。不過(guò)下面寫(xiě)的僅僅是建議呼股,所以如果你有一個(gè)更好的方案,那就試試吧画恰!
入門(mén)
Xcode
Xcode 是大多數(shù) iOS 開(kāi)發(fā)者的選擇彭谁,并且是 Apple 唯一官方支持的IDE。有一些其他的選擇允扇,比如 AppCode 是最有名的缠局,但是除非你是經(jīng)驗(yàn)豐富的開(kāi)發(fā)者则奥,否則就使用 Xcode 吧。不要管它的一些小缺點(diǎn)啦狭园,它現(xiàn)在已經(jīng)蠻好用咯读处。
要安裝 Xcode,只需要下載 Mac App Store 中的 Xcode 唱矛。它會(huì)同時(shí)下載最新的 SDK 和模擬器档泽,同時(shí)你可以從 Preferences > Downloads 下載更多的內(nèi)容。
項(xiàng)目初始化
開(kāi)始iOS開(kāi)發(fā)的時(shí)候揖赴,一個(gè)常見(jiàn)的問(wèn)題是用代碼寫(xiě)所有的 view 還是使用 Interface Builder(Storyboards 或者 XIB )。兩種方案都久經(jīng)考量抑胎。但是燥滑,有下面幾個(gè)考慮:
為什么使用代碼?
- Storyboard 因?yàn)樗膹?fù)雜的 XML 結(jié)構(gòu)容易帶來(lái)版本沖突阿逃,這讓代碼合并變得困難铭拧。
- 容易用代碼結(jié)構(gòu)化以及復(fù)用 view,讓你的代碼變得 DRY恃锉。
- 所有的信息匯集一處搀菩。在 Interface Builder 里面你需要點(diǎn)擊所有的檢查器來(lái)尋找你要找的東西。
為什么用 Storyboard破托?
- 為了更少的技術(shù)要求肪跋,Storyboard 使用了一個(gè)很好的直接貢獻(xiàn)于項(xiàng)目的方法,比如土砂,通過(guò)調(diào)整顏色或者布局的 constraints州既。然而,它要求一個(gè)項(xiàng)目已經(jīng)做好配置萝映,并且開(kāi)發(fā)者有一些時(shí)間掌握基礎(chǔ)
- 當(dāng)你不用構(gòu)建項(xiàng)目也能看到變化的時(shí)候吴叶,集成更快了
- 在 Xcode6 里面,自定義的文字和 UI 元素在 storyboard 里面都可以可視化表示序臂,比你在代碼里面修改好多了
- 從iOS8開(kāi)始蚌卤,Size Classes 允許你設(shè)計(jì)不同類型設(shè)備的屏幕,不用重復(fù)一些工作
忽略文件
當(dāng)把項(xiàng)目放入版本控制系統(tǒng)的時(shí)候奥秆,首先應(yīng)該有一個(gè)好的 .gitignore
文件逊彭。這樣,不必要的文件(用戶設(shè)置吭练,臨時(shí)文件這些)都不會(huì)放進(jìn)你的倉(cāng)庫(kù)里面诫龙。幸運(yùn)的是,Github 已經(jīng)給了我們 Objective-C 和 Swift 語(yǔ)言的模板
CocoaPods
如果你計(jì)劃增加外部依賴(比如鲫咽,第三方庫(kù))在你的項(xiàng)目中签赃,CocoaPods 提供了一個(gè)快捷的途徑谷异,就像這樣:
sudo gem install cocoapods
要開(kāi)始使用,僅僅需要在你的 iOS 項(xiàng)目目錄下運(yùn)行:
pod init
它會(huì)創(chuàng)建一個(gè) Podfile锦聊, 會(huì)管理你所有的依賴歹嘹,在 Podfile 中加入你的依賴后,允許
pod install
來(lái)安裝第三方庫(kù)并且將它們作為 workspace 的一部分孔庭,你的 workspace 也會(huì)包含你自己的項(xiàng)目尺上。 一般推薦提交你自己的項(xiàng)目的依賴,而不是每個(gè)開(kāi)發(fā)者在一個(gè) checkout之后運(yùn)行 pod install
圆到。
注意在之后怎抛,你需要打開(kāi) .xcworkspace
而不是 .xcproject
,否則你的代碼就不能被編譯了芽淡,命令:
pod update
會(huì)升級(jí)所有的 pod 到最新版本马绝,你可以用大量 符號(hào) 來(lái)定義你期望的版本需求。
項(xiàng)目結(jié)構(gòu)
為了組織目錄里面的上百個(gè)源代碼文件挣菲,最好根據(jù)你的架構(gòu)來(lái)設(shè)置一些文件結(jié)構(gòu)富稻。比如,你可以用這樣的:
├─ Models
├─ Views
├─ Controllers
├─ Stores
├─ Helpers
首先白胀,將他們創(chuàng)建為 Group(黃色的目錄)椭赋,用 Xcode 的項(xiàng)目導(dǎo)航里面的你的項(xiàng)目中。然后對(duì)每個(gè)項(xiàng)目里的文件或杠,將它么連接到真實(shí)的文件目錄 —— 通過(guò)打開(kāi)它右邊的文件檢查器哪怔,點(diǎn)擊小小的目錄圖標(biāo),在你的項(xiàng)目目錄下創(chuàng)建一個(gè)和 group 同名的子目錄向抢。
本地化
一開(kāi)始就應(yīng)該把所有的顯示給用戶的字符串放進(jìn)本地化文件蔓涧。這樣不僅僅為了翻譯方便,同時(shí)也便于查找用戶看見(jiàn)的文本笋额。你可以在 build Scheme 中加入一個(gè)啟動(dòng)參數(shù)來(lái)指定特定的語(yǔ)言
-AppleLanguages (Finnish)
對(duì)于更復(fù)雜的翻譯問(wèn)題元暴,比如復(fù)數(shù)(比如 "1 person" 和 "3 people"),你應(yīng)該使用 .stringsdict
format 而不是一個(gè)普通的 localizable.strings
文件兄猩。在有了這個(gè)強(qiáng)大的工具來(lái)處理比如 "one", some", "few" 和 "many" 的情況茉盏。 當(dāng)處理俄羅斯語(yǔ)和阿拉伯語(yǔ)的時(shí)候,你就不用急得抓耳撓腮了枢冤。
關(guān)于本地化的更多信息鸠姨,看 2012年 2月的 Helsink iOS 會(huì)議的 幻燈片,大部分內(nèi)容對(duì)現(xiàn)在也是適用的淹真。
常量
創(chuàng)建被 prefix header 引入的一個(gè) Constants.h
文件讶迁。
不要用宏定義(用 #define
),用實(shí)際的常量定義
static CGFloat const XYZBrandingFontSizeSmall = 12.0f;
static NSString * const XYZAwesomenessDeliveredNotificationName = @"foo";
常量類型安全核蘸,并且有更明確的作用域(并不是在所有沒(méi)有被引入的文件中能使用)巍糯。不能被重定義啸驯,并且可以在調(diào)試器中使用。
分支模型
特別是分發(fā)你的 app 的時(shí)候(如提交到 App Store)祟峦,最好把分支用特別的 tag 區(qū)分罚斗。同時(shí),新特性的開(kāi)發(fā)宅楞,會(huì)引入很多 commit 的针姿,也應(yīng)該在獨(dú)立的分支上提交。git-flow
是一個(gè)幫助你遵從這個(gè)約定的工具厌衙。它簡(jiǎn)單地包裝了 git的分支和tag的命令距淫,幫助維護(hù)一個(gè)正確的分支結(jié)構(gòu)。所有的開(kāi)發(fā)分支(比如 develop
)婶希, 關(guān)于 app版本的tag分支以及提交到 master分支的:
git flow release finish <version>
常用庫(kù)
總得來(lái)說(shuō)溉愁,來(lái)把一個(gè)第三方庫(kù)加入到你的項(xiàng)目中需要慎重考慮。確實(shí)饲趋,一個(gè)靈巧的庫(kù)或許能幫助你解決現(xiàn)在的問(wèn)題,但是可能在之后陷入維護(hù)的噩夢(mèng)撤蟆,比如在下一個(gè)系統(tǒng)版本改變一些東西之后奕塑,或者一個(gè)第三方庫(kù)的場(chǎng)景變成了官方的API。不過(guò)在一個(gè)良好的設(shè)計(jì)的代碼中家肯,切換實(shí)現(xiàn)是很輕松的龄砰。盡量考慮用 Apple 的廣泛(而且優(yōu)秀的)框架吧~
這個(gè)小節(jié)盡量保持剪短。庫(kù)的特性是為了減少模板代碼(比如 AutoLayout)或者解決復(fù)雜的需要很多測(cè)試的問(wèn)題讨衣,比如日期計(jì)算换棚。當(dāng)你在 iOS 中更加專業(yè)的時(shí)候,挖掘這里的源代碼反镇,通過(guò)它們底層的 Apple 框架來(lái)認(rèn)識(shí)他們固蚤,你會(huì)發(fā)現(xiàn)這些可以做大量工作。
AFNetworking
99.95% 的 iOS開(kāi)發(fā)者使用這個(gè)網(wǎng)絡(luò)庫(kù)歹茶,當(dāng) NSURLSession
自己本身也非常完善的時(shí)候夕玩, AFNetworking
仍然能憑借很多 app 需要的隊(duì)列請(qǐng)求管理功能立足于不敗之地。
DateTools 日期工具
總得來(lái)說(shuō)惊豺, 不要自己寫(xiě)日期計(jì)算.幸運(yùn)的是燎孟,有一個(gè) DateTools 是 MIT 協(xié)議的,而且經(jīng)過(guò)徹底測(cè)試的尸昧,你可以放心的在需要用日期的時(shí)候使用它揩页。
Auto Layout 庫(kù)
如果你更喜歡在代碼里面寫(xiě)界面,你會(huì)用過(guò) Apple 難用的 'NSLayoutConstraint' 的工廠或者 Visual Format Language 烹俗。前者很羅嗦爆侣,后者基于字符串萍程,不利于編譯器檢查。
Masonry 通過(guò)它們自己的 DSL 來(lái)取代常量累提, Swift 中一個(gè)類似的庫(kù)是 Cartography尘喝,它利用了語(yǔ)言的豐富的操作符重載特性。如果更加保守的話斋陪,FLKAutoLayout 提供了一個(gè)干凈但是沒(méi)有太多魔法的原生 API 包裝朽褪。
Architecture 架構(gòu)
-
Model-View-Controller-Store (MVCS)
- 這是 Apple 默認(rèn)的架構(gòu)(MVC),通過(guò)擴(kuò)展一表示 Model 實(shí)例的存儲(chǔ)層以及處理網(wǎng)絡(luò)无虚,緩存等內(nèi)容缔赠。
- 每一個(gè)存儲(chǔ)通過(guò)
RACSignal
s 或者void
返回值的自定義 block 方法來(lái)返回。
- Model-View-ViewModel (MVVM)
-
View-Interactor-Presenter-Entity-Routing (VIPER)
- 值得在大型項(xiàng)目中一看的架構(gòu)踢匣,在 MVVM 都顯得復(fù)雜,而且需要關(guān)注測(cè)試的時(shí)候戈抄。
事件模式
這里有一些通知其他對(duì)象的常用方法:
- Delegation (委托): (一對(duì)一) Apple 經(jīng)常用它(或者說(shuō)离唬,太多了)。用它來(lái)執(zhí)行回調(diào)划鸽,比如输莺, Model View 做一個(gè)回調(diào)
- Callback blocks (回調(diào)代碼塊): (一對(duì)一) 可以更加解耦,可以維護(hù)類似的相關(guān)代碼段裸诽。同時(shí)在有很多 sender 的時(shí)候比委托有更好的擴(kuò)展性嫂用。
- Notification Center (通知): (一對(duì)多) 最常用的對(duì)象來(lái)向第一個(gè)觀察者發(fā)送事件的方法。非常解耦合 - 通知甚至可以全局地進(jìn)行觀察丈冬,而不用引用派發(fā)對(duì)象
- Key-Value Observing (KVO嘱函,鍵值編碼): (一對(duì)多) 不需要觀察者來(lái)明確發(fā)送的時(shí)間,就像 Key-Value Coding (KVC) 符合觀察的鍵(屬性)埂蕊。通常不推薦使用实夹,因?yàn)樗蛔匀坏奶匦砸约胺爆嵉腁PI。
- Signals(信號(hào)): (一對(duì)多) ReactiveCocoa 的核心, 允許鏈接和組合你的內(nèi)容, 提供了防止 callback hell 的一個(gè)方法粒梦。
Models
保持你的 model 是不可變的亮航,以及用他們來(lái)改變遠(yuǎn)程的 API 的語(yǔ)義以及類型。Github 的 Mantle 是一個(gè)好的選擇
Views
當(dāng)用 Auto Layout 布局你的 view 的時(shí)候匀们,確保在你父類中加入了下面的代碼:
+ (BOOL)requiresConstraintBasedLayout
{
return YES;
}
否則當(dāng)系統(tǒng)沒(méi)有調(diào)用 -updateConstraints
的時(shí)候缴淋,你可能會(huì)遇到奇怪的 bug。
Controllers
建議使用依賴注入,比如:傳遞任何需要的對(duì)象作為參數(shù)重抖,而不是在一個(gè)單例中保持所有的狀態(tài)露氮。后一種方法僅僅在狀態(tài)是 真的 全局的時(shí)候適用。
+ [[FooDetailsViewController alloc] initWithFoo:(Foo *)foo];
網(wǎng)絡(luò)
傳統(tǒng)的方式:使用回調(diào) 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 {
// :(
}
];
這樣運(yùn)行钟沛,但是如果你有多個(gè)組合的網(wǎng)絡(luò)請(qǐng)求的時(shí)候畔规,就會(huì)進(jìn)入回調(diào)嵌套的地獄。
Reactive 的方法: 使用 RAC 信號(hào)
如果你發(fā)現(xiàn)已經(jīng)陷入了回調(diào)的地獄恨统,看看 ReactiveCocoa (RAC) 吧叁扫,它是一個(gè)萬(wàn)能而且多功能的庫(kù),能改變大家寫(xiě) 整個(gè) app 的方法畜埋,但是你可以在它適用的地方保守地使用它莫绣。
在 Teehan+Lax 和 NSHipster 上面有一些關(guān)于 RAC (and FRP in general) 以及函數(shù)式編程的概念的優(yōu)秀介紹。
// GigStore.h
- (RACSignal *)gigsForArtist:(Artist *)artist;
// GigsViewController.m
[[GigStore sharedStore] gigsForArtist:artist]
subscribeNext:^(NSArray *gigs) {
// Do something with gigs
} error:^(NSError *error) {
// :(
}
];
它允許通過(guò)信號(hào)和其他信號(hào)的結(jié)合悠鞍,在展示它們之前做一些改變对室。
Assets 資源
Asset catalogs 是管理你所有項(xiàng)目可視化資源的最好方式,它們可以同事管理通用的以及設(shè)備相關(guān)的iPhone 4-inch, iPhone Retina, iPad,等)資源咖祭,并且會(huì)自動(dòng)通過(guò)它們的名字分組掩宜。告訴你的設(shè)計(jì)師如何添加它們(Xcode有內(nèi)置的 Git 客戶端)可以節(jié)省很多時(shí)間,否則你會(huì)話很多時(shí)間來(lái)在從郵件或者其他渠道復(fù)制到代碼庫(kù)里面么翰。它可以讓設(shè)計(jì)師馬上嘗試資源改變的樣子牺汤,并且反復(fù)實(shí)驗(yàn)。
Using Bitmap Images 使用位圖
Asset catalogs 僅僅暴露了圖片的名稱硬鞍,圖片集里面的抽象的名字。這可以避免資源名字的沖突戴已,就像 button_large@2x.png
的文件的命名空間在它的圖片集里面固该。遵守一些命名規(guī)則可以讓生活更美好:
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
and ~ipad
是不必要的,但是有他們?cè)谖募锩嫣抢埽?dāng)把文件拖進(jìn)去的時(shí)候伐坏,Xcode會(huì)正確地處置它們。這避免賦值錯(cuò)誤握联。
使用向量圖
你可以用設(shè)計(jì)師原始的 vector graphics (PDFs) 加入到 asset catalogs桦沉,Xcode可以自動(dòng)地根據(jù)它們生成位圖。這減少了你的工程的復(fù)雜性(管理更少的文件)金闽。
代碼風(fēng)格
命名
盡管命名約定很長(zhǎng)纯露,但是Apple一如既往地在 API中 遵守了命名原則。
這里有一些你應(yīng)該使用的基本原則:
一個(gè)方法用 動(dòng)詞 開(kāi)頭的表示它做了一些副作用代芜,但是不會(huì)返回任何東西:
- (void)loadView;
- (void)startAnimating;
任何以 名詞 開(kāi)頭的方法埠褪,沒(méi)有副作用而且返回了它指的對(duì)象:
- (UINavigationItem *)navigationItem;
+ (UILabel *)labelWithText:(NSString *)text;
盡量分離兩者,比如,不要在操作數(shù)據(jù)的時(shí)候產(chǎn)生副作用钞速,反之亦然贷掖。這會(huì)讓你的副作用包含到代碼更小的細(xì)分粒度里面,讓調(diào)試變得非常困難渴语。
結(jié)構(gòu)
Pragma marks 是把你代碼分組的一個(gè)好方法苹威,特別是在 view Controller里。下面的結(jié)構(gòu)可以適用于絕大多數(shù) 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
最重要的一點(diǎn)是在你項(xiàng)目的類中保持一致性
其他風(fēng)格指南
我們公司沒(méi)有任何公司級(jí)別的代碼風(fēng)格指南驾凶,詳細(xì)看看其他開(kāi)發(fā)者的 Objective-C 風(fēng)格指南很有用牙甫,即使一些內(nèi)容是公司相關(guān)的或者過(guò)于激進(jìn)了。
診斷
編譯警告
推薦你盡可能多打開(kāi)編譯警告狭郑,并且像對(duì)待錯(cuò)誤一樣對(duì)待編譯警告腹暖。推薦 這個(gè)PPT。這個(gè)幻燈片覆蓋了如何在特定文件翰萨,或者特別代碼段里面消除相關(guān)警告的內(nèi)容脏答。
簡(jiǎn)單的來(lái)說(shuō),至少需要在 _“Other Warning Flags” 編譯設(shè)置里面定義下面的值:
-
-Wall
(增加很多的警告) -
-Wextra
(增加更多的警告)
同時(shí)打開(kāi) “Treat warnings as errors”
Clang 靜態(tài)分析
Clang 編譯器(Xcode使用的)有一個(gè) 靜態(tài)分析器 來(lái)進(jìn)行你的代碼控制和數(shù)據(jù)流的分析亩鬼,來(lái)檢測(cè)編譯器不能檢測(cè)的許多錯(cuò)誤殖告。
你可以通過(guò)在 Xcode 里面手動(dòng)運(yùn)行 Product → Analyze 菜單項(xiàng)來(lái)手動(dòng)執(zhí)行代碼分析
分析器可以用淺或者深的模式允許,后者更加慢雳锋,但是可以從跨函數(shù)的控制流和數(shù)據(jù)流上分析更多問(wèn)題
推薦:
- 打開(kāi) 所有 分析器檢查 (通過(guò)在 building setting 中打開(kāi)所有 “Static Analyzer” 選項(xiàng))
- 在 release 的編譯設(shè)置里面打開(kāi) “Analyze during ‘Build’” 來(lái)讓分析器自動(dòng)在發(fā)布的版本構(gòu)建的時(shí)候允許垃喊。(這樣你就不需要記住要手動(dòng)運(yùn)行了)
- 把 “Mode of Analysis for ‘Analyze’” 設(shè)置為 Shallow (faster)
- 把 “Mode of Analysis for ‘Build’” 設(shè)置為 to Deep
Faux Pas
我們自己的Ali Rantakari 創(chuàng)建的靴寂,F(xiàn)aux Pas 是一個(gè)極佳的靜態(tài)錯(cuò)誤檢測(cè)工具,它分析你的代碼并且找出那些你自己甚至都沒(méi)發(fā)現(xiàn)的問(wèn)題。在提交你的 App 到應(yīng)用商店前用它吧掖肋!
調(diào)試
當(dāng)你的 App 崩潰的時(shí)候,Xcode 不會(huì)默認(rèn)進(jìn)入到調(diào)試器里面色罚。為了調(diào)試窟扑,你需要增加一個(gè)異常斷點(diǎn)(在 Xcode 的 Debug 導(dǎo)航中點(diǎn) “+”),來(lái)在異常發(fā)生的時(shí)候退出執(zhí)行袋马。在很多情況下初澎,你需要看看觸發(fā)這些異常的代碼。它會(huì)捕捉任何異常虑凛,即使是已經(jīng)處理的碑宴。如果 Xcode 在 一個(gè)第三方庫(kù)里面中斷執(zhí)行,比如桑谍,你可能需要通過(guò)選擇 Edit Breakpoint 并且設(shè)置 Exception 為 Objective-C.延柠。
對(duì)于視圖 debug, Reveal 和 Spark Inspector 這兩個(gè)強(qiáng)有力的可視化檢查工具可以幫你省下很多時(shí)間锣披,特別是在你使用 Auto Layout 并且希望定位出問(wèn)題或者溢出屏幕的視圖的時(shí)候捕仔。Xcode 提供了免費(fèi)的類似功能 匕积,但是只能適用于 iOS 8+ 并且不那么好用。
分析
Xcode 有一個(gè)叫 Instruments 的分析工具榜跌,它包括了
許多分析內(nèi)存闪唆,CPU,網(wǎng)絡(luò)通訊钓葫,圖形以及更多的工具悄蕾,它有點(diǎn)復(fù)雜的,但是它的追蹤內(nèi)存泄漏的時(shí)候還是蠻直觀的础浮。只需要在 Xcode 中 選擇 Product > Profile帆调,選擇 Allocations, 點(diǎn)擊 Record 按鈕并且用一些有用的字符串過(guò)濾申請(qǐng)空間的信息豆同,比如你自己的app的類名番刊。它會(huì)在固定的列中統(tǒng)計(jì),并且告訴你每個(gè)對(duì)象有多少實(shí)例影锈。到底是什么類一直增加實(shí)例導(dǎo)致內(nèi)存泄漏芹务。
Instruments 也有自動(dòng)化的工具來(lái)進(jìn)行錄制并且運(yùn)行UI交互以及JavaScript文件。. UI Auto Monkey 是一個(gè)自動(dòng)化隨機(jī)點(diǎn)擊鸭廷、滑動(dòng)以及旋轉(zhuǎn)你的app的腳本枣抱,他在壓力、滲透測(cè)試中很有用辆床。
統(tǒng)計(jì)
強(qiáng)烈推薦使用一些統(tǒng)計(jì)框架佳晶,他們直觀地告訴你有多少人用你的應(yīng)用。X 特性增加了用戶么讼载?Y 按鈕很難找到么轿秧?為了回答這些問(wèn)題,你可以發(fā)送事件咨堤,允許時(shí)間以及其他記錄的信息到一個(gè)聚集它們并且可視化它們的服務(wù)菇篡。比如, Google Tag Manager 吱型。這個(gè)是一個(gè)比 Google Analytics 更加有用的地方是可以在app 和統(tǒng)計(jì)之間插入數(shù)據(jù)逸贾,所以數(shù)據(jù)邏輯可以通過(guò) web 服務(wù)進(jìn)行修改陨仅,而不用更新你的app津滞。
一個(gè)好的實(shí)踐是創(chuàng)建一個(gè)簡(jiǎn)單的 helper 類,比如 XYZAnalyticsHelper
灼伤,處理 app 內(nèi)部 model 以及數(shù)據(jù)格式 (XYZModel, NSTimeInterval, …)的變換触徐,來(lái)適配字符串為主的數(shù)據(jù)層,
- (void)pushAddItemEventWithItem:(XYZItem *)item editMode:(XYZEditMode)editMode
{
NSString *editModeString = [self nameForEditMode:editMode];
[self pushToDataLayer:@{
@"event": "addItem",
@"itemIdentifier": item.identifier,
@"editMode": editModeString
}];
}
另外的優(yōu)點(diǎn)是狐赡,你在必要的時(shí)候可以替換整個(gè)統(tǒng)計(jì)框架撞鹉,而不用改變 app 其他部分。
Crash Logs 崩潰日志
應(yīng)該讓你的 app 向一個(gè)服務(wù)發(fā)送崩潰日志。你可以手動(dòng)實(shí)現(xiàn)鸟雏,通過(guò) PLCrashReporter 以及你自己的后端享郊。但是強(qiáng)烈推薦你使用現(xiàn)有的服務(wù),比如下面的
當(dāng)你配置好后孝鹊,確保你 保存了 the Xcode archive (.xcarchive
) 對(duì)于每一個(gè) app 放出的版本炊琉。這個(gè) 歸檔中包含了構(gòu)建的app的二進(jìn)制以及調(diào)試符號(hào)(dSYM
),你需要用每個(gè)版本特定的app把你的 Crash 報(bào)告符號(hào)化又活。
構(gòu)建
構(gòu)建設(shè)置
每一個(gè)簡(jiǎn)單的 app 都可以不同的方式構(gòu)建苔咪,最基本的分離是 Xcode 給你 debug 和 release 之間的構(gòu)建方案。后者在編譯的時(shí)候有更多的優(yōu)化柳骄,可能會(huì)導(dǎo)致你需要多調(diào)試一些問(wèn)題团赏。 Apple 建議你在開(kāi)發(fā)的時(shí)候用 debug 模式,在打包的時(shí)候用 release 設(shè)置耐薯。這是默認(rèn)的 Scheme (Play 和 Stop 后面的下拉菜單)舔清,運(yùn)行Run 的時(shí)候會(huì) 用 debug 設(shè)置而運(yùn)行 Archive 的時(shí)候會(huì)使用 release。
然后可柿,對(duì)于真實(shí)的 app 這似乎太簡(jiǎn)單了鸠踪。你 不該 設(shè)置多個(gè)為測(cè)試,staging和其他相關(guān)的開(kāi)發(fā)活動(dòng)复斥。每一個(gè)可能有自己的 URL营密,日志級(jí)別,bundle ID)所以你可以一起安裝它們目锭,以及描述文件评汰。然后一個(gè)簡(jiǎn)單的 debug/release 區(qū)別不能分離這些,你可以在你項(xiàng)目的設(shè)置中的 Info 選項(xiàng)卡做更多的編譯設(shè)置痢虹。
關(guān)于構(gòu)建設(shè)置的 xcconfig
文件
通常構(gòu)建設(shè)置是 Xcode GUI定義的被去,但是你同樣可以用 configuration settings files (“.xcconfig
files”),優(yōu)點(diǎn)是:
- 你可以注釋
- 你可以
#include
其他構(gòu)建設(shè)置文件, 能幫助你減少重復(fù):- 如果你有一些適用于所有構(gòu)建設(shè)置的設(shè)置, 增加一個(gè)
Common.xcconfig
并且在其他構(gòu)建設(shè)置文件里面#include
- 如果你奖唯,比如惨缆,希望有一個(gè) “Debug” 構(gòu)建設(shè)置文件,允許編譯器優(yōu)化丰捷,你只需要
#include "MyApp_Debug.xcconfig"
來(lái)重載其他設(shè)置
- 如果你有一些適用于所有構(gòu)建設(shè)置的設(shè)置, 增加一個(gè)
- 沖突解決和合并變得更輕松
更多關(guān)于這個(gè)主題的信息請(qǐng)看這個(gè) PPT坯墨。
Targets
一個(gè) target 是處于比項(xiàng)目更低一級(jí)的級(jí)別。比如病往,一個(gè)項(xiàng)目可能有多個(gè)target捣染,可能重載它的項(xiàng)目設(shè)置。簡(jiǎn)單地說(shuō)停巷,每個(gè) target 和一個(gè) app相當(dāng)耍攘。比如榕栏,你可能有幾個(gè)因?yàn)閲?guó)家區(qū)分的 app (從同樣的代碼編譯)來(lái)提交到 App Store。每個(gè)會(huì)有 development/staging/release 的構(gòu)建蕾各,所以最好通過(guò)構(gòu)建設(shè)置而不是 target來(lái)區(qū)分扒磁。一個(gè) app 只有一個(gè) target 是很少見(jiàn)的。
Schemes
Schemes 告訴 Xcode 在你點(diǎn)擊 Run, Test, Profile, Analyze 或者 Archive 操作的時(shí)候應(yīng)該怎么做式曲。它們把這些操作映射到一個(gè) target 和一個(gè)構(gòu)建設(shè)置中渗磅。你可以傳遞啟動(dòng)參數(shù),比如 app 需要允許的語(yǔ)言(為了測(cè)試本地化)或者一些了為了調(diào)試用的診斷標(biāo)志检访。
一個(gè)建議的 Scheme 命名是 MyApp (<Language>) [Environment]
:
MyApp (English) [Development]
MyApp (German) [Development]
MyApp [Testing]
MyApp [Staging]
MyApp [App Store]
對(duì)于大多數(shù)環(huán)境來(lái)說(shuō)語(yǔ)言部分是不必要的始鱼,app 會(huì)可能會(huì)以非 Xcode 的方式安裝,比如用 TestFlight脆贵, 啟動(dòng)參數(shù)會(huì)被忽略医清。這個(gè)情況下,為了測(cè)試本地化需要手動(dòng)設(shè)置設(shè)備的語(yǔ)言卖氨。
部署
部署一個(gè)軟件到 iOS 設(shè)備上并不直觀会烙。但是有一些核心觀點(diǎn),只要理解了筒捺,對(duì)你有很大的幫助柏腻。
簽名
當(dāng)你需要在真實(shí)設(shè)備上運(yùn)行軟件的時(shí)候,你需要用一個(gè) Apple 認(rèn)證的 證書(shū) 簽名系吭。每一個(gè)證書(shū)是連接到一個(gè) 公五嫂、私 密鑰對(duì),私鑰會(huì)保存在你的 Mac 的 KeyChain 里面肯尺,證書(shū)有兩種類型
- 開(kāi)發(fā)證書(shū): 每個(gè)組的開(kāi)發(fā)者都有自己的證書(shū)沃缘,而且它通過(guò)請(qǐng)求特到。Xcode可以幫你完成则吟,但是最好不用點(diǎn)擊 "Fix issue" 來(lái)完成槐臀,而是理解它到底做了什么事情。在部署開(kāi)發(fā)版本到設(shè)備上的時(shí)候需要這個(gè)證書(shū)氓仲。
- 發(fā)布證書(shū): 可以有多個(gè)水慨,但是最好每一個(gè)組織有一個(gè),并且通過(guò)內(nèi)部渠道共享敬扛。在提交 App 到 App Store 或者你的企業(yè)的內(nèi)部 App Store的時(shí)候需要這個(gè)證書(shū)晰洒。
描述文件
除了證書(shū),還有 描述文件舔哪, 它把設(shè)備和證書(shū)連接起來(lái)欢顷。而且槽棍,它分成開(kāi)發(fā)和發(fā)布兩種類型捉蚤。
- Development provisioning profile: 開(kāi)發(fā)描述文件抬驴, 它包含了一個(gè)包含所有能安裝這個(gè) app 的設(shè)備列表。它連接了一個(gè)或者多個(gè)開(kāi)發(fā)者允許這個(gè)描述文件使用的證書(shū)缆巧。描述文件可以確認(rèn)特定的 app布持,但是對(duì)于大多數(shù)開(kāi)發(fā)目的,它特別適合用一個(gè)通配符描述文件陕悬,也就是 Apple ID 以一個(gè) 星號(hào) (*)結(jié)尾的题暖。
-
Distribution provisioning profile: 發(fā)布描述文件 本身,對(duì)于不同使用目的也有不同的類型捉超。每一個(gè)發(fā)布描述文件 鏈接到一個(gè)發(fā)布證書(shū)胧卤,并且在證書(shū)過(guò)期的時(shí)候失效。
- Ad-Hoc: 就像開(kāi)發(fā)證書(shū)一個(gè)拼岳,它包含了一個(gè) app 可以安裝的設(shè)備的白名單枝誊。這個(gè)描述文件可以用來(lái)做 beta 版本,每年可以測(cè)試 100 個(gè)設(shè)備惜纸。為了做更細(xì)致的測(cè)試以及升級(jí)到 1000 個(gè)測(cè)試用戶叶撒,你可以使用 Apple 最近發(fā)布的 TestFlight 服務(wù), Supertop 提供了一個(gè) summary of its advantages and issues.
- App Store: 這個(gè) profile 沒(méi)有列出設(shè)備耐版,任何人都可以通過(guò)蘋(píng)果官方的發(fā)布來(lái)安裝祠够,所有 App Store 發(fā)布的 App都需要這個(gè)證書(shū)
- Enterprise: 就像 App Store,沒(méi)有設(shè)備白名單粪牲,任何通過(guò)企業(yè)內(nèi)部網(wǎng)絡(luò)的人都可以從內(nèi)部“應(yīng)用商店”安裝古瓤。應(yīng)用商店可能只是一個(gè)帶連接的網(wǎng)站。這個(gè)描述文件只允許企業(yè)賬號(hào)使用腺阳。
想同步所有的證書(shū)和描述文件到你的機(jī)器湿滓,可以去 Xcode 的 Preferences 的 Accounts下,添加你的 Apple ID, 然后雙擊你的 Team 名稱舌狗。然后底部會(huì)有一個(gè)刷新按鈕叽奥,有時(shí)候你需要重啟 Xcode 來(lái)讓東西顯示出來(lái)。
調(diào)試描述文件
有時(shí)候你需要調(diào)試一個(gè)描述文件的問(wèn)題痛侍。比如朝氓,XCode 可能拒絕安裝 app 到一個(gè)設(shè)備中,因?yàn)樵O(shè)備沒(méi)有在開(kāi)發(fā)或者 Ad-doc發(fā)布的的描述文件設(shè)備列表中主届。這個(gè)情況下赵哲,你可以使用 Craig Hockenberry 的 Provisioning 插件,瀏覽 ~/Library/MobileDevice/Provisioning Profiles
君丁,選擇一個(gè) .mobileprovision
文件枫夺,并用空格鍵調(diào)用 Finder 的快速查看特性,它會(huì)告訴你關(guān)于設(shè)備绘闷,entitlements橡庞,證書(shū)以及 Apple ID的信息较坛。
上傳
iTunes Connect 是你管理上傳到 App Store的 App 的后臺(tái)網(wǎng)站。 Xcode 6 需要一個(gè) Apple ID 作為開(kāi)發(fā)者賬號(hào)來(lái)簽名并且上傳二進(jìn)制扒最。如果你有多個(gè)開(kāi)發(fā)者賬號(hào)而且要上傳它們的 app 就需要一點(diǎn)技巧丑勤。 因?yàn)?一個(gè) Apple ID 只能和一個(gè) iTunes Connect 賬號(hào)關(guān)聯(lián), 一個(gè)變通方案是為每個(gè) iTunes Connect 賬號(hào)創(chuàng)建一個(gè)新的 Apple ID, 使用 Application Loader 取代 Xcode 來(lái)上傳應(yīng)用。這樣有效地解除了上傳最終 .app
文件構(gòu)建和簽名的耦合吧趣。
在上傳二進(jìn)制后法竞,耐心等待,可能要花上一個(gè)小時(shí)强挫。當(dāng)你的 App 出現(xiàn)后岔霸,可以鏈接到對(duì)應(yīng)的App版本并且提交審核
應(yīng)用內(nèi)購(gòu)買(mǎi)
當(dāng)驗(yàn)證一個(gè) App內(nèi)購(gòu)的 receipt的時(shí)候,記住做以下步驟:
- Authenticity: receipt 是來(lái)自 Apple 的
- Integrity: receipt 沒(méi)有被篡改
- App match: receipt 的 App bundle ID 和你的 App bundle ID 一致
- Product match: receipt 里面產(chǎn)品 ID和你期望的一致
- Freshness: 你之前沒(méi)有驗(yàn)證一樣的 receipt ID
如果可能俯渤,把你的 IAP 存儲(chǔ)銷售相關(guān)的內(nèi)容存儲(chǔ)在服務(wù)器端秉剑,并且只在一個(gè)合法的經(jīng)過(guò)上述檢查的 receipt。這樣的設(shè)計(jì)避免了常見(jiàn)的盜竊機(jī)制稠诲,同時(shí)侦鹏,因?yàn)榉?wù)器做了驗(yàn)證,所以你可以使用 Apple 的 HTTP 驗(yàn)證服務(wù)來(lái)取代你自己的 PKCS #7
/ ASN.1
格式臀叙。
更多關(guān)于這個(gè)主題的信息可以看Futurice blog: Validating in-app purchases in your iOS app