轉載自:CocoaChina
Objective-C 是 C 語言的擴展焊夸,增加了動態(tài)類型和面對對象的特性苛败。它被設計成具有易讀易用的,支持復雜的面向對象設計的編程語言镰矿。它是 Mac OS X 以及 iPhone 的主要開發(fā)語言琐驴。
Cocoa 是 Mac OS X 上主要的應用程序框架之一。它由一組 Objective-C 類組成,為快速開發(fā)出功能齊全的 Mac OS X 應用程序提供支持绝淡。
而在日常的編程中宙刘,我們除了要寫代碼,還需要去閱讀別人的代碼牢酵,熟悉過往的業(yè)務邏輯悬包。不知,你可曾發(fā)過牢騷:這代碼怎么能這么寫呢馍乙?有些時候我們的代碼布近,也會被別人去讀,不知你可曾想過丝格,當別人讀到你的代碼的時候會作何評價吊输。誠然,“讓代碼能夠工作”是做為開發(fā)者的頭等大事铁追。但是季蚂,代碼的可維護性卻是更加影響深遠的一件事情。你的代碼既有可能在下一個版本中被修改琅束,也極有可能被交給另外的同事去修改扭屁。畢竟我們寫代碼,不止是在和機器溝通涩禀,而且也是在和人溝通——和其他的程序員溝通料滥。大家都知道“學好普通話,走遍天下都不怕”艾船,同樣的道理:寫出一手漂亮的代碼葵腹,你和誰溝通都沒問題。
即使你的原始代碼修改之后屿岂,其代碼風格和可讀性仍會影響到可維護性和可擴展性践宴。即使代碼不復存在,你的風格和律條仍存活下來爷怀。
下面我們將圍繞一些基本的準則展開討論阻肩,目的是讓我們寫出一手漂亮的代碼,更好的用代碼與其他同事溝通运授,也為了提高我們代碼的可維護性和可修改性烤惊,也是為了讓我們自己工作的地方有一個愉悅的代碼環(huán)境。
(PS:當你真的按照這些看似偏執(zhí)的規(guī)則去做的時候吁朦,你就真的能夠發(fā)現(xiàn)“偉大來自細節(jié)”柒室,而且會受益匪淺。保劍鋒自磨礪出逗宜,梅花香自苦寒來雄右。)
總則
1.Don’t repeat your self.
2.代碼自注釋剥啤,依靠代碼本身來表達你的設計意圖,不要依賴注釋不脯。
3.單一指責府怯,無論是類、函數(shù)防楷、模塊牺丙、包盡可能令其指責純凈且單一。
4.死程序不說謊复局,不要因為防止Crash寫奇葩的代碼冲簿。程序Crash了,反而更容易查找錯誤亿昏。
5.借用美國童子軍軍規(guī):讓營地比你來時更干凈峦剔。
格式
1.任意函數(shù)長度不得超過50行。
2.任意行代碼不得超過80字符角钩×吣可以在設置中設置超過80個字符的提醒。
3.在定義函數(shù)的行前留白一行
4.功能相近的代碼要放在一起递礼。
5.使用#pragma來切分不同功能區(qū)域的代碼惨险。
6.二元運算符和參數(shù)之間需要放置一個空格,一元運算符脊髓、強制類型轉換和參數(shù)之間不放置空格辫愉。關鍵字之后圓括號之前需要放置一個空格.
void *ptr = &value + 10 * 3;
NewType a = (NewType)b;
for(int i = 0; i < 10; i++) {
doCoolThings();
}
7.長的字面值應被拆分為多行。
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",
};
命名
命名是編程中最基本的技能将硝,我們給變量恭朗、函數(shù)、類依疼、包等等命名痰腮。給他們以名字,讓他們有意義涛贯,既能表示他們到底是做什么的诽嘉,也能將其與其他變量區(qū)別開來蔚出。而通過弟翘,語言的發(fā)展史,我們也能夠看到“方便編程人員理解和使用”一直都是編程語言發(fā)展的動力之一骄酗,而命名則是其最最核心的環(huán)節(jié)稀余。像人一樣娶一個好名字至關重要,“丁當”總比“狗蛋”來的好聽趋翻。 為什么要命名睛琳?命名代表著抽象,我們使用名字將一些沒必要關系的細節(jié)隱去,減少我們自己的記憶成本师骗,也更加方便我們理解历等。用過C語言的人都知道,一個變量名最終會轉化成類似于~~~0x11111111~~~之類的地址辟癌,相比去理解和記憶這些地址寒屯,用一個更加抽象的變量名來代表這些地址。無論從理解還是記憶上都要方便的黍少。
命名一定要“名副其實”寡夹,盡可能使用有意的名稱,而且這個意義和指稱的變量真實意義相關厂置。
盡量不要出現(xiàn)沒有任何意義的命名類似于下述形式的命名:
int a = 1;
int b = 3;
CGPoint point = CGPointMake(a,b);
如果換成下面的形式是不是可讀性強了很多:
int startX = 1;
int startY = 3;
CGPoint startPoint = CGPointMake(startX,startY);
命名首字母大寫菩掏,其他命名首字母小寫。并且采用駝峰格式分割單詞昵济。
例如:BWTest
使用能夠讀出來的名稱
人類長于記憶和使用單詞智绸。大腦中的相當一部分就是用來容納和處理單詞的。單詞如果能夠讀的出來访忿,則非常方便我們閱讀和理解传于。
錯誤的示例: genymdhms (生成日期,年、月醉顽、日沼溜、時、分游添、秒)
正確的實例: generationTimeStamp
使用可搜索的名稱
單字母名稱和數(shù)字常量有一個問題系草,就是很難在一大篇文字中找出來。試想一下唆涝,你找~~~MAX_CLASSES_PER_STUDENT~~~容易還是找數(shù)字7容易找都。
文件名
文件名反映出了其實現(xiàn)了什么類(包括大小寫),你需要遵循所參與醒目的約定廊酣。
文件的擴展名及其意義如下:
類別的擴展名以“被擴展的類名+自定義命名部分組成”
例如:NSSstring+Utils.h
縮略詞
雖然方法命名不應使用縮略詞能耻,然而有些縮略詞在過去被反復的使用,所以使用這些縮略詞能更好的的表達代碼的含義亡驰。下表列出了Cocoa可接受的縮略詞晓猛。
以下是一些常用的首字母縮略詞:ASCII,PDF,XML,HTML,URL,RTF,HTTP,TIFF,JPG,PNG,GIF,LZW,ROM,RGB,CMYK,MIDI,FTP…
宏定義全部字母大寫,例如:#define BW_DEBUG 1
常量定義凡辱,字符串定義以小寫字母 k 開頭戒职,隨后首字母大寫
static NSString* const kBWBarTitle = @"動態(tài)";
如果要定義常量使用static const優(yōu)于宏定義,前者會進行類型檢查
因為OC沒有命名空間的概念透乾,所以使用前兩個或者多個字母來表示命名空間洪燥,例如”NSObject中的NS”磕秤,我們也使用自己的命名空間。比如
紅點中使用了VAS:VASAddValueInfo...
錢包中使用了QW:QWApplication....
注釋
讓代碼自注釋捧韵,不要依賴注釋來解釋自己的設計或者編碼意圖市咆。除了特殊情況外,代碼中不要有多余的注釋再来。
函數(shù)
函數(shù)長度不要超過50行床绪,小函數(shù)要比大函數(shù)可閱讀性和可復用性強。
零元函數(shù)最好其弊,一元函數(shù)也不錯癞己,二元函數(shù)擔心了,三元函數(shù)有風險梭伐,高于三元需重構痹雅。函數(shù)的參數(shù)越多,引起其變化的因素就越多糊识。越不利于以后的修改绩社。
不知道當你看到如下形式的函數(shù)的時候,是什么想法:
- (void)RequestGetLocation:(int)lat lon:(int)lon alt:(int)alt isMars:(BOOL)yn bJiejingSOSO:(BOOL)bJiejingSOSO;
盡量少的寫有副作用的函數(shù)
盡量不要出現(xiàn)火車鏈式的命名赂苗,如果可以盡量使用過程變量替代愉耙。
反例例如:
_needLogoutAccount = [[[[BWAppSetting GetInstance] appSetting] valueForKey:NeedLogoutAccounts] retain];
考慮如果改成下述模樣,是不是可讀性一下子提高了很多:
BWAppSetting* shareSetting = [BWAppSetting GetInstance];
BWLockDictionary* defaultSettings = [shareSetting appSetting];
_needLogoutAccount = [[defaultSettings valueForKeyPath:NeedLogoutAccounts] retain];
調(diào)用時所有參數(shù)應該在同一行
[myObject doFooWith:arg1 name:arg2 error:arg3];
或者每行一個參數(shù)拌滋,以冒號對齊:
[myObject doFooWith:arg1
name:arg2
error:arg3];
對于參數(shù)過多的函數(shù)朴沿,盡量使用后面一種對其方式。
不要使用下面的縮進風格:
[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];
如果對傳入?yún)?shù)進行數(shù)據(jù)保護盡量不要用~~~if(!objc)~~~,使用斷言來處理败砂。
- (void) sendArgs:(NSDictionary*)args {
NSAssert(args, @"args is nil");
.....
}
方法參數(shù)名前一般使用的前綴包括“the”赌渣、“an”、“new”昌犹。
示例:
- (void) setTitle: (NSString *) aTitle;
- (void) setName: (NSString *) newName;
- (id) keyForOption: (CDCOption *) anOption
- (NSArray *) emailsForMailbox: (CDCMailbox *) theMailbox;
- (CDCEmail *) emailForRecipients: (NSArray *) theRecipients;
Block相關
在block中使用到self變量的時候坚芜,一定要先weak再strong.
__weaktypeof(self) weakSelf = self;
[self doABlockOperation:^{
__strongtypeof(weakSelf) strongSelf = weakSelf;
if(strongSelf) {
...
}
}];
控制結構
順序結構
分支結構
if-else結構超過四層的時候,要考慮重構斜姥。多層的ifelse結構極其難維護鸿竖。
當需要滿足一定條件時才執(zhí)行某項操作時,最左邊緣應該是愉快路徑代碼铸敏。不要將愉快路徑代碼內(nèi)嵌到if語句中缚忧。多個return是正常合理的。
良好的風格:
- (void) someMethod {
if(![someOther boolValue]) {
return;
}
//Do something important
}
反面教材:
- (void) someMethod {
if([someOther boolValue]) {
//Do something important
}
所有的邏輯塊必須使用花括號包圍搞坝,即使條件體只需編寫一行代碼也必須使用花括號搔谴。
良好的風格:
if(!error) {
returnsuccess;
}
反面教材:
if(!error)
returnsuccess;
...
if(!error)returnsuccess;
循環(huán)結構
遍歷可變?nèi)萜髦埃枰獜椭圃撊萜髯椋闅v該容器的Copy.
//typeof(self.cells) is NSMutableArray
NSArray* cellArrays = [self.cells copy];
for(UITableViewCell* cellincellArrays) {
...
}
盡量不要使用異常敦第,尤其是不要將異常做為業(yè)務邏輯的一部分,在異常中嘗試進行災難恢復店量。
類與對象
明確指定構造函數(shù)
注釋并且明確指定你的類的構造函數(shù)芜果。
對于需要繼承你的類的人來說,明確指定構造函數(shù)十分重要融师。這樣他們就可以只重寫一個構造函數(shù)(可能是幾個)來保證他們的子類的構造函數(shù)會被調(diào)用右钾。這也有助于將來別人調(diào)試你的類時,理解初始化代碼的工作流程旱爆。 ###重載指定構造函數(shù)
當你寫子類的時候舀射,如果需要 init… 方法,記得重載父類的指定構造函數(shù)怀伦。
如果你沒有重載父類的指定構造函數(shù)脆烟,你的構造函數(shù)有時可能不會被調(diào)用,這會導致非常隱秘而且難以解決的 bug房待。
重載 NSObject的方法
如果重載了 NSObject 類的方法邢羔,強烈建議把它們放在 @implementation 內(nèi)的起始處,這也是常見的操作方法桑孩。
通常適用(但不局限)于init…拜鹤,copyWithZone:,以及dealloc方法流椒。所有init…方法應該放在一起敏簿,copyWithZone: 緊隨其后,最后才是dealloc 方法
初始化
不要在 init 方法中宣虾,將成員變量初始化為 0 或者 nil极谊;毫無必要。
現(xiàn)代的 Ojbective-C 代碼通過調(diào)用 alloc 和 init 方法來創(chuàng)建并 retain 一個對象安岂。由于類方法 new 很少使用轻猖,這使得有關內(nèi)存分配的代碼審查更困難。
保持init函數(shù)簡潔域那,不要讓init函數(shù)成為千行的大函數(shù)咙边,當超過50行的時候,適當考慮分拆一下次员。
良好的風格實例:
- (void) commonInit
{
_rightAppendImageView = [UIImageViewnew];
[self.contentView addSubview:_rightAppendImageView];
}
- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [superinitWithStyle:style reuseIdentifier:reuseIdentifier];
if(!self) {
returnself;
}
[self commonInit];
returnself;
}
UIView的子類初始化的時候败许,不要進行任何布局操作。布局操作在LayoutSubViews里面做淑蔚。
UIView的子類布局必須在layoutSubViews里面進行市殷,需要布局的時候調(diào)用~~~setNeedLayout~~~來告訴系統(tǒng),需要重新布局該View刹衫,不要直接調(diào)用~~~layoutSubViews~~~
保持公共 API 簡單
"保持類簡單醋寝;避免 “廚房水槽(kitchen-sink)” 式的 API搞挣。如果一個函數(shù)壓根沒必要公開,就不要這么做音羞。用私有類別保證公共頭文件整潔囱桨。"
與 C++ 不同,Objective-C 沒有方法來區(qū)分公共的方法和私有的方法 – 所有的方法都是公共的(譯者注:這取決于 Objective-C 運行時的方法調(diào)用的消息機制)嗅绰。因此舍肠,除非客戶端的代碼期望使用某個方法,不要把這個方法放進公共 API 中窘面。盡可能的避免了你你不希望被調(diào)用的方法卻被調(diào)用到翠语。這包括重載父類的方法。對于內(nèi)部實現(xiàn)所需要的方法财边,在實現(xiàn)的文件中定義一個類別肌括,而不是把它們放進公有的頭文件中。
// GTMFoo.m
#import "GTMFoo.h"
@interface GTMFoo (PrivateDelegateHandling)
- (NSString *)doSomethingWithDelegate;// Declare private method
@end
@implementation GTMFoo(PrivateDelegateHandling)
...
- (NSString *)doSomethingWithDelegate {
// Implement this method
}
...
@end
在OC2.0以后制圈,你可以在實現(xiàn)文件中使用们童,類擴展來生命你的私有類別:
@interface GMFoo () { ... }
每個文件中只創(chuàng)建或者實現(xiàn)一個類。同一個文件中不要存在多個類鲸鹦。
Protocol單獨用一個文件來創(chuàng)建慧库。盡量不要與相關類混在一個文件中。
類的私有變量以”_“開頭馋嗜。
創(chuàng)建私有變量齐板,份兩種情況。 第一種情況子類需要繼承的葛菇,在頭文件中定義:
// BWTest.h
@interface BWTest : NSObject
{
NSString* _name;
}
第二種情況甘磨,不需要子類繼承的,在實現(xiàn)文件中以Category的方式定義:
// BWTest.m @interface BWTest () { NSString* _name; }
@implementation BWTest
...
@end
公有變量在一般使用屬性的方法定義 @property (….) …
使用委托模式眯停,設置delegate的時候济舆,在ARC下使用 weak ;在MRC下使用 retain ,并且在dealloc中將其指針置空。
外部引用對象莺债,外部不會發(fā)生set操作的對象滋觉,比如在創(chuàng)建界面元素的時候,使用readonly屬性齐邦。
@interface BWView : UIView
@property (nonatomic, strong, readonly) UIView* backgoundView;
@end
@implementation BWView
@end
在類定義中使用到自己定義的類的時候椎侠,盡量不要在頭文件中引入自己定義的類的同文件,使用 @class 替換措拇。在實現(xiàn)文件中引入相應頭文件我纪。
例如:
//BWTest.h @class BWDataCenter; @interface BWTest : NSObject @property (nonatomic, strong) BWDataCenter* dataCenter; @end
//BWTest.m
import “BWDataCenter.h”
@implementation BWTest @end
如果一個類只是DTO(data transfer object),只是作為數(shù)據(jù)傳輸使用,可以不用引入使用的自定義的類的頭文件浅悉,只是用 @class 趟据,表明相應的自定義的類型。
對于DTO類型的對象仇冯,在給其成員變量設置值的時候可以考慮使用KVC之宿,實現(xiàn)下述函數(shù):
- (void) setValue:(id)value forKey:(NSString *)key
{
if([key isEqualToString:kRedDotAppInfoPath]) {
....
}elseif...
....
}
- (id) valueForKey:(NSString *)key {
....
}
點標記語法
屬性和冪等方法(多次調(diào)用和一次調(diào)用返回的結果相同)使用點標記語法訪問族操,其他的情況使用方括號標記語法苛坚。 良好的風格:
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
反面實例:
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
Cocoa相關
每個NSObject都有其生命周期,要在其生命周期的合適的時機做合適的事情色难。
例如:在初始化的時候泼舱,進行變量初始化,在銷毀的時候枷莉,銷毀變量等等娇昙。
盡量不要在界面布局的寫任何死數(shù)字
錯誤的示范:
CGFloat delta = SYSTEM_VERSION >= 7.0 ? 0.0f : -14.0f;
newFrame = CGRectMake(245 + delta,
(self.frame.size.height - tipNewSize.height)/2,
tipNewSize.width,
tipNewSize.height);
dotFrame = CGRectMake(258.0 + delta, (self.frame.size.height - tipDotSize.height)/2,
tipDotSize.width,
tipDotSize.height);
iconFrame = CGRectMake(245 + delta,
(self.frame.size.height - tipIconSize.height)/2,
tipIconSize.width,
tipIconSize.height);
numFrame = CGRectMake(245+delta, (self.frame.size.height - tipNumSize.height)/2, tipNumSize.width, tipNumSize.height);
正確的示范:
CGFloat cellHeight = CGRectGetHeight(self.frame);
CGFloat cellWidth = CGRectGetWidth(self.frame);
CGRect numFrame = CGRectZero;
numFrame.size = CGSizeMake(cellWidth,cellHeight);
...
布局時盡量使用相對布局,比如使用子View在父View中的相對位置笤妙。
在使用UITableView和UITableViewCell的時候一定要考慮到cell被復用的情況冒掌,在合適的時機對重用的cell進行清除操作。
為UITableViewCell功能或者子View的時候有限考慮子類化蹲盘。盡量不要使用在delegate中為Cell添加View股毫。子類化,利于Cell重用和對cell內(nèi)新添加的子View的布局召衔。
良好的風格示例:
@interface BWSettingCell : UITableViewCell
@property (nonatomic, strong, readonly) UIImageView* rightAppendImageView;
@end
@implementation BWSettingCell
- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [superinitWithStyle:style reuseIdentifier:reuseIdentifier];
if(!self) {
returnself;
}
_rightAppendImageView = [UIImageViewnew];
[self.contentView addSubview:_rightAppendImageView];
returnself;
}
- (void) layoutSubviews
{
[superlayoutSubviews];
CGSize rightImageSize = _rightAppendImageView.image.size;
_rightAppendImageView.frame = CGRectMake(CGRectGetWidth(self.frame) - rightImageSize.width,
(CGRectGetHeight(self.frame) - rightImageSize.height) /2,
rightImageSize.width,
rightImageSize.height);
}
@end
反面教材:
- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* const settingCellIdentify = @"settingCellIdentify";
UITableViewCell* cell = [self.tableView dequeueReusableCellWithIdentifier:settingCellIdentify];
if(!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:settingCellIdentify];
}
static int kSettingCellSubViewTag = 90001;
//非常錯誤的地方铃诬,盡量不要這樣寫
[cell.contentView removeAllSubviews];
UIImageView* rightAppendingView = [UIImageViewnew];
rightAppendingView.image = nil;
rightAppendingView.frame = CGRectMake(230, 8, 30, 30);
[cell.contentView addSubview:rightAppendingView];
returncell;
}
設計模式相關
使用設計模式的最基本原則,除非你明確知道自己要做件什么事情苍凛,而且知道使用特定設計模式帶來的影響趣席,否則不要刻意的使用設計模式。
單例模式
創(chuàng)建一個單例模式可以使用dispatch_once
+ (instancetype)defaultManager
{
if(!_defaultManager) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_defaultManager = [[FlappyEggManager alloc] init];
});
}
return_defaultManager;
}
觀察者模式
如果只是單純的傳遞數(shù)據(jù)醇蝴,不要使用觀察者模式宣肚,容易導致邏輯鏈斷裂。
參考資料
1.《Clean Code》
2.《編寫可閱讀代碼的藝術》
3.《Google Objective-C Style Guide》
4.《Introduction to Coding Guidelines for Cocoa》
5.《iOS應用開發(fā)最佳實踐系列一:編寫高質量的Objective-C代碼》
想要閱讀更多內(nèi)容悠栓,歡迎關注微信公共賬號IOS_Tips霉涨。