你會編寫高質(zhì)量的Objective-C代碼?
點標(biāo)記語法
屬性和冪等方法(多次調(diào)用和一次調(diào)用返回的結(jié)果相同)使用點標(biāo)記語法訪問,其他的情況使用方括號標(biāo)記語法。
良好的風(fēng)格:
不良的風(fēng)格:
- view.backgroundColor = [UIColor orangeColor];
- [UIApplication sharedApplication].delegate;
- [view setBackgroundColor:[UIColor orangeColor]];
- UIApplication.sharedApplication.delegate;
間距
二元運算符和參數(shù)之間需要放置一個空格,一元運算符、強(qiáng)制類型轉(zhuǎn)換和參數(shù)之間不放置空格。關(guān)鍵字之后圓括號之前需要放置一個空格。
- void *ptr = &value + 10 * 3;
- NewType a = (NewType)b;
- for (int i = 0; i <</span> 10; i++) {
- doCoolThings();
- }
數(shù)組和字典類型的字面值的方括號兩邊各放置一個空格。
NSArray *theShit = @[ @1, @2, @3 ];
字典字面值的鍵和冒號之間沒有空格,冒號和值之間有一個空格。
NSDictionary *keyedShit = @{ GHDidCreateStyleGuide: @YES };
C函數(shù)聲明中,左括號的前面不保留空格,并且函數(shù)名應(yīng)該像類一樣帶有命名空間標(biāo)識。
良好的風(fēng)格: void RNCwesomeFunction(BOOL hasSomeArgs);
長的字面值應(yīng)被拆分為多行。
良好的風(fēng)格:
- NSArray *theShit = @[
- @"Got some long string objects in here.",
- [AndSomeModelObjects too],
- @"Moar strings."
- ];
- NSDictionary *keyedShit = @{
- @"this.key": @"corresponds to this value",
- @"otherKey": @"remoteData.payload",
- @"some": @"more",
- @"JSON": @"keys",
- @"and": @"stuff",
- };
每一行代碼使用4個空格縮進(jìn)。不使用tab縮進(jìn)。
方法簽名以及其他關(guān)鍵字(if/else/switch/while等)后面跟隨的左花括號總是和語句出現(xiàn)于同一行,而右花括號獨占一行。
良好的風(fēng)格:
- if (user.isHappy) {
- //Do something
- }
- else {
- //Do something else
- }
如果一個方法內(nèi)有多個功能區(qū)域,可以使用空行分隔功能區(qū)域。
每一行代碼不要超過100個字符。
每一個方法之前都有一個99字符寬的注釋行,注釋行相對于使用空行更能提高代碼的辨識度,當(dāng)一行代碼很長的時候,注釋行也起到了越界檢測的作用。注釋行:
///////////////////////////////////////////////////////////////////////////////////////////////////
條件語句
所有的邏輯塊必須使用花括號包圍,即使條件體只需編寫一行代碼也必須使用花括號。
良好的風(fēng)格做法:
- if (!error) {
- return success;
- }
不良的風(fēng)格:
if (!error)
return success;
或:
if (!error) return success;
三元運算符
長的三元運算符應(yīng)使用圓括號括起來。三元運算符僅用于賦值和做參數(shù)。
- Blah *a = (stuff == thing ? foo : bar);
合并的nil三元運算符應(yīng)該盡量避免。
不良的風(fēng)格:
- Blah *b = thingThatCouldBeNil ?: defaultValue;
多分支條件應(yīng)該使用if語句或重構(gòu)為實例變量。
良好的風(fēng)格:
- result = a > b ? x : y;
不良的風(fēng)格:
- result = a > b ? x = c > d ? c : d : y;
異常和錯誤處理
不要在流控制語句中使用異常(NSException)。
異常僅用于表明程序員的錯誤。
為了表明一個錯誤,使用NSError *。
當(dāng)一個方法通過引用返回一個錯誤參數(shù),應(yīng)該檢測返回值的狀態(tài),而不是錯誤參數(shù)的狀態(tài)。
良好的風(fēng)格:
- NSError *error;
- if (![self trySomethingWithError:&error]) {
- // Handle Error
- }
不良的風(fēng)格:
- NSError *error;
- [self trySomethingWithError:&error];
- if (error) {
- // Handle Error
- }
在方法執(zhí)行成功的情況下賦值非Null值給錯誤參數(shù),會使路徑跳轉(zhuǎn)到假條件分支(隨后程序奔潰)。
代理
除了繼承一個類或?qū)崿F(xiàn)一個協(xié)議,否則在頭文件中僅使用類聲明@class指令,不用#import導(dǎo)入類頭文件。
如果一個delegate只有幾個方法,比如只是提交和取消,推薦使用block編寫動作響應(yīng)代碼。
由于代理方法的聲明一般都很長,所以必須將代理對象和其他的協(xié)議對象放在實例變量定義的下面,否則實例變量定義的對齊方式將會被打亂掉。
當(dāng)需要實現(xiàn)多個協(xié)議的時候,將每一個協(xié)議名拆分到單獨的行。
良好的風(fēng)格:
- @interface CustomModelViewController : TTViewController <</span>
- TTModelDelegate,
- TTURLRequestDelegate
- > {
方法
一個方法的命名首先描述返回什么,接著是什么情況下被返回。方法簽名中冒號的前面描述傳入?yún)?shù)的類型。以下類方法和實例方法命名的格式語法:
- [object/class thing+condition];
- [object/class thing+input:input];
- [object/class thing+identifer:input];
Cocoa命名舉例:
- realPath = [path stringByExpandingTildeInPath];
- fullString = [string stringByAppendingString:@"Extra Text"];
- object = [array objectAtIndex:3];
- // 類方法
- newString = [NSString stringWithFormat:@"%f",1.5];
- newArray = [NSArray arrayWithObject:newString];
良好的自定義方法命名風(fēng)格:
- recipients = [email recipientsSortedByLastName];
- newEmail = [CDCEmail emailWithSubjectLine:@"Extra Text"];
- emails = [mailbox messagesReceivedAfterDate:yesterdayDate];
當(dāng)需要獲取對象值的另一種類型的時候,方法命名的格式語法如下:
- [object adjective+thing];
- [object adjective+thing+condition];
- [object adjective+thing+input:input];
良好的自定義方法命名風(fēng)格:
- capitalized = [name capitalizedString];
- rate = [number floatValue];
- newString = [string decomposedStringWithCanonicalMapping];
- subarray = [array subarrayWithRange:segment];
方法簽名盡量做到含義明確。
不良的風(fēng)格:
- -sortInfo // 是返回排序結(jié)果還是給info做排序
- -refreshTimer // 返回一個用于刷新的定時器還是刷新定時器
- -update // 更新什么,如何更新
良好的風(fēng)格:
- -currentSortInfo // "current" 清楚地修飾了名詞SortInfo
- -refreshDefaultTimer // refresh是一個動詞。
- -updateMenuItemTitle // 一個正在發(fā)生的動作
方法類型修飾符+/-后要放置一個空格,各參數(shù)名之間也要放置一個空格。
良好的風(fēng)格:
- - (void)setExampleText:(NSString *)text image:(UIImage *)image;
如果方法的命名特別長,將方法名拆分成多行。
良好的風(fēng)格:
- color = [NSColor colorWithCalibratedHue: 0.10
- saturation: 0.82
- brightness: 0.89
- alpha: 1.00];
不要將私有的實例變量和方法聲明在頭文件中,應(yīng)將私有變量和方法聲明在實現(xiàn)文件的類擴(kuò)展內(nèi)。
不良的風(fēng)格:
- //MyViewController.h文件
- @interface MyViewController : UIViewController<</span>
- UITalbeViewDataSource,
- UITableViewDelegate> {
- @private:
- UITableView *_myTableView; // 私有實例變量
- }
- // 內(nèi)部使用的屬性
- @property (nonatomic,strong) NSNumber *variableUsedInternally;
- - (void)sortName; // 只用于內(nèi)部使用的方法
- @end
- 良好的風(fēng)格:
- //MyViewController.m文件使用類擴(kuò)展
- @interface MyViewController()<</span>
- UITalbeViewDataSource,
- UITableViewDelegate> {
- UITableView *_myTableView;
- // 外部需要訪問的實例變量聲明為屬性,不需要外部訪問的聲明為實例變量
- NSNumber * variableUsedInternally;
- }
- // 從Xcode4.3開始,可以不寫方法的前置聲明,Interface Builder和Storyboard仍然可以找到方法的定義
- @end
構(gòu)造函數(shù)通常應(yīng)該返回實例類型而不是id類型
參數(shù)
方法參數(shù)名前一般使用的前綴包括“the”、“an”、“new”。
良好的風(fēng)格:
- - (void) setTitle: (NSString *) aTitle;
- - (void) setName: (NSString *) newName;
- - (id) keyForOption: (CDCOption *) anOption
- - (NSArray *) emailsForMailbox: (CDCMailbox *) theMailbox;
- - (CDCEmail *) emailForRecipients: (NSArray *) theRecipients;
變量
變量的命令應(yīng)盡量做到自描述。除了在for()循環(huán)語句中,單字母的變量應(yīng)該避免使用(如i,j,k等)。一般循環(huán)語句的當(dāng)前對象的命名前綴包括“one”、“a/an”。對于簡單的單個對象使用“item”命名。
良好的風(fēng)格:
- for (i = 0; i < count; i++) {
- oneObject = [allObjects objectAtIndex: i];
- NSLog (@"oneObject: %@", oneObject);
- }
- NSEnumerator *e = [allObjects objectEnumerator];
- id item;
- while (item = [e nextObject])
- NSLog (@"item: %@", item);
指針變量的星號指示符應(yīng)該緊靠變量,比如NSString *text,而不是NSString* text或NSString * text。
盡量的使用屬性而非實例變量。除了在初始化方法(init,initWithCoder:等)、dealloc方法以及自定義setter與getter方法中訪問屬性合成的實例變量,其他的情況使用屬性進(jìn)行訪問。
良好的風(fēng)格:
- @interface RNCSection: NSObject
- @property (nonatomic) NSString *headline;
- @end
不良的風(fēng)格:
- @interface RNCSection : NSObject {
- NSString *headline;
- }
當(dāng)你使用@synthesize指令時,編譯器會自動為你創(chuàng)建一個下劃線_開頭的的實例變量,所以不需要同時聲明實例變量和屬性。
不良的風(fēng)格:
- @interface RNCSection : NSObject {
- NSString *headline;
- }
- @property (nonatomic) NSString *headline;
- @end
良好的風(fēng)格:
- @interface RNCSection: NSObject
- @property (nonatomic) NSString *headline;
- @end
不要使用@synthesize除非是編譯器需要。注意在@protoco協(xié)議中的@optional可選屬性必須被顯式地使用@synthesize指令合成屬性。
縮略詞
雖然方法命名不應(yīng)使用縮略詞,然而有些縮略詞在過去被反復(fù)的使用,所以使用這些縮略詞能更好的的表達(dá)代碼的含義。下表列出了Cocoa可接受的縮略詞。
........................................................
以下是一些常用的首字母縮略詞
ASCII PDF XML HTML URL RTF HTTP TIFF JPG PNG GIF LZW ROM RGB CMYK MIDI FTP
命名
方法和變量的命令應(yīng)該盡可能做到自描述。
良好的風(fēng)格:
- UIButton *settingsButton;
不良的風(fēng)格:
- UIButton *setBut;
對于NSString、NSArray、NSNumber或BOOL類型,變量的命名一般不需要表明其類型。
良好的風(fēng)格:
- NSString *accountName;
- NSMutableArray *mailboxes;
- NSArray *defaultHeaders;
- BOOL userInputWasUpdated;
不良的風(fēng)格:
- NSString *accountNameString;
- NSMutableArray *mailboxArray;
- NSArray *defaultHeadersArray;
- BOOL userInputWasUpdatedBOOL;
如果變量不是以上基本常用類型,則變量的命名就應(yīng)該反映出自身的類型。但有時僅需要某些類的一個實例的情況下,那么只需要基于類名進(jìn)行命名。
- NSImage *previewPaneImage;
- NSProgressIndicator *uploadIndicator;
- NSFontManager *fontManager; // 基于類名命名
大部分情況下,NSArray或NSSet類型的變量只需要使用單詞復(fù)數(shù)形式(比如mailboxes),不必在命名中包含“mutable”。如果復(fù)數(shù)變量不是NSArray或NSSet類型,則需要指定其類型。
良好的風(fēng)格:
- NSDictionary * keyedAccountNames;
- NSDictionary * messageDictionary;
- NSIndexSet * selectedMailboxesIndexSet;
由于Objective-C不支持名字空間,為了防止出現(xiàn)命名空間的沖突,在類名和常類型變量名前添加一個由三個大寫的字母組成的前綴(如RNC),對于Core Data實體名則可以忽略此規(guī)則。如果你子類化了標(biāo)準(zhǔn)的Cocoa類,將前綴和父類名合并是一個很好的做法。如繼承UITableView的類可命名為RNCTableView。
常類型變量名的書寫風(fēng)格采用駝峰式大小寫(第一個單詞的首字母小寫,其余單詞的第一個字母大寫。如firstName而不是first_name或firstname。),并使用關(guān)聯(lián)的類名作為其命名前綴,
推薦的做法:
- static const NSTimeInterval RNCArticleViewControllerNavigationFadeAnimationDuration = 0.3;
不推薦的做法:
- static const NSTimeInterval fadetime = 1.7;
下劃線
使用屬性的時候,實例變量應(yīng)該使用self.進(jìn)行訪問和設(shè)值。局部變量的命令不要包含下劃線。實例變量的命名必須使用下劃線_作為前綴,這樣可以縮小Xcode自動完成的選項取值范圍。
注釋
在需要的時候,注釋可對代碼做必要的解釋。更新代碼時一定要更新注釋,防止對代碼造成誤解。
使用javadoc風(fēng)格的文檔注釋語法。注釋的第一行是對注釋API的總結(jié),隨后的注釋行是對代碼更多細(xì)節(jié)的解釋。
良好的風(fēng)格:
- @property (nonatomic) NSUInteger maxContentLength;
init與dealloc
dealloc方法應(yīng)該被放置在實現(xiàn)方法的頂部,直接在@synthesize或@dynamic語句之后。init方法應(yīng)該被放置在dealloc方法的下面。
init方法的結(jié)構(gòu)看上去應(yīng)該像這樣:
- - (instancetype)init {
- self = [super init]; // or call the designated initalizer
- if (self) {
- // Custom initialization
- }
- return self;
- }
字面值
對于NSString,NSDictionary,NSArray和NSNumber類,當(dāng)需要創(chuàng)建這些類的不可變實例時,應(yīng)該使用這些類的字面值表示形式。使用字面值表示的時候nil不需要傳入NSArray和NSDictionary中作為字面值。這種語法兼容老的iOS版本,因此可以在iOS5或者更老的版本中使用它。
良好的風(fēng)格:
- NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
- NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
- NSNumber *shouldUseLiterals = @YES;
- NSNumber *buildingZIPCode = @10018;
不良的風(fēng)格:
- 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 *buildingZIPCode = [NSNumber numberWithInteger:10018];
如非必要,避免使用特定類型的數(shù)字(相較于使用5.3f,應(yīng)使用5.3)。
CGRect函數(shù)
相較于使用結(jié)構(gòu)體輔助函數(shù)(如CGRectMake()函數(shù)),優(yōu)先使用C99結(jié)構(gòu)體初始化語法。
CGRect rect = {.origin.x = 3.0, .origin.y = 12.0, .size.width = 15.0, .size.height = 80.0 };
當(dāng)訪問CGRect結(jié)構(gòu)體的x、y、width、height成員時,應(yīng)使用CGGeometry函數(shù),不直接訪問結(jié)構(gòu)體成員。蘋果對CGGeometry函數(shù)的介紹:
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.
良好的風(fēng)格:
- CGRect frame = self.view.frame;
- CGFloat x = CGRectGetMinX(frame);
- CGFloat y = CGRectGetMinY(frame);
- CGFloat width = CGRectGetWidth(frame);
- CGFloat height = CGRectGetHeight(frame);
不良的風(fēng)格:
- 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;
常量
優(yōu)先使用常類型變量,而不是內(nèi)嵌的字符串字面值或數(shù)字,因為常類型變量能很容易的復(fù)用常用的變量值(如π),同時可以快速地修改值而無需查找替換。常類型變量應(yīng)該聲明為static類型,不要使用#define,除非常類型變量被作為宏使用。
良好的風(fēng)格:
- static NSString * const RNCAboutViewControllerCompanyName = @"The New York Times Company";
- static const CGFloat RNCImageThumbnailHeight = 50.0;
不良的風(fēng)格:
- #define CompanyName @"The New York Times Company"
- #define thumbnailHeight 2
枚舉類型
當(dāng)使用enum關(guān)鍵字時,推薦使用蘋果最新引入的固定基礎(chǔ)類型語法,因為這將獲得強(qiáng)類型檢查與代碼完成功能。SDK現(xiàn)在包含了一個固定基礎(chǔ)類型的宏——NS_ENUM()。
NS_ENUM是在iOS6中開始引入的,為了支持之前的iOS版本,使用簡單的內(nèi)聯(lián)方法:
- #ifndef NS_ENUM
- #define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
- #endif
良好的風(fēng)格:
- typedef NS_ENUM(NSInteger, RNCAdRequestState) {
- RNCAdRequestStateInactive,
- RNCAdRequestStateLoading
- };
私有屬性
私有屬性應(yīng)該被聲明在實現(xiàn)文件的類擴(kuò)展中(即匿名的category)。不要將私有屬性聲明在命名的category(如RNCPrivate或private),除非是擴(kuò)展其他類。
良好的風(fēng)格:
- @interface NYTAdvertisement ()
- @property (nonatomic, strong) GADBannerView *googleAdView;
- @property (nonatomic, strong) ADBannerView *iAdView;
- @property (nonatomic, strong) UIWebView *adXWebView;
- @end
圖片的命名
圖片的命名應(yīng)該保持一致,以圖片的用途描述作為圖片文件名。文件名的命名使用駝峰式大小寫風(fēng)格,文件名后可跟隨一個自定義的類名或者是自定義的屬性名(如果有屬性名)、也可以再跟上顏色描述以及/或者位置、圖片的最終狀態(tài)。
良好的風(fēng)格:
RefreshBarButtonItem / RefreshBarButtonItem@2x 和 RefreshBarButtonItemSelected / RefreshBarButtonItemSelected@2x
ArticleNavigationBarWhite / ArticleNavigationBarWhite@2x 和 ArticleNavigationBarBlackSelected / ArticleNavigationBarBlackSelected@2x.
被用作相似用途的圖片應(yīng)該使用一個圖片文件夾進(jìn)行分開管理。
布爾類型
因為nil被解析為了NO,所以和nil作比較沒有任何的必要。不要將變量和YES直接比較,因為YES被定義為1而BOOL類型是8位的unsigned int,即BOOL的值不僅僅是1或0。
良好的風(fēng)格:
- if (!someObject) {
- }
不良的風(fēng)格:
- if (someObject == nil) {
- }
對于一個BOOL值:兩種最佳實踐:
- if (isAwesome)
- if (![someObject boolValue])
不良的風(fēng)格:
- if ([someObject boolValue] == NO)
- if (isAwesome == YES) // Never do this.
如果一個BOOL類型的屬性名是一個形容詞,忽略屬性名的“is”前綴是允許的,但需要為訪問器指定約定的方法名,比如:
@property (assign, getter=isEditable) BOOL editable;
單例
應(yīng)該使用線程安全的模式創(chuàng)建共享的單例實例。
- + (instancetype)sharedInstance {
- static id sharedInstance = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- sharedInstance = [[self alloc] init];
- });
- return sharedInstance;
- }
附錄
Xcode主題
大部分的開發(fā)者都使用Xcode默認(rèn)的字體顏色主題,其實好的主題不僅能提高源代碼的辨識度,同時也增添了編碼的樂趣。以下是二款Xcode字體顏色主題鏈接:
https://github.com/vinhnx/Ciapre-Xcode-theme
https://github.com/tursunovic/xcode-themes
代碼片段
熟練使用代碼片段庫可以提高編碼的速度。Xcode4中,打開一個項目并讓右側(cè)編輯區(qū)可視,然后點擊右側(cè)底部面板的第四個{}圖標(biāo),打開代碼片段庫,你可以將常用的代碼拖入其中。以下是一個最新的開源代碼片段庫鏈接:
https://github.com/mattt/Xcode-Snippets