Apple公司提供了一些代碼規範文檔。若是有內容未在此文檔中說起,請參考以下內容:html
全部適用Objective-C語言開發的項目。 在這裏咱們但願以相似斷言的方式,你們逐條對比寫出的代碼和下列規範是否吻合,以達到預期的代碼的可讀性。ios
基於iOS objective-c項目對於命名,目前分爲變量名和函數名兩類git
在這裏咱們把描述一個事物或者抽象事物的描述符統稱爲變量名。變量名目前分爲幾類: 類名,協議名,組合名,oc類內部變量,全局變量,枚舉類型,block類型,結構體類型。 如下分別例舉了幾種類型的例子。github
@interface MKUserTrackingBarButtonItem : UIBarButtonItem
@protocol MKMapViewDelegate <NSObject> @protocol MKAnnotation <NSObject>
@interface NSString (NSStringExtensionMethods)
@property (nonatomic, copy) NSString *title; @property (nonatomic, readonly, getter=isLoading) BOOL loading; @property (nonatomic, getter=isSelected) BOOL selected;
NSString * const NSSystemClockDidChangeNotification
enum
枚舉,由於它支持強類型檢查及自動完成。SDK如今也支持枚舉定義宏NS_ENUm()
和NS_OPTION()
,前者的各個選項是互斥的,然後者能夠經過按位或|
來組合使用。typedef NS_OPTIONS(NSUInteger, MKDirectionsTransportType) { MKDirectionsTransportTypeAutomobile = 1 << 0, MKDirectionsTransportTypeWalking = 1 << 1, MKDirectionsTransportTypeAny = 0x0FFFFFFF }
typedef void (^MKDirectionsHandler)(MKDirectionsResponse *response, NSError *error);
typedef struct { CLLocationDegrees latitude; CLLocationDegrees longitude; } CLLocationCoordinate2D;
apple使用的縮寫objective-c
######一些習慣 1.上述咱們說一個層級變量起名爲 限定詞+功能+類型。這裏咱們有個例外的地方,對於NSString, NSArray, NSNumber, BOOL類型咱們無需指定類型。安全
2.對於命名一個複數形式的變量,若是它不是NSArray或者NSSet最好指定類型。 3.對於其餘類型,好比Image, Indicator這樣的特殊類型或UI組件在變量命名的後半部分指定它的類型是有必要的。 尤爲對於XXXManager類型的變量寫成好比fontManager是必須的,不然沒法理解它的含義。
一些習慣的例子app
oc語言實際上很貼近天然語言。先拋開一般做爲全局函數用的c/cpp函數,oc的類內部函數一般看起來就像是一個句子。在這個命名規範裏不去結合語法分析這個了,一下會根據函數常有的功能去作個分類。iphone
#####以動詞開頭方法ide
- (void)insertOverlay:(id <MKOverlay>)overlay atIndex:(NSUInteger)index level:(MKOverlayLevel)level; - (BOOL)createSymbolicLinkAtURL:(NSURL *)url withDestinationURL:(NSURL *)destURL error:(NSError **)error;
1.對於以動詞開頭的函數,表示去執行某一個任務。 2.咱們通常定義他的返回值爲void, 當須要獲得他是否執行成功的狀態時能夠以BOOL做爲返回值 3.有一個例外是init開頭通常是用於構造,返回一個構造的實例。
#####以名詞開頭方法svn
- (MKAnnotationView *)viewForAnnotation:(id <MKAnnotation>)annotation; + (instancetype)circleWithCenterCoordinate:(CLLocationCoordinate2D)coord radius:(CLLocationDistance)radius;
1.對於以動詞開頭的函數,表示返回某一個具體事物。 2.當函數做爲回調函數存在時,它是例外的
#####回調函數
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row; - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender; - (void)browserDidScroll:(NSBrowser *)sender; - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window; - (BOOL)windowShouldClose:(id)sender; - (void)windowDidChangeScreen:(NSNotification *)notification;
1.消息發送者必須做爲參數。若是函數參數只有消息發送者自己,將他放到函數最後。若是有2個及以上參數則放到第一位。 2.did和will常常用在回調函數當作標記'已經發生'或者'將要發生'。 3.'should'應用場景一般是詢問代理行爲是否應該發生,一般返回BOOL。 4.經過通知的回調通常來講全部數據都放在notification內部,因此不須要返回值以及其餘參數 5.全部回調函數均以名詞開頭,標示是什麼引起的回調。咱們能夠認爲去掉開頭的名詞和調用者參數,基本和咱們以前定的規範一致。
#####一些通用規則和建議 1.參數冒號以前用名詞指代明確的參數類型 2.多個參數不須要用and鏈接 3.一些介詞有助於提高函數名的可讀性,好比:for,with,from,in,on,at等。
####處理魔術變量 使用常量而非內聯的字串literal或魔術數,由於這樣能更方便地修改它們。
使用static const
常量,禁止使用#define
宏來定義變量,使用宏沒有類型檢查,並易被覆蓋定義而很難檢測。
For example:
static NSString * const ZDAboutViewControllerCompanyName = @"The New York Times Company"; static const CGFloat ZDAboutViewThumbImageHeight = 50.0;
Not:
#define CompanyName @"The ZDWorks Company" #define thumbnailHeight 2
使用dot-notation(.表示法)來獲取/更改property。 Bracket notation([]表示法)適用於其餘領域。
For example:
view.backgroundColor = [UIColor orangeColor]; [UIApplication sharedApplication].delegate;
Not:
[view setBackgroundColor:[UIColor orangeColor]]; UIApplication.sharedApplication.delegate;
只能在初始化方法、析構方法和自定義getter/setter裏面,直接訪問實例變量(ivar),其餘狀況只能經過dot-notation訪問property。更多內容參見 here. 不要直接聲明實例變量,聲明property便可。
使用BBUncrustify來格式化代碼,formatter使用Clang,配置文件見.clang-format
{
),另起一行關閉(}
)if
/else
/switch
/while
/block
etc.)在當前行打開,另起一行關閉For example:
- (void)foo:(User *)user { if (user.isHappy) { //Do something } else { //Do something else } }
@dynamic
或@synthesize
佔據一行,Xcode4.4之後省略@synthesize
。條件語句的body必須被括號包含,即便只有一行。這樣便於在body內新增操做而不會出錯,同時可讀性更強。
For example:
if (!error) { return success; }
Not:
if (!error) return success;
or
if (!error) return success;
OC的方法,須要在符號+/-後添加一個空格。前一個參數和後一箇中綴之間有且僅有一個空格,好比下方示例的text參數和image中綴之間。
For Example:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
指針變量的*與指針類型中間隔一個空格,與變量名中間無空格,e.g., NSString *text
not NSString* text
or NSString * text
。
對外接口必須寫註釋
註釋能夠採用/* */
和//
兩種註釋符號,涉及到多行註釋時,儘可能使用/* */
。方法裏的註釋只能使用//
,由於嵌套/**/
極可能帶來沒法預知的問題。
Xcode會生成一段默認註釋,咱們須要在此基礎上擴充,加入功能描述和修改記錄部分。雖然svn/git可以看到完整的修改記錄以及經過blame查找責任人,可是commit太多的時候很難定位。
For example:
// // ClockTricksController.m // ZDClock // // Created by John_Ma on 13-11-26. // Copyright (c) 2013年 ZDworks Co., Ltd. All rights reserved. // 功能描述: // 修改記錄:(僅記錄功能修改) // 張三 2012-02-02 建立該單元 // 小明 2010-03-02 增長本地點單功能。 //
方法註釋通常出如今.h文件裏,.m文件裏儘可能保持簡潔,使用方法名完整描述功能和參數。方法註釋使用VVDocument插件生成,並在每次修改後及時更新。
For example:
/** * <#Description#> * * @param application <#application description#> * @param launchOptions <#launchOptions description#> * * @return <#return value description#> */
儘可能不要出現方法內註釋,若有可能將相關代碼Extract到新方法裏,使用方法名描述其功能。若是必需要要使用方法內註釋,使用//
註釋在所要描述的代碼前一行或者同一行末尾。
.h文件中只暴露目前被其餘類使用的接口、屬性。內部使用的接口、屬性在extension(匿名category)中定義,好比IBOutlet等。
在.h實現protocol亦是如此,會暴露該protocol包含的接口。若是外部無需使用相關接口,則移到extesion中。
For example:
// .m file @interface Test ()<UITableViewDataSource> @property (nonatomic, strong) IBOutlet UIButton *refreshButton; - (void)privateDoSth; @end
Not:
// .m file @interface Test : NSObject<UITableViewDataSource> @property (nonatomic, strong) IBOutlet UIButton *refreshButton; - (void)privateDoSth; @end
只有當可以提升代碼的可讀性時,才應該使用三目運算符?:,好比單一判斷條件。若是有多個判斷條件,使用if會更好些。
For example:
result = a > b ? x : y;
Not:
result = a > b ? x = c > d ? c : d : y;
當方法使用引用返回表示錯誤的參數時,使用返回值判斷,而非該錯誤變量。
For example:
NSError *error; if (![self trySomethingWithError:&error]) { // Handle Error }
Not:
NSError *error; [self trySomethingWithError:&error]; if (error) { // Handle Error }
在成功的狀況下,Apple的一些API會將奇怪的值而非nil寫入錯誤參數,因此不要使用該錯誤變量來判斷。
NSString
, NSDictionary
, NSArray
和NSNumber
的immutable實例應該使用literal來建立,mutable實例也建議經過這種方式及mutableCopy方法來建立。須要注意的是須要作nil檢測。
For example:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"]; NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"}; NSNumber *shouldUseLiterals = @YES; NSNumber *buildingZIPCode = @10018;
Not:
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];
使用CGGeometry
functions而非結構體的數據成員來獲取x
, y
, width
, or height
的值。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.
全部在這裏定義、使用CGRect結構體做爲輸入參數的方法,先對這些矩形作標準化操做,再計算它們的方繪製。因此咱們應該直接經過這些方法,而非訪問結構體的數據成員來得到這些矩形的屬性。
For example:
CGRect frame = self.view.frame; CGFloat x = CGRectGetMinX(frame); CGFloat y = CGRectGetMinY(frame); CGFloat width = CGRectGetWidth(frame); CGFloat height = CGRectGetHeight(frame);
Not:
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;
私有property應該定義在類擴展(匿名類別)中。這樣有個好處是,當你須要將其暴露給外部,直接command+x、command+v到.h文件中便可。
For example:
@interface ZDAdvertisement () @property (nonatomic, strong) GADBannerView *googleAdView; @property (nonatomic, strong) ADBannerView *iAdView; @property (nonatomic, strong) UIWebView *adXWebView; @end
在OC中,使用以下線程安全的方式來建立單例
+ (instancetype)sharedInstance { static id sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; }
這種方式能夠防止 可能的崩潰.
函數長度(行數)不該超過2/3屏幕,禁止超過70行。
例外:對於順序執行的初始化函數,若是其中的過程沒有提取爲獨立方法的必要,則沒必要限制長度。
儘早返回錯誤:
For example:
- (Task *)creatTaskWithPath:(NSString *)path { if (![path isURL]) { return nil; } if (![fileManager isWritableFileAtPath:path]) { return nil; } if ([taskManager hasTaskWithPath:path]) { return nil; } Task *aTask = [[Task alloc] initWithPath:path]; return aTask; }
Not:
- (Task *)creatTaskWithPath:(NSString *)path { Task *aTask; if ([path isURL]) { if ([fileManager isWritableFileAtPath:path]) { if (![taskManager hasTaskWithPath:path]) { aTask = [[Task alloc] initWithPath:path]; } else { return nil; } } else { return nil; } } else { return nil; } return aTask; }