示例
下面是一個示例頭文件官边,演示了@interface聲明的正確注釋和間隔
// GOOD:
#import <Foundation/Foundation.h>
@class Bar;
/**
* 示例類演示了良好的Objective-C的代碼風格闺鲸。所有的接口,
* 類別和協(xié)議(讀取:在頭文件中的所有非凡的頂級聲明)必須注釋,
* 注釋也必須與它們所記錄的對象相鄰旋廷。
*/
@interface Foo : NSObject
/** The retained Bar. */
@property(nonatomic) Bar *bar;
/** The current drawing attributes. */
@property(nonatomic, copy) NSDictionary<NSString *, NSNumber *> *attributes;
/**
* Convenience creation method.
* See -initWithBar: for details about @c bar.
*
* @param bar The string for fooing.
* @return An instance of Foo.
*/
+ (instancetype)fooWithBar:(Bar *)bar;
/**
* Designated initializer.
*
* @param bar A string that represents a thing that does a thing.
*/
- (instancetype)initWithBar:(Bar *)bar;
/**
* Does some work with @c blah.
*
* @param blah
* @return YES if the work was completed; NO otherwise.
*/
- (BOOL)doWorkWithBlah:(NSString *)blah;
@end
一個示例源文件拦盹,演示了一個接口的@ implementation的正確的注釋和間隔草姻。
// GOOD:
#import "Shared/Util/Foo.h"
@implementation Foo {
/** The string used for displaying "hi". */
NSString *_string;
}
+ (instancetype)fooWithBar:(Bar *)bar {
return [[self alloc] initWithBar:bar];
}
- (instancetype)init {
// Classes with a custom designated initializer should always override
// the superclass's designated initializer.
return [self initWithBar:nil];
}
- (instancetype)initWithBar:(Bar *)bar {
self = [super init];
if (self) {
_bar = [bar copy];
_string = [[NSString alloc] initWithFormat:@"hi %d", 3];
_attributes = @{
@"color" : [UIColor blueColor],
@"hidden" : @NO
};
}
return self;
}
- (BOOL)doWorkWithBlah:(NSString *)blah {
// Work should be done here.
return NO;
}
@end
間距和格式
空格鍵 VS Tab 鍵
只使用空格,一次縮進2個空格氛改。我們使用空格來縮進帐萎。不要在代碼中使用制表符。當你點擊tab鍵時胜卤,你應該設置你的編輯器來釋放空格疆导,并在行上減少拖尾空間
行長
OC文件的最大長度為100
在Xcode中,你可以 設置Preferences > Text Editing > Page guide at column: 100來使行長錯誤更容易被發(fā)現(xiàn)
方法聲明和定義
在 - 或 + 和返回類型之間應該使用一個空格,參數列表中除了參數之外沒有空格葛躏。
方法應該是這樣的:
// GOOD:
- (void)doSomethingWithString:(NSString *)theString {
...
}
星號之前的間隔是可選的澈段。在添加新代碼時,要與周圍文件的樣式保持一致舰攒。
如果你有太多的參數要在一條行上败富,那么給一參一行是最好的選擇。如果使用了多個行摩窃,則在參數之前使用冒號對齊兽叮。
// GOOD:
- (void)doSomethingWithFoo:(GTMFoo *)theFoo
rect:(NSRect)theRect
interval:(float)theInterval {
...
}
當第二個或后面的參數名稱比第一個更長的時候,將第二個和后面的行縮進至少四個空格,保持冒號對齊
// GOOD:
- (void)short:(GTMFoo *)theFoo
longKeyword:(NSRect)theRect
evenLongerKeyword:(float)theInterval
error:(NSError **)theError {
...
}
條件語句
if, while, for, , switch和比較運算符后面加空格
// GOOD:
for (int i = 0; i < 5; ++i) {
}
while (test) {};
當循環(huán)體或條件語句適合于一行時充择,可以省略括號德玫。
// GOOD:
if (hasSillyName) LaughOutLoud();
for (int i = 0; i < 10; i++) {
BlowTheHorn();
}
// AVOID:
if (hasSillyName)
LaughOutLoud(); // AVOID.
for (int i = 0; i < 10; i++)
BlowTheHorn(); // AVOID.
如果If子句有else子句,那么這兩個子句都應該使用括號椎麦。
// GOOD:
if (hasBaz) {
foo();
} else {
bar();
}
// AVOID:
if (hasBaz) foo();
else bar(); // AVOID.
if (hasBaz) {
foo();
} else bar(); // AVOID.
對下一個案例的故意影響應該用注釋記錄下來宰僧,除非在下一次案例之前沒有任何干預代碼
// GOOD:
switch (i) {
case 1:
...
break;
case 2:
j++;
// Falls through.
case 3: {
int k;
...
break;
}
case 4:
case 5:
case 6: break;
}
表達式
在二進制操作符和賦值上使用空格。為一元運算符省略空格观挎。不要在括號內添加空格
// GOOD:
x = 0;
v = w * x + y / z;
v = -y * (x + z);
表達式中的因子可能省略空格
// GOOD:
v = w*x + y/z;
方法調用
方法調用應該像方法聲明那樣格式化
當有選擇格式樣式時琴儿,遵循已經在給定源文件中使用的約定。調用應該在一行上有所有的參數
// GOOD:
[myObject doFooWith:arg1 name:arg2 error:arg3];
或者每一行有一個參數嘁捷,冒號對齊
// GOOD:
[myObject doFooWith:arg1
name:arg2
error:arg3];
不要使用這些格式:
// AVOID:
[myObject doFooWith:arg1 name:arg2 // some lines with >1 arg
error:arg3];
[myObject doFooWith:arg1
name:arg2 error:arg3];
[myObject doFooWith:arg1
name:arg2 // aligning keywords instead of colons
error:arg3];
與聲明和定義一樣造成,當第一個關鍵字比其他關鍵字短時,將后面的行縮進至少4個空格雄嚣,保持冒號對齊
// GOOD:
[myObj short:arg1
longKeyword:arg2
evenLongerKeyword:arg3
error:arg4];
包含多個代碼塊的調用可能使它們的參數名在四個空間縮進中左對齊
函數調用
函數調用應該包含盡可能多的參數晒屎,除了需要更短的行來清楚或說明參數。
函數參數的延續(xù)行可以縮進以與開括號對齊缓升,或者可能有一個4個空格的縮進鼓鲁。
// GOOD:
CFArrayRef array = CFArrayCreate(kCFAllocatorDefault, objects, numberOfObjects,
&kCFTypeArrayCallBacks);
NSString *string = NSLocalizedStringWithDefaultValue(@"FEET", @"DistanceTable",
resourceBundle, @"%@ feet", @"Distance for multiple feet");
UpdateTally(scores[x] * y + bases[x], // Score heuristic.
x, y, z);
TransformImage(image,
x1, x2, x3,
y1, y2, y3,
z1, z2, z3);
使用帶有描述性名稱的局部變量來縮短函數調用和減少調用的嵌套
// GOOD:
double scoreHeuristic = scores[x] * y + bases[x];
UpdateTally(scoreHeuristic, x, y, z);
異常
使用@catch和@finally標簽在同一行上的異常格式。在@和打開括號( { )之間添加空格港谊,以及@catch和捕獲對象聲明之間的空格骇吭。如果您必須使用OC異常,請按照以下方式格式化它們歧寺。但是燥狰,請注意避免拋出異常,原因是您不應該使用異常斜筐。
// GOOD:
@try {
foo();
} @catch (NSException *ex) {
bar(ex);
} @finally {
baz();
}
函數的長度
傾向于定義小而集中的功能龙致。
長函數和方法有時是適當的,因此沒有嚴格限制在函數長度上顷链。如果一個函數超過40行净当,考慮它是否可以在不損害程序結構的情況下被分解。
即使你的長函數現(xiàn)在運行得很好蕴潦,幾個月后有人修改它可能會增加新的行為。這可能導致難以找到的bug俘闯。使您的函數保持簡短和簡單潭苞,使其他人更容易閱讀和修改您的代碼。
在更新遺留代碼時真朗,考慮將長時間的函數分解成更小此疹、更易于管理的部分。
垂直空格
有節(jié)制的使用垂直空格。為了讓更多的代碼更容易在屏幕上顯示蝗碎,避免在函數的括號內放置空白行湖笨。將空行限制在函數和邏輯組之間的一個或兩個之間。
命名
在合理的范圍內蹦骑,名字應該盡可能的描述慈省。遵循標準 Objective-C naming rules.。避免非標準的縮寫眠菇。不要擔心節(jié)省空間边败,因為讓你的代碼立即被一個新讀者理解是非常重要的。例如:
// GOOD:
// Good names.
int numberOfErrors = 0;
int completedConnectionsCount = 0;
tickets = [[NSMutableArray alloc] init];
userInfo = [someObject object];
port = [network port];
NSDate *gAppLaunchDate;
// AVOID:
// Names to avoid.
int w;
int nerr;
int nCompConns;
tix = [[NSMutableArray alloc] init];
obj = [someObject object];
p = [network port];
任何類捎废、類別笑窜、方法、函數或變量名都應該在名稱中使用所有大寫字母和 縮寫詞 登疗。這遵循了蘋果的標準排截,在一個名稱中使用所有大寫字母,如URL辐益、ID断傲、TIFF和EXIF。
C函數和typedef的名稱應該大寫荷腊,并使用駝峰大小寫來處理周圍的代碼艳悔。
文件名
文件名應該反映它們所包含的類實現(xiàn)的名稱,包括案例女仰。遵循您的項目使用的約定猜年。文件擴展應該如下:
Extension | Type |
---|---|
.h | C/C++/Objective-C header file |
.m | Objective-C implementation file |
.mm | Objective-C++ implementation file |
.cc | Pure C++ implementation file |
.c | C implementation file |
包含跨項目或大型項目中共享的代碼的文件應該具有一個明確的惟一名稱,通常包括項目或類前綴疾忍。
類的文件名應該包括被擴展的類的名稱乔外,比如GTMNSString+Utils.h或NSTextView + GTMAutocomplete.h
類名
類名(以及類別和協(xié)議名稱)應該以大寫形式開始,并使用混合大小寫來限制單詞一罩。
在設計跨多個應用程序共享的代碼時杨幼,可以接受和推薦前綴(例如GTMSendMessage)。對于依賴于外部庫的大型應用程序的類聂渊,還建議使用前綴差购。
分類名
類別名應該從一個3個字符的前綴開始,將類別標識為項目的一部分汉嗽,或者用于一般用途欲逃。
類別名應該包含它所擴展的類的名稱。例如饼暑,如果我們想要在NSString上創(chuàng)建一個類別來進行解析稳析,我們將把這個類別放入一個名為NSString+GTM.h解析的文件中,類別本身會被命名為GTMNSStringParsingAdditions,文件名稱和類別可能不匹配洗做,因為這個文件可能有許多與解析相關的單獨類別。方法類別應該共享前綴(gtm_myCategoryMethodOnAString:)為了防止碰撞在Objective - C的全局名稱空間彰居。
在類名和類的開括號之間應該有一個空格
// GOOD:
// Using a category to extend a Foundation class.
@interface NSString (GTMNSStringParsingAdditions)
- (NSString *)gtm_parsedString;
@end
OC方法名
方法和參數名通常以小寫形式開始诚纸,然后使用混合大小寫。
適當的大寫應該受到尊重陈惰,包括名字的開頭畦徘。
// GOOD:
+ (NSURL *)URLWithString;
如果可能的話,方法名應該像一個句子一樣讀奴潘,這意味著您應該選擇使用方法名流的參數名旧烧。OC的方法名稱往往很長,但是這有一個好處画髓,那就是代碼塊幾乎可以像散文一樣讀懂掘剪,因此很多實現(xiàn)注釋都是不必要的。
使用介詞和連接詞奈虾,如“and”夺谁、“from”和“to”,在第二個和后面的參數名中肉微,只有在必要的時候才可以澄清方法的含義或行為
// GOOD:
- (void)addTarget:(id)target action:(SEL)action; // GOOD; no conjunction needed
- (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view; // GOOD; conjunction clarifies parameter
- (void)replaceCharactersInRange:(NSRange)aRange
withAttributedString:(NSAttributedString *)attributedString; // GOOD.
返回對象的方法應該有一個名稱匾鸥,開頭是一個名詞,標識返回的對象:
// GOOD:
- (Sandwich *)sandwich; // GOOD.
// AVOID:
- (Sandwich *)makeSandwich; // AVOID.
訪問器方法應該與得到的對象相同碉纳,但是不應該使用get來前綴勿负。例如
// GOOD:
- (id)delegate; // GOOD.
// AVOID:
- (id)getDelegate; // AVOID.
返回布爾形容詞值的訪問器有方法名,但這些方法的屬性名省略了is劳曹。
點表示法只用于屬性名奴愉,而不使用方法名
// GOOD:
@property(nonatomic, getter=isGlorious) BOOL glorious;
- (BOOL)isGlorious;
BOOL isGood = object.glorious; // GOOD.
BOOL isGood = [object isGlorious]; // GOOD.
// AVOID:
BOOL isGood = object.isGlorious; // AVOID.
// GOOD:
NSArray<Frog *> *frogs = [NSArray<Frog *> arrayWithObject:frog];
NSEnumerator *enumerator = [frogs reverseObjectEnumerator]; // GOOD.
// AVOID:
NSEnumerator *enumerator = frogs.reverseObjectEnumerator; // AVOID.
請參閱 Apple's Guide to Naming Methods,以獲得更多關于Objective-C命名的詳細信息铁孵。
這些指導原則僅適用于OC方法锭硼。C++方法名繼續(xù)遵循C++風格指南中設置的規(guī)則。
函數名
正則函數有混合的情況蜕劝。
通常檀头,函數應該以大寫字母開頭,并為每個新單詞都有大寫字母(也就是"Camel Case" ”或“Pascal案例”)
// GOOD:
static void AddTableEntry(NSString *tableEntry);
static BOOL DeleteFile(char *filename);
因為OC不提供命名空間岖沛,非靜態(tài)函數應該有一個前綴暑始,這樣可以減少名稱沖突的幾率。
// GOOD:
extern NSTimeZone *GTMGetDefaultTimeZone();
extern NSString *GTMGetURLScheme(NSURL *URL);
變量名
變量名通常以小寫字母開頭婴削,并使用混合大小寫來限制單詞蒋荚。
實例變量有突出的下劃線。文件范圍或全局變量有一個前綴g馆蠕,例如:mylocal變量期升、_myInstanceVariable、gmyglobal變量互躬。
常見的變量名
讀者應該能夠從名稱中推斷變量類型播赁,但是不要使用系統(tǒng)符號來表示語法屬性,例如變量的靜態(tài)類型(int或指針)吼渡。
在方法或函數范圍之外聲明的文件范圍或全局變量(而不是常量)應該很少見容为,并且應該具有前綴g。
// GOOD:
static int gGlobalCounter;
實例變量
實例變量名是混合用例寺酪,應該使用下劃線進行前綴坎背,比如_usernameTextField。
注意:谷歌之前對OC的慣例是一個拖尾下劃線〖娜福現(xiàn)有的項目可能會選擇在新代碼中繼續(xù)使用拖尾下劃線得滤,以保持項目代碼庫中的一致性。前綴或后綴的一致性應該在每個類中保持盒犹。
常量
常量符號(const的全局變量和靜態(tài)變量和用定義創(chuàng)建的常量)應該使用混合大小寫來限制單詞懂更。
常量應該有一個適當的前綴
// GOOD:
extern NSString *const GTLServiceErrorDomain;
typedef NS_ENUM(NSInteger, GTLServiceError) {
GTLServiceErrorQueryResultMissing = -3000,
GTLServiceErrorWaitTimedOut = -3001,
};
因為OC不提供命名空間,所以使用外部鏈接的常量應該有一個前綴急膀,它可以最小化名稱沖突的機會沮协,通常就像ClassNameConstantName或ClassNameEnumName一樣。
對于與Swift代碼的互操作性卓嫂,枚舉值應該具有擴展typedef名稱的名稱:
// GOOD:
typedef NS_ENUM(NSInteger, DisplayTinge) {
DisplayTingeGreen = 1,
DisplayTingeBlue = 2,
};
常量可以在適當的時候使用小寫的k前綴:
// GOOD:
static const int kFileCount = 12;
static NSString *const kUserKey = @"kUserKey";
類型和聲明
局部變量
在最窄的實際范圍內聲明變量慷暂,并接近它們的使用。在聲明中初始化變量晨雳。
// GOOD:
CLLocation *location = [self lastKnownLocation];
for (int meters = 1; meters < 10; meters++) {
reportFrogsWithinRadius(location, meters);
}
有時候行瑞,效率會使在其使用范圍之外聲明一個變量更加合適。這個示例聲明了不同的初始化悍募,并且每次通過循環(huán)都不必要地發(fā)送lastlocation消息:
// AVOID:
int meters; // AVOID.
for (meters = 1; meters < 10; meters++) {
CLLocation *location = [self lastKnownLocation]; // AVOID.
reportFrogsWithinRadius(location, meters);
}
在自動引用計數中蘑辑,指向OC對象的指針默認初始化為nil,因此不需要顯式地初始化到nil
Unsigned Integers
避免無符號整數坠宴,除非系統(tǒng)接口使用的類型匹配洋魂。
當使用無符號整數時,會出現(xiàn)一些微妙的錯誤喜鼓。除了在系統(tǒng)接口中匹配NSUInteger時副砍,只需要在數學表達式中使用已簽名的整數
// GOOD:
NSUInteger numberOfObjects = array.count;
for (NSInteger counter = numberOfObjects - 1; counter > 0; --counter)
// AVOID:
for (NSUInteger counter = numberOfObjects - 1; counter > 0; --counter) // AVOID.
無符號整數可用于標記和位掩碼,不過通常NS_OPTIONS或NS_ENUM會更合適
類型與大小不一致
由于在32位和64位構建中存在不同的大小庄岖,所以除了匹配系統(tǒng)接口之外豁翎,避免類型long、NSInteger隅忿、NSUInteger和CGFloat心剥。
類型long邦尊、NSInteger、NSUInteger和CGFloat在32-64位構建之間的大小有所不同优烧。在處理由系統(tǒng)接口公開的值時蝉揍,使用這些類型是合適的,但是在大多數其他計算中應該避免使用這些類型
// GOOD:
int32_t scalar1 = proto.intValue;
int64_t scalar2 = proto.longValue;
NSUInteger numberOfObjects = array.count;
CGFloat offset = view.bounds.origin.x;
// AVOID:
NSInteger scalar2 = proto.longValue; // AVOID.
文件和緩沖區(qū)大小通常超過32位限制畦娄,因此應該使用int64t聲明它們又沾,而不是使用long、NSInteger或NSUInteger熙卡。
注釋
注釋對于保持我們的代碼可讀性是至關重要的杖刷。下面的規(guī)則描述了您應該如何評論和在哪里。但是請記住:雖然注釋很重要驳癌,但最好的代碼是自文檔化滑燃。給類型和變量提供合理的名稱要比使用模糊的名稱好得多,然后試圖通過注釋來解釋它們喂柒。
注意標點不瓶、拼寫和語法;讀寫得好的評論比寫得不好的評論要容易得多。
注釋應該像敘述性文本一樣可讀灾杰,有適當的大小寫和標點符號蚊丐。在很多情況下,完整的句子比句子片段更容易讀懂艳吠。較短的注釋麦备,例如代碼行末尾的注釋,有時可能不那么正式昭娩,但是使用一致的樣式凛篙。當你寫你的評論時,為你的讀者寫:下一個需要理解你的代碼的貢獻者栏渺。下一個可能是你呛梆!
文檔注釋
文件可以從對其內容的描述開始。每個文件都可能包含以下內容:
- 許可樣板如果必要的話磕诊。為項目使用的許可選擇適當的樣板文件填物。
- 必要時對文件內容的基本描述。
如果您對帶有作者行的文件進行了重大更改霎终,請考慮刪除作者行滞磺,因為修訂歷史已經提供了更詳細、更準確的作者身份記錄莱褒。
聲明注釋
每一個非凡的界面击困,公共的和私有的祈争,都應該有一個附帶的評論來描述它的目的以及它是如何與更大的場景相適應的寨闹。
注釋應該用于文檔類、屬性破讨、ivars棍弄、函數滚婉、類別然低、協(xié)議聲明和枚舉英遭。
// GOOD:
/**
* A delegate for NSApplication to handle notifications about app
* launch and shutdown. Owned by the main app controller.
*/
@interface MyAppDelegate : NSObject {
/**
* The background task in progress, if any. This is initialized
* to the value UIBackgroundTaskInvalid.
*/
UIBackgroundTaskIdentifier _backgroundTaskID;
}
/** The factory that creates and manages fetchers for the app. */
@property(nonatomic) GTMSessionFetcherService *fetcherService;
@end
在Xcode解析這些接口以顯示格式化的文檔時,會鼓勵使用doxygen風格的注釋企蹭。有各種各樣的Doxygen命令;在項目中始終如一地使用它們。
如果您已經在文件頂部的注釋中詳細描述了一個接口智末,那么可以簡單地聲明“在文件的頂部看到注釋”谅摄,但是一定要有一些注釋。
另外系馆,每個方法都應該有一個注釋來解釋它的函數送漠、參數、返回值由蘑、線程或隊列假設以及任何副作用闽寡。文檔注釋應該放在公共方法的標題中,或者在方法之前尼酿,用于非平凡的私有方法爷狈。
使用描述性形式(“打開文件”)而不是命令形式(“打開文件”)來獲取方法和函數注釋。注釋描述了這個函數;它沒有告訴函數該做什么裳擎。
如果有的話涎永,記錄線程的使用假設,即類鹿响、屬性或方法羡微。如果一個類的實例可以被多個線程訪問,那么就需要格外注意來記錄多線程使用的規(guī)則和不變量惶我。
任何屬性和ivars的值妈倔,例如NULL或-1,都應該在注釋中記錄绸贡。
聲明注釋解釋了如何使用方法或函數盯蝴。注釋解釋方法或函數是如何實現(xiàn)的,應該與實現(xiàn)而不是聲明一起使用恃轩。
實現(xiàn)注釋
提供注釋解釋復雜的结洼、微妙的或復雜的代碼段
// GOOD:
// Set the property to nil before invoking the completion handler to
// avoid the risk of reentrancy leading to the callback being
// invoked again.
CompletionHandler handler = self.completionHandler;
self.completionHandler = nil;
handler();
當有用的時候,也提供關于被考慮或放棄的實現(xiàn)方法的注釋叉跛。
結束行注釋應該與代碼至少分隔兩個空格松忍。如果您對后續(xù)行有幾條注釋,則可以更容易地將它們排好
// GOOD:
[self doSomethingWithALongName]; // Two spaces before the comment.
[self doSomethingShort]; // More spacing to align the comment.
有歧義的符號
在需要避免歧義的地方筷厘,使用反引號或豎條在注釋中引用變量名和符號鸣峭,以便使用引號或將符號命名為內聯(lián)宏所。
在Doxygen- Style的注釋中,更喜歡使用單空間文本命令來分隔符號摊溶,比如@c爬骤。
當一個符號是一個常見的詞,可能使這個句子讀起來像它的結構很糟糕時莫换,劃分有助于提供清晰的信息霞玄。一個常見的例子是符號計數:
// GOOD:
// Sometimescount
will be less than zero.
或者引用已經包含引號的內容
// GOOD:
// Remember to callStringWithoutSpaces("foo bar baz")
當一個符號是自顯的時候,不需要反勾或豎條
// GOOD:
// This class serves as a delegate to GTMDepthCharge.
Doxygen格式也適用于標識符號拉岁。
// GOOD:
/** @param maximum The highest value for @c count. */
目標擁有者
對于沒有使用ARC管理的對象坷剧,當它超出了最常見的OC-用法慣例時,使指針明確的持有所有權模型喊暖。
手動引用計數
NSObject派生對象的實例變量被假定為保留;如果它們沒有被保留惫企,它們應該被認為是弱的陵叽,或者是用__ weak限定符聲明的偏序。
在Mac軟件中刊殉,一個例外是被標記為@IBOutlet的變量,這些變量被認為是不被保留的碗硬。
實例變量是指向Core Foundation挽懦、C++和其他非Objective-C對象的指針醒第,它們應該總是用強而弱的注釋聲明客年,以指示哪些指針是不被保留的。Core Foundation和其他非Objective-C對象指針需要顯式的內存管理欺劳,即使在構建自動引用計數時也是鹏往。
強而弱的聲明的例子:
// GOOD:
@interface MyDelegate : NSObject
@property(nonatomic) NSString *doohickey;
@property(nonatomic, weak) NSString *parent;
@end
@implementation MyDelegate {
IBOutlet NSButton *_okButton; // Normal NSControl; implicitly weak on Mac only
AnObjcObject *_doohickey; // My doohickey
__weak MyObjcParent *_parent; // To send messages back (owns this instance)
// non-NSObject pointers...
CWackyCPPClass *_wacky; // Strong, some cross-platform object
CFDictionaryRef *_dict; // Strong
}
@end
自動引用計數
當使用ARC時,對象的所有權和生命周期是明確的,因此自動保留的對象不需要額外的注釋。
C語言特性
宏命令
避免使用宏,特別是在const變量、枚舉、XCode代碼片段或C函數的情況下光酣。
宏使您看到的代碼與編譯器看到的代碼有所不同〕猓現(xiàn)代C將傳統(tǒng)的宏用于常量和不必要的實用函數袖瞻。只有當沒有其他可用的解決方案時霉晕,才應該使用宏伟葫。
在需要宏的地方撼玄,使用一個惟一的名稱來避免編譯單元中符號沖突的風險废膘。如果實際操作丐黄,則在使用后不定義宏的范圍限制范圍斋配。
宏名應該使用大寫字母來和下劃線表示。類似函數的宏可以使用C函數命名方法灌闺。不要定義看起來是C或objective-C關鍵字的宏
// GOOD:
#define GTM_EXPERIMENTAL_BUILD ... // GOOD
// Assert unless X > Y
#define GTM_ASSERT_GT(X, Y) ... // GOOD, macro style.
// Assert unless X > Y
#define GTMAssertGreaterThan(X, Y) ... // GOOD, function style.
// AVOID:
#define kIsExperimentalBuild ... // AVOID
#define unless(X) if(!(X)) // AVOID
避免那些擴展到不平衡C或objective-C結構的宏艰争。避免引入范圍的宏,或者可能模糊塊中值的捕獲桂对。
避免在header中生成類甩卓、屬性或方法定義的宏,以作為公共API使用蕉斜。這些代碼只會讓代碼難以理解逾柿,而且語言已經有了更好的方法來實現(xiàn)這一點。
避免產生方法實現(xiàn)的宏宅此,或者生成稍后在宏之外使用的變量的聲明鹿寻。宏不應該通過隱藏變量的位置和方式來讓代碼難以理解。
// AVOID:
#define ARRAY_ADDER(CLASS) \
-(void)add ## CLASS ## :(CLASS *)obj toArray:(NSMutableArray *)array
ARRAY_ADDER(NSString) {
if (array.count > 5) { // AVOID -- where is 'array' defined?
...
}
}
可以接受的宏使用的例子包括斷言和調試日志宏诽凌,這些宏是根據構建設置有條件地編譯的,這些宏通常都沒有編譯成版本坦敌。
非標準擴展
除非另有說明侣诵,否則不能使用C/OC的非標準擴展。
編譯器支持不屬于標準c語言的各種擴展狱窘,包括復合語句表達式(例如foo = ({ int x; Bar(&x); x }))
)和變長數組杜顺。
__attribute__
是一個被批準的異常,因為它在OC API規(guī)范中被使用蘸炸。
條件運算符的二進制形式躬络,A ?: B
,是一個批準的例外搭儒。
Cocoa and Objective-C特征
確定指定初始值
清楚地標識您的指定初始化器穷当。
對于那些可能會子類化你的類的人來說,很重要的一點是淹禾,指定的初始化器被清楚地識別出來馁菜。這樣,它們只需要覆蓋一個單一的初始化器(可能是幾個)铃岔,以保證其子類的初始化器被調用汪疮。它還可以幫助那些在將來調試類的人理解初始化代碼的流程,如果他們需要的話。使用注釋或NS_DESIGNATED_INITIALIZER
初始化器宏來識別指定的初始化器智嚷。如果你使用NS_DESIGNATED_INITIALIZER
初始化器卖丸,用NS_UNAVAILABLE
標記不支持的初始化器。
重寫指定初始值
當編寫一個需要init的子類時盏道。方法稍浆,確保覆蓋超類的指定初始化器。
如果您不能覆蓋超類的指定初始化器摇天,那么您的初始化器可能不會在所有的情況下被調用粹湃,這會導致難以找到bug的微妙和困難。
重寫NSObject方法
提供一個NSObject的重寫方法在@implementation前
這通常適用于(但不限于)init
,copyWithZone:
和dealloc
方法泉坐。init
方法應該組合在一起为鳄,然后是其他典型的NSObject方法,比如description
腕让、isEqual:
和hash
孤钦。
便利類工廠方法可能在NSObject方法之前。
初始化
不要在init方法中初始化實例變量為0或nil;這樣做是多余的纯丸。
新分配的對象的所有實例變量都被初始化為0(除了isa)偏形,所以不要通過重新初始化變量為0或nil來搞亂init方法。
header中的實例變量應該是@protected或@private
實例變量通常應該在實現(xiàn)文件中聲明觉鼻,或者由屬性自動合成俊扭。當在頭文件中聲明ivars時,應該將其標記為@protected或@private坠陈。
// GOOD:
@interface MyClass : NSObject {
@protected
id _myInstanceVariable;
}
@end
Avoid +new
不要調用NSObject類方法萨惑,也不要在子類中重寫它。相反仇矾,使用alloc和init方法來實例化保留對象庸蔼。
現(xiàn)在OC代碼顯式地調用alloc和init方法來創(chuàng)建和保留對象。由于新的類方法很少被使用贮匕,所以它使得對正確的內存管理的代碼檢查變得更加困難姐仅。
保持公共API的簡單性
保持簡單的類;避免“kitchen-sink”API。如果一個方法不需要公開刻盐,就把它放在公共接口之外掏膏。
與C++不同,OC不區(qū)分公共和私有方法;任何消息都可以發(fā)送到一個對象敦锌。因此壤追,避免將方法放置在公共API中,除非它們實際被用于類的使用者使用供屉。這有助于減少當你不期望時被調用的可能性行冰。這包括從父類中覆蓋的方法溺蕉。
由于內部方法不是真正的私有的,所以很容易意外地覆蓋超類的“私有”方法悼做,從而使一個非常困難的bug發(fā)現(xiàn)疯特。一般來說,私有方法應該有一個相當獨特的名稱肛走,它可以防止子類無意地覆蓋它們漓雅。
#import and #include
import OC和Objective-C++頭文件,并#include c/c++頭文件朽色。
在#import和#include之間進行選擇邻吞,這是基于您所包含的消息頭的語言。
當包含使用Objective-C++或OC的頭時葫男,使用#import抱冷。當包含標準C或C++頭時,請使用#include梢褐。標題應該提供自己的定義保護旺遮。
包括順序
頭包的標準順序是相關的頭、操作系統(tǒng)頭盈咳、語言庫頭耿眉,以及其他依賴項的頭組。
相關的頭在其他的前面鱼响,以確保它沒有隱藏的依賴項鸣剪。對于實現(xiàn)文件,相關的頭文件是頭文件丈积。對于測試文件筐骇,相關的頭是包含測試接口的頭。
空白行可以分隔包含標題的邏輯上不同的組桶癣。
// GOOD:
#import "ProjectX/BazViewController.h"
#import <Foundation/Foundation.h>
#include <unistd.h>
#include <vector>
#include "base/basictypes.h"
#include "base/integral_types.h"
#include "util/math/mathutil.h"
#import "ProjectX/BazModel.h"
#import "Shared/Util/Foo.h"
為系統(tǒng)框架使用傘頭
為系統(tǒng)框架和系統(tǒng)庫導入傘頭文件,而不是包含單獨的文件娘锁。
雖然從諸如Cocoa或Foundation之類的框架中包含單獨的系統(tǒng)頭可能看起來很誘人牙寞,但實際上,如果您包含頂級根框架莫秆,那么它在編譯器上的工作就會更少间雀。根框架通常是預編譯的,并且可以更快地加載镊屎。此外惹挟,請記住使用@import或import而不是OC frameworks。
// GOOD:
@import UIKit; // GOOD.
#import <Foundation/Foundation.h> // GOOD.
// AVOID:
#import <Foundation/NSArray.h> // AVOID.
#import <Foundation/NSString.h>
...
在init和dealloc中避免訪問器
在init和dealloc方法執(zhí)行過程中缝驳,實例的子類可能處于不一致的狀態(tài)连锯,所以這些方法中的代碼應該避免調用self的訪問方法归苍。
當init和dealloc方法執(zhí)行時,子類還沒有被初始化或者已經進行了處理运怖,使得訪問器方法可能不可靠拼弃。在實際操作中,在這些方法中直接分配和釋放ivars摇展,而不是依賴訪問器吻氧。
// GOOD:
- (instancetype)init {
self = [super init];
if (self) {
_bar = 23; // GOOD.
}
return self;
}
// AVOID:
- (instancetype)init {
self = [super init];
if (self) {
self.bar = 23; // AVOID.
}
return self;
}
// GOOD:
- (void)dealloc {
[_notifier removeObserver:self]; // GOOD.
}
// AVOID:
- (void)dealloc {
[self removeNotifications]; // AVOID.
}
setter copy NSString
使用NSString的setter應該總是復制它所接受的字符串。這通常也適用于NSArray和NSDictionary之類的集合咏连。
不要僅僅保留字符串盯孙,因為它可能是一個NSMutableString。這就避免了調用者在您不知情的情況下更改它祟滴。
接收和保存集合對象的代碼也應該考慮到傳遞的集合可能是可變的振惰,因此集合可以更安全地作為原始副本的副本或可變副本被保存。
// GOOD:
@property(nonatomic, copy) NSString *name;
- (void)setZigfoos:(NSArray<Zigfoo *> *)zigfoos {
// Ensure that we're holding an immutable collection.
_zigfoos = [zigfoos copy];
}
使用輕量級泛型來記錄包含的類型
所有在Xcode 7或更新版本上編譯的項目都應該使用OC輕量級泛型表示法來輸入包含的對象踱启。
每個NSArray报账、NSDictionary或NSSet引用都應該使用輕量級泛型進行聲明,以改進類型安全性埠偿,并顯式地記錄使用情況透罢。
// GOOD:
@property(nonatomic, copy) NSArray<Location *> *locations;
@property(nonatomic, copy, readonly) NSSet<NSString *> *identifiers;
NSMutableArray<MyLocation *> *mutableLocations = [otherObject.locations mutableCopy];
如果完全注釋的類型變得復雜,請考慮使用typedef來保持可讀性
// GOOD:
typedef NSSet<NSDictionary<NSString *, NSDate *> *> TimeZoneMappingSet;
TimeZoneMappingSet *timeZoneMappings = [TimeZoneMappingSet setWithObjects:...];
使用最具描述性的公共超類或協(xié)議冠蒋。在最常見的情況下羽圃,當沒有其他已知的情況時,將該集合聲明為使用id顯式的異構性
// GOOD:
@property(nonatomic, copy) NSArray<id> *unknowns;
避免拋出異常
不要@throw
OC異常抖剿,但是您應該準備好從第三方或OS調用中捕獲它們朽寞。
在此之前,在Apple's Introduction to Exception Programming Topics for Cocoa.中斩郎,建議使用錯誤對象來進行錯誤交付脑融。
我們使用-fobjc-exception
來編譯(主要是我們得到@synchronized
),但是我們沒有@throw
缩宜。當需要使用第三方代碼或庫時肘迎,可以使用@try
、@catch
和@finally
锻煌。如果您確實使用了它們妓布,請準確地記錄您希望拋出的方法。
nil Checks
只對邏輯流使用nil
檢查宋梧。
使用nil
指針檢查應用程序的邏輯流匣沼,而不是在發(fā)送消息時防止崩潰。將消息發(fā)送給nil
reliably returns nil捂龄,作為一個整數或浮點值释涛,0作為一個值加叁,它被初始化為0
,而_Complex
的值等于{0,0}
枢贿。
注意殉农,這適用于nil
作為消息目標,而不是作為參數值局荚。個別方法可能或不能安全地處理nil參數值超凳。
請注意,這與檢查C/C++指針和針對NULL
的塊指針是不同的耀态,這是運行時不處理的轮傍,會導致應用程序崩潰。您仍然需要確保您不會取消一個空指針首装。
BOOL陷阱
將一般值轉換為BOOL
時要小心创夜。避免直接與YES
進行比較。
在OS X和32位的iOS版本中仙逻,BOOL
定義為一個已簽名的字符驰吓,因此它可能具有除YES(1)
和NO(0)
之外的值,不要將一般值直接轉換為BOOL
系奉。
常見的錯誤包括轉換或轉換數組的大小檬贰、指針值或對BOOL
的逐位邏輯操作的結果,這取決于整數值的最后一個字節(jié)的值缺亮,仍然會導致沒有值翁涤。當將一個一般值轉換為BOOL
時,使用三元運算符返回一個YES
或NO
值萌踱。
您可以安全地交換和轉換BOOL
葵礼、_Bool
和bool
;
(參見C++Std 4.7.4、4.12和C99 Std 6.3.1.2)并鸵。在Objective-C方法簽名中使用BOOL鸳粉。
使用BOOL使用邏輯運算符 (&&
, ||
and!
)也是有效的,并且將返回可以安全地轉換為BOOL的值园担,而不需要使用三元運算符届谈。
// AVOID:
- (BOOL)isBold {
return [self fontTraits] & NSFontBoldTrait; // AVOID.
}
- (BOOL)isValid {
return [self stringValue]; // AVOID.
}
// GOOD:
- (BOOL)isBold {
return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}
- (BOOL)isValid {
return [self stringValue] != nil;
}
- (BOOL)isEnabled {
return [self isValid] && [self isBold];
}
另外,不要直接將BOOL
變量直接與YES
進行比較粉铐。對于那些精通C語言的人來說疼约,閱讀不僅困難卤档,而且上面的第一點表明蝙泼,返回值可能并不總是您所期望的
// AVOID:
BOOL great = [foo isGreat];
if (great == YES) { // AVOID.
// ...be great!
}
// GOOD:
BOOL great = [foo isGreat];
if (great) { // GOOD.
// ...be great!
}
沒有實例變量的接口
忽略不聲明任何實例變量的接口上的空括號
// GOOD:
@interface MyClass : NSObject
// Does a lot of stuff.
- (void)fooBarBam;
@end
// AVOID:
@interface MyClass : NSObject {
}
// Does a lot of stuff.
- (void)fooBarBam;
@end
Cocoa 模式
代理模式
在這樣做的時候,委托劝枣、目標對象和塊指針不應該被保留汤踏,這樣就可以創(chuàng)建一個保留循環(huán)织鲸。
為了避免引起一個保留循環(huán),當一個委托或目標指針被清除時溪胶,就應該立即釋放搂擦,這樣就不再需要對對象進行消息傳遞了。
如果沒有明確的時間哗脖,委托或目標指針不再需要瀑踢,指針只應該被保留。
塊指針不能被保留才避。為了避免在客戶機代碼中造成保留周期橱夭,塊指針應該被用于回調,只有在它們被調用或者不再需要它們之后才可以顯式地釋放它們桑逝。否則棘劣,回調應該通過弱委托或目標指針來完成
Objective-C++
語言匹配風格
風格匹配語言
在一個Objective-C++源文件中,遵循您正在實現(xiàn)的函數或方法的語言的風格楞遏。為了減少在混合使用Cocoa/Objective-C++和C++時不同命名風格之間的沖突茬暇,請遵循所實現(xiàn)的方法的風格。
對于@實現(xiàn)塊中的代碼寡喝,使用OC命名規(guī)則糙俗。對于C++類方法中的代碼,使用C++命名規(guī)則拘荡。
// GOOD:
// file: cross_platform_header.h
class CrossPlatformAPI {
public:
...
int DoSomethingPlatformSpecific(); // impl on each platform
private:
int an_instance_var_;
};
// file: mac_implementation.mm
#include "cross_platform_header.h"
// A typical Objective-C class, using Objective-C naming.
@interface MyDelegate : NSObject {
@private
int _instanceVar;
CrossPlatformAPI* _backEndObject;
}
- (void)respondToSomething:(id)something;
@end
@implementation MyDelegate
- (void)respondToSomething:(id)something {
// bridge from Cocoa through our C++ backend
_instanceVar = _backEndObject->DoSomethingPlatformSpecific();
NSString* tempString = [NSString stringWithFormat:@"%d", _instanceVar];
NSLog(@"%@", tempString);
}
@end
// The platform-specific implementation of the C++ class, using
// C++ naming.
int CrossPlatformAPI::DoSomethingPlatformSpecific() {
NSString* temp_string = [NSString stringWithFormat:@"%d", an_instance_var_];
NSLog(@"%@", temp_string);
return [temp_string intValue];
}
項目可能選擇使用80列的行長度限制來與Google的C++風格指南保持一致
OC風格的異常
顯示風格異常
沒有期望遵循這些樣式推薦的代碼行臼节,在行末尾的行末尾或// NOLINTNEXTLINE
的后面,需要// NOLINT
珊皿。有時网缝,OC代碼部分必須忽略這些樣式的建議(例如,代碼可能是機器生成的蟋定,或者代碼結構是不可能正確的風格)粉臊。
在那一行上的// NOLINT
或前一行的// NOLINTNEXTLINE
評論可以用來指示讀者,代碼是故意忽略樣式的指導方針的驶兜。此外扼仲,這些注釋還可以通過諸如linters和正確處理代碼之類的自動化工具來獲得。注意抄淑,在//
和NOLINT
之間有一個空格屠凶。