Introduction
這個(gè)style guide規(guī)范描述了我們iOS開(kāi)發(fā)團(tuán)隊(duì)喜歡的Objectiv-C編程習(xí)慣。
代碼規(guī)范的意義,在于提高團(tuán)隊(duì)各個(gè)成員寫的代碼的一致性和可讀性。一致性能減少工程師編寫代碼風(fēng)格的困惑和猶豫;良好的可讀性更是能幫助我們?yōu)g覽其他人代碼、以便合作和code review。
它并不是去定義the right way or the wrong way,而是達(dá)成一個(gè)需要遵守的共識(shí),it's about agreeing on doing things the same way。
這個(gè)規(guī)范可能比其他一般的style guide規(guī)范涵蓋了更多的方面和細(xì)節(jié)。
歡迎聯(lián)系我,給出你關(guān)于這個(gè)規(guī)范的想法。
Credits
它絕大部分內(nèi)容,來(lái)自Wonderful Objective-C style guide,并加入了自己的一些偏好。
Background
此規(guī)范也參考了一些蘋果官方文檔。如果在此規(guī)范里找不到你關(guān)心的內(nèi)容,可以嘗試在下面的文檔里尋找:
- The Objective-C Programming Language
- Cocoa Fundamentals Guide
- Coding Guidelines for Cocoa
- iOS App Programming Guide
Tools
VVDocumenter-Xcode
VVDocumenter-Xcode是一個(gè)幫我們寫注釋(用于生成文檔)的Xcode插件??梢允褂?a target="_blank" rel="nofollow">Alcatraz Xcode package manager安裝后重啟Xcode。
clang-format
- Download clang-format 3.5.2
- Copy clang-format into folder /usr/local/bin/
- Copy pre-commit into folder .gits/hooks/
- chmod +x pre-commit
- Change .clang-format if you want to change the style.
Cocoapods
當(dāng)需要一些“輪子”時(shí),可以先在現(xiàn)有的代碼里面找找(Podfile或者Libraray)。沒(méi)有的話,再用pod search或者github找找,看有沒(méi)有代碼質(zhì)量高的、大家廣泛使用的(watch、star、fork數(shù)量多)、比較活躍、比較友好的第三方代碼,看看實(shí)現(xiàn)原理以及使用方法,是否適合我們的需求。
或可以通過(guò)繼承,滿足我們的需求。如果第三方的代碼有bug,或者需要改進(jìn),可以自己fork后修改使用(順便可以給作者發(fā)一個(gè)pull request)
在你有更好的實(shí)現(xiàn)、有時(shí)間的前提下,可以自己造更好的“輪子”。
Podfile.lock 有變動(dòng)表示使用的第三庫(kù)版本有變化,如果采用新的版本,需要檢查新版本的第三方庫(kù)沒(méi)有問(wèn)題。
盡量使用最新版本的第三方庫(kù)。
但發(fā)包前對(duì)此文件的修改要多尤其小心。最好發(fā)完包后,自己看看新版本有無(wú)問(wèn)題,如果有必要再讓測(cè)試看看。
Cocoapods命令解釋
- pod update: Updating local specs repositories, 更新工程中的第三方庫(kù)
- pod update --no-repo-update:使用local specs repositories中最新的版本
- pod install: Updating local specs repositories, 安裝podfile.lock中規(guī)定版本
- pod install --no-repo-update:當(dāng)podfile.lock中規(guī)定的版本,不在local specs repositories中,install會(huì)失敗
Cocoapods使用規(guī)范
- pod update命令,由小組某個(gè)成員,開(kāi)啟定時(shí)任務(wù):
30 12 * * 1 cd /Users/Can/Workspace/project_name;pod update;
//用來(lái)檢查第三方庫(kù)是否有更新,并升級(jí)庫(kù)。這個(gè)是為了定期檢查第三方庫(kù)是否有新版本。
//一個(gè)小組成員做這件事就可以
//發(fā)現(xiàn)Podfile.lock變了,表示第三方代碼版本升級(jí)了,應(yīng)該了解一下升級(jí)的內(nèi)容,另外上線前不要輕易升級(jí)影響功能第三方代碼 - pod install命令,由小組其他成員,開(kāi)啟定時(shí)任務(wù):
30 12 * * * cd /Users/Can/Workspace/project_name;pod install;
//主要用來(lái)Updating local specs repositories,防止install失敗 - pull完代碼推薦使用pod install,pod install --no-repo-update,后者速度更快可能會(huì)報(bào)錯(cuò)
- git checkout 老代碼,推薦使用pod install --no-repo-update,因?yàn)槭抢洗a,一般不會(huì)報(bào)錯(cuò)
- Podfile.lock任何情況下不能刪除
- pod update --no-repo-update,只用在pod install不成功,但需要馬上編譯成功的情況
新版本的cocoapods的一些命令執(zhí)行方式,有些變化,上面的內(nèi)容有部分過(guò)期
Table of Contents
- Language
- Project Organization
- Code Organization
- Line Wrapping (Code Width)
- Spacing
- Comments
- Documentation
- Naming
- Methods
- Variables
- Property Attributes
- Dot-Notation Syntax
- Literals
- Constants
- Enumerated Types
- Switch Statements and Case Label Blocks
- Private Properties
- Booleans
- Conditionals
- Return Statements
- Init Methods
- Class Constructor Methods
- CGRect Functions
- Error handling
- Singletons
- Line Breaks
- Warnings
- Other Objective-C Style Guides
<h2 id="language">Language</h2>
使用美國(guó)英語(yǔ),而非英國(guó)等國(guó)家英語(yǔ)。
Preferred:
UIColor *myColor = [UIColor whiteColor];
Not Preferred:
UIColor *myColour = [UIColor whiteColor];
<h2 id="project-organization">Project Organization</h2>
Xcode工程,采用類似下面的工程文件結(jié)構(gòu)。
Your_Project
|-- AppDelegate.h
|-- AppDelegate.m
|-- Images.xcassets
|-- MainMenu.xib
|-- Supporting Files
|-- Models
|-- Views
|-- Controllers
|-- Stores
|-- Helpers
|-- ...
并且保持對(duì)應(yīng)的物理文件系統(tǒng)路徑,方便在finder里面查找,也能保持finder內(nèi)文件整齊。因此需要加入新的group時(shí),需要先在文件系統(tǒng)里新建一個(gè)folder,然后add這個(gè)folder。
<h2 id="code-organization">Code Organization</h2>
使用#pragma mark -
對(duì).m文件里面的Methods進(jìn)行歸類。
#pragma mark - Class Methods
+ (ClassObject)classWithDefaults;
+ (ClassObject)convertToJSON;
+ (ClassObject)convertToXML;
#pragma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - IBActions
- (IBAction)submitData:(id)sender {}
#pragma mark - Public
- (void)publicMethod {}
#pragma mark - Private
- (void)privateMethod {}
#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
#pragma mark - NSObject
- (NSString *)description {}
<h2 id="language">line-wrapping-(code-width)</h2>
合適的規(guī)范一般并不需要一行能容納無(wú)限長(zhǎng)度的代碼,80列的限制反而能提醒你,可能你的代碼可讀性不太好。
Preferences->Text Editing->Page Guide at column:
As seen here:
<h2 id="spacing">Spacing</h2>
- 縮進(jìn)使用4個(gè)空格(Xcode默認(rèn))。
- 操作符左右留空格。
Preferred:
if (self.changeEable && self.shouldChange)
sum = count1 + count2
Not Preferred:
if (self.changeEable&&self.shouldChange)
sum=count1+count2
-
方法體的花括號(hào)需要在新的一行開(kāi)啟,在新的一行關(guān)閉。而其它花括號(hào)(
if
/else
/switch
/while
etc.),加入一個(gè)空格后在行尾開(kāi)啟,在新一行關(guān)閉(Xcode默認(rèn))。
Preferred:
if (user.isHappy) {
// Do something
} else {
// Do something else
}
Not Preferred:
if (user.isHappy)
{
// Do something
} else
{
// Do something else
}
在使用else或者else if,它們前后的大括號(hào)應(yīng)該是在一行,否則不太好看:
Not Preferred:
if (user.isHappy)
{
// Do something
}
else
{
// Do something else
}
- methods之間只留一個(gè)空行。
- 盡量使用auto-synthesis。如果需要使用
@synthesize
,每個(gè)property需要新開(kāi)一行,@dynamic
也是需要新開(kāi)一行。 - 當(dāng)methods里面需要傳入block時(shí),不要使用冒號(hào)對(duì)齊對(duì)方式:
Preferred:
// blocks are easily readable
[UIView animateWithDuration:1.0 animations:^{
// something
} completion:^(BOOL finished) {
// something
}];
Not Preferred:
// colon-aligning makes the block indentation hard to read
[UIView animateWithDuration:1.0
animations:^{
// something
}
completion:^(BOOL finished) {
// something
}];
<h2 id="comments">Comments</h2>
如果需要注釋,它只用來(lái)解釋為什么這段代碼要這么寫,并且是正確的,代碼變化時(shí)也需要馬上更新,不能有誤導(dǎo)。
盡可能避免大量使用注釋,好的代碼應(yīng)該盡可能是self-documenting。
Exception: 上面兩條不適用于生成文檔用的注釋.
<h2 id="documentation">Documentation</h2>
在頭文件中的以下內(nèi)容,需要寫注釋用來(lái)生成文檔(可以使用appledoc)。
VVDocumenter-Xcode也可以幫我們。
- Classes
- Prototype
- Constants
- Properties
- Class Methods
- Instance Methods
<a id="class-documentation">Class Documentation</a>
-- **EXAMPLE PENDING** --
<a id="constant-documentation">Constant Documentation</a>
/**
* @memberof PDFReaderConfig
* Default value for bookmarksEnabled: TRUE
*/
extern const BOOL kPDFReaderDefaultBookmarksEnabled;
<a id="property-documentation">Property Documentation</a>
/**
* Enable/disable bookmark support.
*
* @see kPDFReaderDefaultBookmarksEnabled
*/
@property (nonatomic, readwrite, unsafe_unretained, getter=isBookmarksEnabled)
BOOL bookmarksEnabled;
<a id="class-method-documentation">Class Method Documentation</a>
/**
* Returns the shared PDFReaderConfig instance, creating it if necessary.
*
* @return The shared PDFReaderConfig instance.
*/
+ (instancetype)sharedConfig;
<a id="instance-method-documentation">Instance Method Documentation</a>
/**
* Initializes and returns a newly allocated PDFReaderViewController object.
*
* @param object Reference to an initialized PDFReaderDocument
*
* @return Initialized class instance or nil on failure
*
* @throws "<MissingArguments>" When object is nil
* @throws "<WrongType>" When object is not a reference to a PDFReaderDocument
* object
*
* @remark Designated initializer.
*/
- (instancetype)initWithReaderDocument:(PDFReaderDocument *)object;
/**
* Update bookmark state for each page in the PDFReaderDocument's bookmarks
* list.
*/
- (void)updateToolbarBookmarkIcon;
<h2 id="naming">Naming</h2>
<h3 id="naming-conventions-for-methods-and-variables">Naming Conventions for Methods and Variables</h3>
詳盡的、描述性的方法和變量名,對(duì)代碼的self-documenting是很有幫助的。命名的清晰性和簡(jiǎn)潔性都很重要,然而,在Objective-C的世界里,不能為簡(jiǎn)潔性犧牲清晰性。
Preferred:
UIButton *settingsButton;
NSString *pageTitle = @"My Title";
int pageCounter = 0;
Not Preferred:
UIButton *setBut;
NSString *string = @"My Title";
int c = 0;
<h3 id="naming-conventions-for-constants-and-macros">Naming Conventions for Constants and Macros</h3>
下面的命名方式第一眼望去可能覺(jué)得好復(fù)雜,習(xí)慣習(xí)慣了就好了,別偷懶。。。
tPRE_Space_Name
Where:
Element | Definition | Value | Example |
---|---|---|---|
t | type = constant | k* | kMIX_MyClass_DefaultTitle |
type = macro | m* | mBUZ_MyClass_doubleIt | |
PRE | three-letter prefix | XXX | kXXX_MyClass_MenuBarHeight |
Space | unique namespace within tPRE | WindowSize | kXXX_MyClass_WindowSize |
Name | unique name within tPRE_Space | BorderColor | kZRE_MyClass_BorderColor |
isIphone | mZBB_MyClass_isIphone |
類型(t)元素要么是"k" (for constant),要么是"m" (for macro)。
PRE元素一般是公司名稱前三個(gè)字母的大寫。
Space元素可以是Library名、類名、app名稱或模塊名稱,采用駝峰命名法并首字母大寫。
Name元素是具體名稱,constant采用駝峰命名法并首字母大寫,macro采用駝峰命名法并首字母小寫。
Preferred:
static const NSInteger kMIX_PDFReader_DefaultPagingViews = 3;
Not Preferred:
static NSTimeInterval const fadetime = 1.7;
Preferred
#define mMIX_MacroLib_rgb(r,g,b) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:1.0f]
Not Preferred
#define RGBColor(r,g,b) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:1.0f]
類的成員變量采用駝峰命名法并首字母小寫。
Preferred:
@property (nonatomic, readwrite, strong) NSString *descriptiveVariableName;
// if/when needed
@synthesize descriptiveVariableName = _descriptiveVariableName;
Not Preferred:
id varnm;
...
@property (strong) id varnm;
...
@synthesize varnm;
<h3 id="naming-conventions-for-enumerated-types">Naming Conventions for Enumerated Types</h3>
Enumerated Type names 用e開(kāi)頭,其他和Constants一樣。
Preferred:
// using the NS_ENUM macro (preferred)
typedef NS_ENUM(NSInteger, eMIX_UtilityLib_PlayerStateType) {
PlayerStateOff,
PlayerStatePlaying,
PlayerStatePaused
};
Not Preferred:
enum {
PlayerStateOff,
PlayerStatePlaying,
PlayerStatePaused
};
typedef NSInteger PlayerState;
<h3 id="underscores">Underscores</h3>
讀或?qū)懰衟roperties,instance variables,都應(yīng)該用self.
,除了以下三種列外情況:
- Setup and Tear down: init and dealloc
- Overriding accessors (getters/setters)
- Archiving activities: e.g. NSCoding Protocol's encodeWithCoder and initWithCoder
當(dāng)一個(gè)對(duì)象還沒(méi)有初始化好,它處于不穩(wěn)定的狀態(tài),調(diào)用getters/setters的副作用,可能會(huì)導(dǎo)致異常情況發(fā)生,所以應(yīng)該直接使用_variableName
。see: Don’t Message self in Objective-C init (or dealloc)。
重載getter/setter來(lái)添加自定義的邏輯,必須使用_variableName
,否則會(huì)無(wú)限循環(huán)。
歸檔或使用其它持久化方法時(shí),使用_variableName
可以避免getters/setters的副作用對(duì)屬性的改變。比如你使用getter讀一個(gè)屬性時(shí),里面的邏輯可能修改了其他屬性的值。
還有,local variables不能使用下劃線。
<h2 id="methods">Methods</h2>
方法名的-/+符號(hào)后面要留一個(gè)空格,冒號(hào)前面的描述詞不能省略,方法里面不要使用and。
Preferred:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
Not Preferred:
-(void)setT:(NSString *)text i:(UIImage *)image;
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
- (id)taggedView:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height; // Never do this.
<h2 id="variables">Variables</h2>
變量名應(yīng)該盡可能的描述自身的存儲(chǔ)信息的內(nèi)容。for()
小括弧中的臨時(shí)變量、或用于計(jì)數(shù)的變量,可以簡(jiǎn)單一些。
星號(hào)*
應(yīng)該緊靠變量名,而不是類型后面,比如:NSString *text
而不是 NSString* text
或 NSString * text
。
如果存在qualifier,格式應(yīng)該為ClassName * qualifier variableName;
。
為了一致性,類的成員變量應(yīng)該使用property
。
Preferred:
@interface MyAppTutorial : NSObject
@property (strong, nonatomic) NSString *tutorialName;
@end
Not Preferred:
@interface MyAppTutorial : NSObject {
NSString *tutorialName;
}
<h2 id="property-attributes">Property Attributes</h2>
property的所有屬性,應(yīng)該按照atomicity、accessibility(readonly, readwrite)、storage的順序明確列出來(lái)。
Preferred:
@property (nonatomic, readwrite, weak) IBOutlet UIView *containerView;
@property (nonatomic, readwrite, strong) NSString *tutorialName;
Not Preferred:
@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (nonatomic) NSString *tutorialName;
一個(gè)非容器類,如果它具有mutable的相關(guān)類,比如NSString與NSMutableString、NSURLRequest與NSMutableURLRequest,在定義類成員變量時(shí),應(yīng)該使用copy
而不是strong
。why?當(dāng)你定義NSSting為strong時(shí),它可能被賦值為NSMutableString類型對(duì)象,這個(gè)對(duì)象可能會(huì)在你不注意的情況下改變值,而你既然把它類型聲明為NSSting,這種情況顯然不是你需要的。
Preferred:
@property (nonatomic, readwrite, copy) NSString *tutorialName;
Not Preferred:
@property (nonatomic, readwrite, strong) NSString *tutorialName;
<h2 id="dot-notation-syntax">Dot-Notation Syntax</h2>
前面講Underscores時(shí),說(shuō)了下點(diǎn)語(yǔ)法的使用。
還有一個(gè)限制是,點(diǎn)語(yǔ)法只應(yīng)該用在訪問(wèn)對(duì)象成員變量,而不是去調(diào)用對(duì)象的方法:
Preferred:
NSInteger arrayCount = self.array.count;
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
Not Preferred:
NSInteger arrayCount = [self.array count];
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
<h2 id="literals">Literals</h2>
盡可能使用語(yǔ)法糖去創(chuàng)建immutable對(duì)象。
Preferred:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @10018;
Not Preferred:
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingStreetNumber = [NSNumber numberWithInteger:10018];
<h2 id="constants">Constants</h2>
因?yàn)轭愋桶踩木壒?,常量不?yīng)該用#define
去定義,應(yīng)該聲明為全局變量。
Preferred:
// Format: type const constantName = value;
NSString * const kMIX_MyClassShortDateFormat = @"MM/dd/yyyy";
// Macro: #define constantName value
\#define mMIX_MacroLib_isIPad (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
Not Preferred:
\#define CompanyName @"MyCompany"
\#define thumbnailHeight 2
為了使全局變量能夠在其他文件類使用,可以在.h
中加入:
extern NSString * const kMIX_MyClass_ShortDateFormat;
如果這個(gè)全局變量只用在當(dāng)前類里面,需要在.m
中去定義,并且前面加上static
關(guān)鍵字限定作用域。
static NSString * const kMIX_MyClass_ShortDateFormat = @"MM/dd/yyyy";
一個(gè)static變量如果定義在函數(shù)體里面,它的值能夠一直保存,下次調(diào)用這個(gè)函數(shù)時(shí),它的值還是上次調(diào)用這個(gè)函數(shù)后的值。
<h2 id="enumerated-types">Enumerated Types</h2>
使用NS_ENUM()
去定義枚舉,它能夠讓編譯器檢查枚舉的數(shù)據(jù)類型。在swift里使用Objecive-C的枚舉,也是需要使用NS_ENUM()
才可以。
Preferred
// using the NS_ENUM macro (preferred)
typedef NS_ENUM(NSInteger, eMIX_UtilityLib_PlayerStateType) {
PlayerStateOff,
PlayerStatePlaying,
PlayerStatePaused
};
// with explicit value assignments
typedef NS_ENUM(NSInteger, eMIX_UtilityLib_PlayerStateType) {
PlayerStateOff = 0,
PlayerStatePlaying = 1,
PlayerStatePaused = 2
};
Not Preferred:
// Apple Modern Objective-C style, provides type checking, enum type declared inline...
typedef enum _eMIX_UtilityLib_PlayerStateType : NSUInteger {
PlayerStateOff,
PlayerStatePlaying,
PlayerStatePaused
} eMIX_UtilityLib_PlayerStateType;
// Apple Modern Objective-C style, provides type checking, enum type declared seperately...
enum _eMIX_UtilityLib_PlayerStateType : NSInteger {
PlayerStateOff,
PlayerStatePlaying,
PlayerStatePaused
};
typedef enum _eMIX_UtilityLib_PlayerStateType : NSInteger eMIX_UtilityLib_PlayerStateType;
// Apple legacy style enum (32bit/64bit portability, no formal relationship between type and enum)...
enum {
PlayerStateOff,
PlayerStatePlaying,
PlayerStatePaused
};
typedef NSInteger PlayerState;
// Apple legacy style enum (no 32bit/64bit portability)...
typedef enum {
PlayerStateOff,
PlayerStatePlaying,
PlayerStatePaused
} PlayerState;
// NOTE: Implies...
// typedef int PlayerState;
// Generic C-style enum (no 32bit/64bit portability)...
typedef enum {
PlayerStateOff,
PlayerStatePlaying,
PlayerStatePaused
} PlayerState;
<h2 id="switch-statements-and-case-label-blocks">Switch Statements and Case Label Blocks</h2>
前面說(shuō)過(guò),switich:
后面的大括號(hào)不應(yīng)該新開(kāi)一行。
case:
后面一般是不用帶大括號(hào)的,但如果你需要在里面聲明臨時(shí)變量,編譯器要求必須要帶大括號(hào)。為了一致性和可讀性,當(dāng)case后面的語(yǔ)句多于一行時(shí),帶上大括號(hào)。
switch (condition) {
case 1:
// ...
break;
case 2: {
// ...
// Multi-line example using braces
break;
}
case 3:
// ...
break;
default:
// ...
break;
}
當(dāng)多個(gè)case:
需要執(zhí)行的代碼一樣時(shí),需要去掉break
,并注釋fall-through!
:
switch (condition) {
case 1:
// ** fall-through! **
case 2:
// code executed for values 1 and 2
break;
default:
// ...
break;
}
當(dāng)使用枚舉時(shí),default
是多余的,不用寫。每個(gè)枚舉值都需要覆蓋到,否則會(huì)有警告。
kMIX_LeftMenuTopItemType menuType = kMIX_LeftMenuTopItemMain;
switch (menuType) {
case kMIX_LeftMenuTopItemMain:
// ...
break;
case kMIX_LeftMenuTopItemShows:
// ...
break;
case kMIX_LeftMenuTopItemSchedule:
// ...
break;
}
<h2 id="private-properties">Private Properties</h2>
類私有的變量應(yīng)該聲明在class extensions (anonymous categories)中。它可以放在.m
文件里,或者新建一個(gè) <headerfile>+Private.h文件,給子類或測(cè)試用。
For Example:
@interface MyAppDetailViewController ()
@property (nonatomic, readwrite, strong) GADBannerView *googleAdView;
@property (nonatomic, readwrite, strong) ADBannerView *iAdView;
@property (nonatomic, readwrite, strong) UIWebView *adXWebView;
@end
<h2 id="image-naming">Image Naming</h2>
圖片放入Images.xassets,命名方式是首字母大寫的駝峰命名法,并將不同的部分往后靠,在命名中盡量加入模塊名、類名進(jìn)行區(qū)分。比如ShortRentCarRentButtonNormal,ShortRentCarRentButtonTouched。
Examples:
-
RefreshBarButtonItem
/RefreshBarButtonItem@2x
andRefreshBarButtonItemSelected
/RefreshBarButtonItemSelected@2x
-
ArticleNavigationBarWhite
/ArticleNavigationBarWhite@2x
andArticleNavigationBarBlackSelected
/ArticleNavigationBarBlackSelected@2x
.
<h2 id="booleans">Booleans</h2>
Objective-C使用YES
和NO
,而不是true
和false
(在CoreFoundation,C或C++代碼里可以使用)。
nil可以表示NO
,所以在if
中,對(duì)象的值與NO
比較是沒(méi)必要的。
在if
中不要任何值與YES
、NO
比較,YES
是#define YES (BOOL)1,而BOOL
是8bit。
Preferred:
if (someObject) {
// code...
}
if (![anotherObject boolValue]) {
// code...
}
Not Preferred:
if (someObject == nil)
if ([anotherObject boolValue] == NO)
if (isAwesome == YES) // Never do this.
if (isAwesome == true) // Never do this.
如果一個(gè)類的成員變量類型是BOOL
,并且它可以是個(gè)形容詞XXX,那么is
的前綴可以省略,但需要加getter=isXXX
@property (nonatomic, readwrite, unsafe_unretained, getter=isEditable) BOOL editable;
參見(jiàn)Cocoa Naming Guidelines.
<h2 id="conditionals">Conditionals</h2>
為了避免錯(cuò)誤、方便以后增加代碼、一致性和可讀性,if
后面的大括號(hào)應(yīng)該一直有,大括號(hào)在if
所在行開(kāi)啟。避免的錯(cuò)誤包括誤將if
大括號(hào)代碼塊里面的內(nèi)容弄到if
外面去了,還有even more dangerous defect
Preferred:
if (!error) {
return success;
}
Not Preferred:
if (!error)
return success;
or
if (!error) return success;
<h3 id="conditional-expressions">Conditional Expressions</h3>
if
小括號(hào)里面的表達(dá)式,應(yīng)該是一個(gè)變量,而不是一個(gè)函數(shù)調(diào)用,在if
小括號(hào)里包含太多邏輯不太容易引起人注意。但類似[anObject boolValue]
這種只會(huì)讀值,不會(huì)有其他邏輯的,是一個(gè)例外,但anObject的變量名也需要足夠清楚,這種情況單獨(dú)再聲明一個(gè)bool型變量不會(huì)增加可讀性。
Preferred:
BOOL continuousPlayEnabled = [[MediaAppPrefs sharedInstance] continuousPlay];
MediaAppTrack *nextMediaTrack = [MediaAppPlayer nextTrack];
if (continuousPlayEnabled && nextMediaTrack) {
// play the next song
}
Not Preferred:
if ([[MediaAppPrefs sharedInstance] continuousPlay] && [MediaAppPlayer nextTrack]) {
// play the next song
}
<h3 id="ternary-operator">Ternary Operator</h3>
三元操作符號(hào)不可濫用,導(dǎo)致可讀性降低。
Preferred:
NSInteger value = 5;
result = (value != 0) ? x : y;
BOOL isHorizontal = YES;
result = (isHorizontal) ? x : y;
Not Preferred:
result = a > b ? x = c > d ? c : d : y;
result = isHorizontal ? x : y;
<h2 id="return-statements">Return Statements</h2>
不要return
一個(gè)函數(shù)調(diào)用。
不要在函數(shù)中間調(diào)用return
,可以在函數(shù)前面檢查傳參合法性時(shí)使用return
。對(duì)于一個(gè)比較長(zhǎng)的函數(shù),中間有個(gè)return
,讀代碼的人很難找到。
Preferred:
- (BOOL) playNext
{
BOOL continuousPlayEnabled = [[MediaAppPrefs sharedInstance] continuousPlay];
MediaAppTrack *nextMediaTrack = [MediaAppPlayer nextTrack];
return (continuousPlayEnabled && nextMediaTrack);
}
Not Preferred:
- (BOOL) playNext
{
return ([[MediaAppPrefs sharedInstance] continuousPlay] && [MediaAppPlayer nextTrack]);
}
<h2 id="init-methods">Init Methods</h2>
初始化方法返回類型應(yīng)該為instancetype
,而不是id
。
- (instancetype)init
{
self = [super init];
if (self) {
// ...
}
return self;
}
參見(jiàn)NSHipster.com。
另外,應(yīng)該定義更安全、合理的初始化方法。參見(jiàn)Cocoa Fundamentals Guide中Object Creation一章節(jié)。
<h2 id="class-constructor-methods">Class Constructor Methods</h2>
類的創(chuàng)建對(duì)象便捷方法返回類型應(yīng)該為instancetype
,而不是id
。
@interface Airplane
+ (instancetype)airplaneWithType:(MyAppAirplaneType)type;
@end
參見(jiàn)NSHipster.com.
<h2 id="cgrect-functions">CGRect Functions</h2>
獲取CGRect
的x
、y
、width
或height
,應(yīng)該使用CGGeometry
functions 而不是直接訪問(wèn)結(jié)構(gòu)體成員。 From Apple's CGGeometry
reference:
All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.
Preferred:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
CGRect frame = CGRectMake(0.0, 0.0, width, height);
Not Preferred:
CGRect frame = self.view.frame;
CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;
CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };
<h2 id="error-handling">Error handling</h2>
參見(jiàn)Apple's developer documentation, 可以向方法傳入NSError變量的指針獲取異常情況,調(diào)用這類方法時(shí),需要對(duì)NSError變量進(jìn)行檢查,如果錯(cuò)誤需要作出合適的處理:
Preferred:
NSError *error;
BOOL success;
success = [self trySomethingWithError:&error];
if (!success) {
// Handle Error
// e.g.
NSLog(@"Something bad just happened: %@", error);
}
Not Preferred:
NSError *error;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
<h2 id="singletons">Singletons</h2>
單例的正確創(chuàng)建方法:
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
// thread-safe init
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
This will prevent possible and sometimes prolific crashes.
另外,還應(yīng)該在dealloc中添加abort()
方法:
- (void)dealloc {
// implement -dealloc & remove abort() when refactoring for
// non-singleton use.
abort();
}
因?yàn)椴粦?yīng)該給單例對(duì)象發(fā)送dealloc
消息。參見(jiàn)StackOverflow thread.
<h2 id="line-breaks">Line Breaks</h2>
避免一行太長(zhǎng),應(yīng)該在合適的地方斷行。
Preferred:
self.productsRequest = [[SKProductsRequest alloc]
initWithProductIdentifiers:productIdentifiers];
Not Preferred:
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
<h2 id="warnings">Warnings</h2>
在target's Build Settings,中應(yīng)該開(kāi)啟"Treat Warnings as Errors",并開(kāi)啟盡可能多的警告。參見(jiàn)additional warnings。如果有些警告你無(wú)法避免,可以使用Clang's pragma feature。
<h2 id="log">Log</h2>
使用DDLog。并使用合適的等級(jí)打印不同級(jí)別的信息。
<h2 id="other-objective-c-style-guides">Other Objective-C Style Guides</h2>
上面的規(guī)范可能你不太喜歡,或者沒(méi)有涉及到你需要的某些方面,可以參考下面的內(nèi)容: