《代碼規范》
類的布局
- (void)dealloc
-?(instancetype)init
#pragma mark - Life Cycle Methods
-?(void)viewWillAppear:(BOOL)animated
-?(void)viewDidAppear:(BOOL)animated
-?(void)viewWillDisappear:(BOOL)animated
-?(void)viewDidDisappear:(BOOL)animated
#pragma?mark?-?Override?Methods
#pragma?mark?-?Intial?Methods
#pragma?mark?-?Network?Methods
#pragma?mark?-?Target?Methods
#pragma?mark?-?Public?Methods
#pragma?mark?-?Private?Methods
#pragma?mark?-?UITableViewDataSource
#pragma?mark?-?UITableViewDelegate
#pragma?mark?-?Lazy?Loads
#pragma?mark?-?NSCopying
#pragma?mark?-?NSObject??Methods
#pragma mark - description
Switch語句
1. 每個分支都必須用大括號括起來
switch?(integer)?{
case?1:??{
//?...
break;
}
case?2:?{
//?...
break;
}
case?3:?{
//?...
break;
}
default:{
//?...
break;
}
}
2. 使用枚舉類型時,不能有default分支, 除了使用枚舉類型以外,都必須有default分支
RWTLeftMenuTopItemType?menuType?=?RWTLeftMenuTopItemMain;
switch?(menuType)?{
case?RWTLeftMenuTopItemMain:?{
//?...
break;
}
case?RWTLeftMenuTopItemShows:?{
//?...
break;
}
case?RWTLeftMenuTopItemSchedule:?{
//?...
break;
}
}
在Switch語句使用枚舉類型的時候,如果使用了default分支,在將來就無法通過編譯器來檢查新增的枚舉類型了。
函數
1. 一個函數的長度必須限制在80~100行以內
2. 一個函數只做一件事(單一原則)
每個函數的職責都應該劃分的很明確(就像類一樣)。
推薦這樣寫:
dataConfiguration()
viewConfiguration()
不推薦這樣寫:
void?dataConfiguration()
{
...
viewConfiguration()
}
3. 對于有返回值的函數(方法),每一個分支都必須有返回值
推薦這樣寫:
int?function()
{
if(condition1){
return?count1
}else?if(condition2){
return?count2
}else{
return?defaultCount
}
}
4. 對輸入參數的正確性和有效性進行檢查,參數錯誤立即返回
推薦這樣寫:
void?function(param1,param2)
{
if(param1?is?unavailable){
return;
}
if(param2?is?unavailable){
return;
}
//Do?some?right?thing
}
5. 將函數內部比較復雜的邏輯提取出來作為單獨的函數
一個函數內的不清晰(邏輯判斷比較多,行數較多)的那片代碼,往往可以被提取出去,構成一個新的函數,然后在原來的地方調用它這樣你就可以使用有意義的函數名來代替注釋,增加程序的可讀性。
舉一個發送郵件的例子:
openEmailSite();
login();
writeTitle(title);
writeContent(content);
writeReceiver(receiver);
addAttachment(attachment);
send();
中間的部分稍微長一些,我們可以將它們提取出來:
void?writeEmail(title,?content,receiver,attachment)
{
writeTitle(title);
writeContent(content);
writeReceiver(receiver);
addAttachment(attachment);
}
然后再看一下原來的代碼:
openEmailSite();
login();
writeEmail(title,?content,receiver,attachment)
send();
6. 避免使用全局變量,類成員(class member)來傳遞信息,盡量使用局部變量和參數。
在一個類里面,經常會有傳遞某些變量的情況。而如果需要傳遞的變量是某個全局變量或者屬性的時候,有些朋友不喜歡將它們作為參數,而是在方法內部就直接訪問了:
class?A?{
var?x;
func?updateX()
{
...
x?=?...;
}
func?printX()
{
updateX();
print(x);
}
}
我們可以看到,在printX方法里面,updateX和print方法之間并沒有值的傳遞,乍一看我們可能不知道x從哪里來的,導致程序的可讀性降低了。
而如果你使用局部變量而不是類成員來傳遞信息,那么這兩個函數就不需要依賴于某一個類,而且更加容易理解,不易出錯:
func?updateX()?->?String
{
x?=?...;
return?x;
}
func?printX()
{
String?x?=?updateX();
print(x);
}
三. iOS規范
變量
1. 變量名必須使用駝峰格式
類,協議使用大駝峰:
HomePageViewController.h
對象等局部變量使用小駝峰:
NSString?*personName?=?@"";
NSUInteger?totalCount?=?0;
2. 變量的名稱必須同時包含功能與類型
UIButton?*addBtn?//添加按鈕
UILabel?*nameLbl?//名字標簽
NSString?*addressStr//地址字符串
3. 系統常用類作實例變量聲明時加入后綴
常量
1. 常量以相關類名作為前綴
推薦這樣寫:
static?const?NSTimeInterval?ZOCSignInViewControllerFadeOutAnimationDuration?=?0.4;
2. 建議使用類型常量,不建議使用#define預處理命令
首先比較一下這兩種聲明常量的區別:
預處理命令:簡單的文本替換,不包括類型信息,并且可被任意修改。
類型常量:包括類型信息,并且可以設置其使用范圍,而且不可被修改。
使用預處理雖然能達到替換文本的目的,但是本身還是有局限性的:
不具備類型信息。
可以被任意修改。
3. 對外公開某個常量:
如果我們需要發送通知,那么就需要在不同的地方拿到通知的“頻道”字符串(通知的名稱),那么顯然這個字符串是不能被輕易更改,而且可以在不同的地方獲取。這個時候就需要定義一個外界可見的字符串常量。
推薦這樣寫:
//頭文件
extern?NSString?*const?ZOCCacheControllerDidClearCacheNotification;
//實現文件
static?NSString?*?const?ZOCCacheControllerDidClearCacheNotification?=?@"ZOCCacheControllerDidClearCacheNotification";
static?const?CGFloat?ZOCImageThumbnailHeight?=?50.0f;
不推薦這樣寫:
#define?CompanyName?@"Apple?Inc."
#define?magicNumber?42
宏
1. 宏、常量名都要使用大寫字母,用下劃線‘_’分割單詞。
#define?URL_GAIN_QUOTE_LIST?@"/v1/quote/list"
#define?URL_UPDATE_QUOTE_LIST?@"/v1/quote/update"
#define?URL_LOGIN??@"/v1/user/login”
2. 宏定義中如果包含表達式或變量,表達式和變量必須用小括號括起來。
#define?MY_MIN(A,?B)??((A)>(B)?(B):(A))
CGRect函數
其實iOS內部已經提供了相應的獲取CGRect各個部分的函數了,它們的可讀性比較高,而且簡短,推薦使用:
推薦這樣寫:
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);
而不是
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?};
Block
為常用的Block類型創建typedef
定義類型:
typedef int(^EOCSomeBlock)(BOOL flag, int value);
通過簡單的賦值來實現:
EOCSomeBlock?block?=?^(BOOL?flag,?int?value){
//?Implementation
};
定義作為參數的Block:
-?(void)startWithCompletionHandler:?(void(^)(NSData?*data,?NSError?*error))completion;
這里的Block有一個NSData參數,一個NSError參數并沒有返回值
typedef?void(^EOCCompletionHandler)(NSData?*data,?NSError?*error);
-?(void)startWithCompletionHandler:(EOCCompletionHandler)completion;”
通過typedef定義Block簽名的好處是:如果要某種塊增加參數,那么只修改定義簽名的那行代碼即可。
字面量語法
盡量使用字面量值來創建 NSString , NSDictionary , NSArray , NSNumber 這些不可變對象:
推薦這樣寫:
NSArray?*names?=?@[@"Brian",?@"Matt",?@"Chris",?@"Alex",?@"Steve",?@"Paul"];
NSDictionary?*productManagers?=?@{@"iPhone"?:?@"Kate",?@"iPad"?:?@"Kamal",?@"Mobile?Web"?:?@"Bill"};
NSNumber?*shouldUseLiterals?=?@YES;
NSNumber?*buildingZIPCode?=?@10018;
不推薦這樣寫:
NSArray?*names?=?[NSArray?arrayWithObjects:@"Brian",?@"Matt",?@"Chris",?@"Alex",?@"Steve",?@"Paul",?nil];
NSDictionary?*productManagers?=?[NSDictionary?dictionaryWithObjectsAndKeys:?@"Kate",?@"iPhone",?@"Kamal",?@"iPad",?@"Bill"?];
NSNumber?*shouldUseLiterals?=?[NSNumber?numberWithBool:YES];
NSNumber?*buildingZIPCode?=?[NSNumber?numberWithInteger:10018];
屬性
1. 屬性的命名使用小駝峰
推薦這樣寫:
@property?(nonatomic,?readwrite,?strong)?UIButton?*confirmButton;
2. 屬性的關鍵字推薦按照 原子性,讀寫,內存管理的順序排列
推薦這樣寫:
@property?(nonatomic,?readwrite,?copy)?NSString?*name;
@property?(nonatomic,?readonly,?copy)?NSString?*gender;
@property?(nonatomic,?readwrite,?strong)?UIView?*headerView;
3. Block屬性應該使用copy關鍵字
推薦這樣寫:
typedef?void?(^ErrorCodeBlock)?(id?errorCode, NSString?*message);
@property?(nonatomic,?readwrite,?copy)?ErrorCodeBlock?errorBlock; ? //將block拷貝到堆中
4. 形容詞性的BOOL屬性的getter應該加上is前綴
推薦這樣寫:
@property?(assign,?getter=isEditable)?BOOL?editable;
5. 使用getter方法做懶加載
實例化一個對象是需要耗費資源的,如果這個對象里的某個屬性的實例化要調用很多配置和計算,就需要懶加載它,在使用它的前一刻對它進行實例化:
-?(NSDateFormatter?*) dateFormatter
{
if?(!_dateFormatter)?{
_dateFormatter?=?[[NSDateFormatter?alloc]?init];
NSLocale?*enUSPOSIXLocale?=?[[NSLocale?alloc]?initWithLocaleIdentifier:@"en_US_POSIX"];
[_dateFormatter?setLocale:enUSPOSIXLocale];
[_dateFormatter?setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS"];
}
return?_dateFormatter;
}
6. 除了init和dealloc方法,建議都使用點語法訪問屬性
使用點語法的好處:
setter:
setter會遵守內存管理語義(strong, copy, weak)。
通過在內部設置斷點,有助于調試bug。
可以過濾一些外部傳入的值。
捕捉KVO通知。
getter:
允許子類化。
通過在內部設置斷點,有助于調試bug。
實現懶加載(lazy initialization)。
注意:
懶加載的屬性,必須通過點語法來讀取數據。因為懶加載是通過重寫getter方法來初始化實例變量的,如果不通過屬性來讀取該實例變量,那么這個實例變量就永遠不會被初始化。
在init和dealloc方法里面使用點語法的后果是:因為沒有繞過setter和getter,在setter和getter里面可能會有很多其他的操作。而且如果它的子類重載了它的setter和getter方法,那么就可能導致該子類調用其他的方法。
7. 盡量使用不可變對象
建議盡量把對外公布出來的屬性設置為只讀,在實現文件內部設為讀寫。具體做法是:
在頭文件中,設置對象屬性為readonly。
在實現文件中設置為readwrite。
這樣一來,在外部就只能讀取該數據,而不能修改它,使得這個類的實例所持有的數據更加安全。而且,對于集合類的對象,更應該仔細考慮是否可以將其設為可變的。
如果在公開部分只能設置其為只讀屬性,那么就在非公開部分存儲一個可變型。所以當在外部獲取這個屬性時,獲取的只是內部可變型的一個不可變版本,例如:
在公共API中:
@interface?EOCPerson?:?NSObject
@property?(nonatomic,?copy,?readonly)?NSString?*firstName;
@property?(nonatomic,?copy,?readonly)?NSString?*lastName;
@property?(nonatomic,?strong,?readonly)?NSSet?*friends?//向外公開的不可變集合
-?(id)initWithFirstName:(NSString*)firstName?andLastName:(NSString*)lastName;
-?(void)addFriend:(EOCPerson*)person;
-?(void)removeFriend:(EOCPerson*)person;
@end
在這里,我們將friends屬性設置為不可變的set。然后,提供了來增加和刪除這個set里的元素的公共接口。
在實現文件里:
@interface?EOCPerson?()
@property?(nonatomic,?copy,?readwrite)?NSString?*firstName;
@property?(nonatomic,?copy,?readwrite)?NSString?*lastName;
@end
@implementation?EOCPerson?{
NSMutableSet?*_internalFriends;??//實現文件里的可變集合
}
-?(NSSet*)friends
{
return?[_internalFriends?copy];?//get方法返回的永遠是可變set的不可變型
}
-?(void)addFriend:(EOCPerson*)person
{
[_internalFriends?addObject:person];?//在外部增加集合元素的操作
//do?something?when?add?element
}
-?(void)removeFriend:(EOCPerson*)person
{
[_internalFriends?removeObject:person];?//在外部移除元素的操作
//do?something?when?remove?element
}
-?(id)initWithFirstName:(NSString*)firstName?andLastName:(NSString*)lastName
{
if?((self?=?[super?init]))?{
_firstName?=?firstName;
_lastName?=?lastName;
_internalFriends?=?[NSMutableSet?new];
}
return?self;
}
我們可以看到,在實現文件里,保存一個可變set來記錄外部的增刪操作。
這里最重要的代碼是:
-?(NSSet*)friends
{
return?[_internalFriends?copy];
}
這個是friends屬性的獲取方法:它將當前保存的可變set復制了一不可變的set并返回。因此,外部讀取到的set都將是不可變的版本。
類
1. 類的名稱應該以三個大寫字母為前綴;創建子類的時候,應該把代表子類特點的部分放在前綴和父類名的中間
推薦這樣寫:
//父類
ZOCSalesListViewController
//子類
ZOCDaySalesListViewController
ZOCMonthSalesListViewController
2. initializer && dealloc
推薦:
將 dealloc 方法放在實現文件的最前面
將init方法放在dealloc方法后面。如果有多個初始化方法,應該將指定初始化方法放在最前面,其他初始化方法放在其后。
2.1 dealloc方法里面應該直接訪問實例變量,不應該用點語法訪問
2.2 init方法的寫法:
init方法返回類型必須是instancetype,不能是id。
必須先實現[super init]。
-?(instancetype)init
{
self?=?[super?init];?//?call?the?designated?initializer
if?(self)?{
//?Custom?initialization
}
return?self;
}
2.3 指定初始化方法
指定初始化方法(designated initializer)是提供所有的(最多的)參數的初始化方法,間接初始化方法(secondary initializer)有一個或部分參數的初始化方法。
注意事項1:間接初始化方法必須調用指定初始化方法。
@implementation?ZOCEvent
//指定初始化方法
-?(instancetype)initWithTitle:(NSString?*)title?date:(NSDate?*)date
location:(CLLocation?*)location
{
self?=?[super?init];
if?(self)?{
_title?=?title;
_date?=?date;
_location?=?location;
}
return?self;
}
//間接初始化方法
-??(instancetype)initWithTitle:(NSString?*)title?date:(NSDate?*)date
{
return?[self?initWithTitle:title?date:date?location:nil];
}
//間接初始化方法
-??(instancetype)initWithTitle:(NSString?*)title
{
return?[self?initWithTitle:title?date:[NSDate?date]?location:nil];
}
@end
注意事項2:如果直接父類有指定初始化方法,則必須調用其指定初始化方法
-?(instancetype)initWithNibName:(NSString?*)nibNameOrNil?bundle:(NSBundle?*)nibBundleOrNil
{
self?=?[super?initWithNibName:nibNameOrNil?bundle:nibBundleOrNil];
if?(self)?{
}
return?self;
}
注意事項3:如果想在當前類自定義一個新的全能初始化方法,則需要如下幾個步驟
定義新的指定初始化方法,并確保調用了直接父類的初始化方法。
重載直接父類的初始化方法,在內部調用新定義的指定初始化方法。
為新的指定初始化方法寫文檔。
看一個標準的例子:
@implementation?ZOCNewsViewController
//新的指定初始化方法
-?(id)initWithNews:(ZOCNews?*)news
{
self?=?[super?initWithNibName:nil?bundle:nil];
if?(self)?{
_news?=?news;
}
return?self;
}
//?重載父類的初始化方法
-?(id)initWithNibName:(NSString?*)nibNameOrNil?bundle:(NSBundle?*)nibBundleOrNil{
return?[self?initWithNews:nil];
}
@end
在這里,重載父類的初始化方法并在內部調用新定義的指定初始化方法的原因是你不能確定調用者調用的就一定是你定義的這個新的指定初始化方法,而不是原來從父類繼承來的指定初始化方法。
假設你沒有重載父類的指定初始化方法,而調用者卻恰恰調用了父類的初始化方法。那么調用者可能永遠都調用不到你自己定義的新指定初始化方法了。
而如果你成功定義了一個新的指定初始化方法并能保證調用者一定能調用它,你最好要在文檔中明確寫出哪一個才是你定義的新初始化方法。或者你也可以使用編譯器指令__attribute__((objc_designated_initializer))來標記它。
方法文檔
看一個指定初始化方法的注釋:
/
*??Designated?initializer.?* ? ?總結性的短語
*??@param?store?The?store?for?CRUD?operations. ? ? ? 參數的描述
*? @param searchService The search service used to query the store.? ? ? 參數的描述
*??@return?A?ZOCCRUDOperationsStore?object. ? ? ? ? ?返回值的描述
*/
-?(instancetype)initWithOperationsStore:(id)store?searchService:(id)searchService;
NSArray& NSMutableArray
1. addObject之前要非空判斷。
2. 取下標的時候要判斷是否越界。
3. 取第一個元素或最后一個元素的時候使用firtstObject和lastObject
NSNotification
1. 通知的名稱
建議將通知的名字作為常量,保存在一個專門的類中:
//?Const.h
extern?NSString?*?const?ZOCFooDidBecomeBarNotification
//?Const.m
NSString?*?const?ZOCFooDidBecomeBarNotification?=?@"ZOCFooDidBecomeBarNotification";
2. 通知的移除
通知必須要在對象銷毀之前移除掉。
1. Xcode工程文件的物理路徑要和邏輯路徑保持一致。
2. 忽略沒有使用變量的編譯警告
對于某些暫時不用,以后可能用到的臨時變量,為了避免警告,我們可以使用如下方法將這個警告消除:
-?(NSInteger)giveMeFive
{
NSString?*foo;
#pragma?unused?(foo)
return?5;
}
3. 手動標明警告和錯誤
-?(NSInteger)divide:(NSInteger)dividend?by:(NSInteger)divisor
{
//手動明確一個錯誤:
#error?Whoa,?buddy,?you?need?to?check?for?zero?here!
return?(dividend?/?divisor);
}
-?(float)divide:(float)dividend?by:(float)divisor
{
手動明確一個警告:
#warning?Dude,?don't?compare?floating?point?numbers?like?this!
if?(divisor?!=?0.0)?{
return?(dividend?/?divisor);
}?else?{
return?NAN;
}
}