一徒河、代碼組織
1. 方法分布
在函數(shù)分組和protocol/delegate實現(xiàn)中使用#pragma mark 來分類方法宙地,要遵循以下一般結(jié)構(gòu):
#pragma mark- Life Cycle //類的生命周期相關(guān)的方法,如viewWillAppear等喧兄。
#pragma mark- Public Method //對外的接口无畔,公有方法。
#pragma mark- Delegate //代理方法吠冤;在下面用 #pragma mark UITableViewDelegate 等來區(qū)分不同控件的代理方法檩互,這里建議,自己控件的代碼方法寫在前面咨演,系統(tǒng)控件的代碼方法寫在靠后的位置
#pragma mark- Event Response //按鈕響應(yīng)事件闸昨、通知方法、定時器方法等薄风。
#pragma mark- Getters and Setters //Get 和 Set 方法
#pragma mark- Private Method //私有方法饵较,方法封裝等。
Tips:
- 總體的原則是方法/代碼量可能比較多的塊放在后面遭赂,如私有方法一般都最多的循诉。
- 使用Snippets來保證一致
替換地址:~/Library/Developer/Xcode/UserData/CodeSnippets
2. 一些原則
<1> 行數(shù)限制:一個方法最多不能超過50行, 一個文件原則上不能超過1000行撇他,超過行數(shù)要考慮封裝茄猫。
<2> Storyboard/純代碼:建議比較復雜的視圖使用純代碼來進行創(chuàng)建和布局,比較簡單的視圖使用Storyboard或Xib來進行布局困肩。這樣做的原因是復雜視圖使用Storyboard看起來會很復雜划纽,而且修改維護起來也不方便。
Tips: 在使用autoLayout布局時锌畸,可以把布局代碼寫在viewDidLoad里面勇劣,但是如果是用frame 布局,在viewDidLoad里面布局就很容易出問題。一般是放在viewWillAppear里面比默。
<3> 視圖初始化:盡量使用getter的方法來進行視圖的初始化幻捏,而是都集中在initView中。
<4> 盡量少的寫private method,盡量把一些通用的代碼命咐,用類目或者工具類的方式獨立出來篡九,方便后面的修改和調(diào)試。
<5> 不要在 init 和 dealloc 及getter 和setter方法中使用 self.property 的方式來訪問成員變量醋奠。
3. XCode 工程
物理文件應(yīng)該與Xcode工程文件保持同步榛臼,任何Xcode分組的創(chuàng)建應(yīng)該在文件系統(tǒng)的文件體現(xiàn)。
二钝域、命名規(guī)范
總的來說, iOS命名兩大原則是:可讀性高和防止命名沖突. Objective-C 的命名通常都比較長, 名稱遵循駝峰式命名法. 一個好的命名標準很簡單, 就是做到在開發(fā)者一看到名字時, 就能夠懂得它的含義和使用方法.
1. 常量的命名
常量是容易重復被使用和無需通過查找和代替就能快速修改值讽坏。常量應(yīng)該使用static來聲明而不是使用#define锭魔,除非顯式地使用宏例证。
對于常量的命名最好在前面加上字母k作為標記. 如:
static const NSTimeInterval kAnimationDuration = 0.3;
定義作為NSDictionary或者Notification等的Key值字符串時加上const關(guān)鍵字, 以防止被修改. 如:
NSString *const UIApplicationDidEnterBackgroundNotification
Tips:
I. 若常量作用域超出編譯單元(實現(xiàn)文件), 需要在類外可見時, 使用extern
關(guān)鍵字, 并加上該類名作為前綴. 如 extern NSString *const PGThumbnailSize
II. 全局常量(通知或者關(guān)鍵字等)盡量用const來定義. 因為如果使用宏定義, 一來宏可能被重定義. 二來引用不同的文件可能會導致宏的不同. 另外,對于#define也添加一下前綴k
2. 枚舉的命名
對于枚舉類型, 經(jīng)常會看到下面的定義方式:
typedef enum : NSUInteger {
NSEnumScanQRCodeManul = 1, //手動添加頁面打開二維碼掃描頁面
NSEnumScanQRCodeDeviceList = 2, //直接打開二維碼掃描頁面
NSEnumScanQRCodePasswd = 3, //設(shè)備找回密碼顯示密碼界面
} NSEnumScanQRCodeType;
typedef enum {
PTZ_CONTROL_STYLE_None, //默認值:正常
PTZ_CONTROL_STYLE_Zoom, //變倍
PTZ_CONTROL_STYLE_Focus, //聚焦
PTZ_CONTROL_STYLE_Preset, //預置位
PTZ_CONTROL_STYLE_Unknown, //未知
} PTZ_CONTROL_STYLE;
首先迷捧,兩種都用的是C的定義方法织咧。在命名上,第一種方法漠秋,使用NS做為前綴笙蒙,容易被誤解為系統(tǒng)的定義,而且Enum這個單詞我覺得沒有必要庆锦;第二種捅位,感覺怪怪的,像是一個宏定義一樣搂抒。
作為一個正宗的iOS開發(fā)者艇搀,當然要以O(shè)bjective-C的方式來定義,看下系統(tǒng)是怎么定義的:
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone,
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
這邊需要注意的是: 枚舉類型命名要加相關(guān)類名前綴并且枚舉值命名要加枚舉類型前綴.
3. 變量和對象的命名
給一個對象命名時建議采用修飾+類型的方式. 如果只用修飾命名會引起歧義, 比如title (這個到底是個NSString還是UILabel?). 同樣的, 如果只用類型來命名則會缺失作用信息, 比如label (好吧, 我知道你是個UILabel, 但是我不知道它是用來做什么的呀?). So, 正確的命名方式為:
titleLabel //表示標題的label, 是UILabel類型
confirmButton //表示確認的button, 是UIButton類型
對于BOOL類型, 應(yīng)加上is前綴, 比如- (BOOL)isEqualToString:(NSString *)aString
這樣會更加清晰. 如果某方法返回非屬性的 BOOL 值, 那么應(yīng)根據(jù)其功能, 選用 has/is/should 當前綴, 如- (BOOL)hasPrefix:(NSString *)aString
4. 下劃線
當使用屬性時求晶,實例變量應(yīng)該使用self.來訪問和改變焰雕。這就意味著所有屬性將會視覺效果不同,因為它們前面都有self.芳杏。
私有變量應(yīng)該盡可能代替實例變量的使用矩屁。盡管使用實例變量是一種有效的方式,但更偏向于使用屬性來保持代碼一致性爵赵。
局部變量不應(yīng)該包含下劃線吝秕。
應(yīng)該使用下劃線訪問屬性的位置:
<1> getter 和 setter方法中
<2> init 和 dealloc方法,原因可以參考:文章
三空幻、編碼規(guī)范
編碼規(guī)范簡單來說就是為了保證寫出來的代碼具備三個原則:可復用, 易維護, 可擴展. 這其實也是面向?qū)ο蟮幕驹瓌t. 可復用, 簡單來說就是不要寫重復的代碼, 有重復的部分要盡量封裝起來重用. 否則修改文件的時候得滿地找相同邏輯的地方...這個就用no zuo no die來描述吧, 哈哈...易維護, 就是不要把代碼復雜化, 不要去寫巨復雜邏輯的代碼, 而是把復雜的邏輯代碼拆分開一個個小的模塊, 這也是Do one thing的概念, 每個模塊(或者函數(shù))職責要單一, 這樣的代碼會易于維護, 也不容易出錯. 可擴展則是要求寫代碼時要考慮后面的擴展需求郭膛。
編碼規(guī)范直接通過示例來介紹, “Talk is cheap, show me the code”.
1. 判斷nil或者YES/NO
Preferred:
if (someObject) { ... }
if (!someObject) { ... }
Not preferred:
if (someObject == YES) { ...}
if (someObject != nil) { ...}
if (someObject == YES)容易誤寫成賦值語句, 自己給自己挖坑了...而且if (someObject)寫法很簡潔, 何樂而不為呢?
OC中BOOL定義
2. BOOL賦值
Preferred:
BOOL isAdult = (age > 18);
Not preferred:
BOOL isAdult;
if (age > 18) {
isAdult = YES;
} else{
isAdult = NO;
}
3. 魔鬼數(shù)字
Preferred:
if (car == Car.Nissan)
or
const int adultAge = 18; if (age > adultAge) { ... }
Not preferred:
if (carName == "Nissan")
or
if (age > 18) { ... }
魔鬼數(shù)字每次修改的時候容易被遺忘, 地方多了找起來就悲劇了. 而且定義成枚舉或者static可以讓錯誤發(fā)生在編譯階段. 另外僅僅看到一個數(shù)字, 完全不知道這個數(shù)字代表的意義. 納尼?
4. 復雜的條件判斷
Preferred:
if ([self canDeleteJob:job]) { ... }
- (BOOL)canDeleteJob:(Job *)job {
BOOL invalidJobState = (job.JobState == JobState.New
|| job.JobState == JobState.Submitted
|| job.JobState == JobState.Expired);
BOOL invalidJob = (job.JobTitle && job.JobTitle.length);
return invalidJobState || invalidJob;
}
Not preferred:
if (job.JobState == JobState.New
|| job.JobState == JobState.Submitted
|| job.JobState == JobState.Expired
|| (job.JobTitle && job.JobTitle.length)) {
//....
}
清晰明了, 每個函數(shù)DO ONE THING!
5. 嵌套判斷
Preferred:
if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;
return YES;
Not preferred:
BOOL isValid = NO;
if (user.UserName) {
if (user.Password) {
if (user.Email) isValid = YES;
}
}
return isValid;
一旦發(fā)現(xiàn)某個條件不符合, 立即返回, 條理更清晰
6. 參數(shù)過多
Preferred:
- (void)registerUser(User *user) {
// to do...
}
Not preferred:
- (void)registerUserName:(NSString *)userName
password:(NSString *)password
email:(NSString *)email {
// to do...
}
當發(fā)現(xiàn)實現(xiàn)某一功能需要傳遞的參數(shù)太多時, 就預示著你應(yīng)該聚合成一個model類了...這樣代碼更整潔, 也不容易因為參數(shù)太多導致出錯
7. 回調(diào)方法
Preferred:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
函數(shù)調(diào)用的可知性, 回調(diào)時被調(diào)用者要知道其調(diào)用者, 方便信息的傳遞, 所以建議在回調(diào)方法中第一個參數(shù)中加上調(diào)用者
8. Block的循環(huán)引用問題
Block確實是個好東西, 但是用起來一定要注意循環(huán)引用的問題, 否則可能會引起內(nèi)存泄露。
__weak typeof(self) weakSelf = self;
dispatch_block_t block = ^{
[weakSelf doSomething]; // weakSelf != nil
// preemption, weakSelf turned nil
[weakSelf doSomethingElse]; // weakSelf == nil
};
可以用下面這種方式:
__weak typeof(self) weakSelf = self;
myObj.myBlock = ^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomething]; // strongSelf != nil
// preemption, strongSelf still not nil
[strongSelf doSomethingElse]; // strongSelf != nil
}else {
// Probably nothing...
return;
}
};
解決方法就是在block體內(nèi)define一個strong的self, 然后執(zhí)行的時候判斷下self是否還在, 如果在就繼續(xù)執(zhí)行下面的操作, 否則return或拋出異常.這樣可能有點麻煩氛悬,所以可以自定義一個宏:
@weakify(object)
@strongify(object)
#ifndef weakify
#if DEBUG
#if __has_feature(objc_arc)
#define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) autoreleasepool{} __block __typeof__(object) block##_##object = object;
#endif
#else
#if __has_feature(objc_arc)
#define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) try{} @finally{} {} __block __typeof__(object) block##_##object = object;
#endif
#endif
#endif
#ifndef strongify
#if DEBUG
#if __has_feature(objc_arc)
#define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object;
#else
#define strongify(object) autoreleasepool{} __typeof__(object) object = block##_##object;
#endif
#else
#if __has_feature(objc_arc)
#define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object;
#else
#define strongify(object) try{} @finally{} __typeof__(object) object = block##_##object;
#endif
#endif
#endif
9. 方法大括號和其他大括號(if/else/switch/while 等.)總是在同一行語句打開但在新行中關(guān)閉则剃。
Prefered:
if (user.isHappy) {
//Do something
} else {
//Do something else
}
Noet Prefered:
if (user.isHappy)
{
//Do something
} else
{
//Do something else
}
10. 注釋
當需要注釋時耘柱,注釋應(yīng)該用來解釋這段特殊代碼為什么要這樣做。任何被使用的注釋都必須保持最新或被刪除棍现。
11. 日志
日志的主要作用是幫助我們快速定位問題调煎,特別是網(wǎng)上問題。
打印日志時的一些規(guī)范:
1> 分級
Debug (在線上環(huán)境不會打印) ELYTDLog
可以打印任何自己覺得有幫助的內(nèi)容己肮。
Info ELYTILog
業(yè)務(wù)關(guān)鍵節(jié)點打印士袄。如調(diào)用接口(關(guān)鍵參數(shù)也要打印)谎僻,請求成功娄柳、請求失敗等。
Error ELYTELog
錯誤日志艘绍,嚴重或會導致業(yè)務(wù)不能正常使用的異常赤拒;
2> 注意點
- 后續(xù)在代碼中不要再出現(xiàn)NSLog、printf诱鞠,所有日志都要歸到某一級中挎挖。
- 頻繁打印一般情況下只能使用 Debug 級別。
12. 斷言的使用
使用斷言主要是為了保持代碼的健壯性航夺,在發(fā)生絕對不應(yīng)該出現(xiàn)的錯誤時能夠及時發(fā)現(xiàn)和處理蕉朵。
斷言為真,則表明程序運行正常阳掐;斷言為假始衅,則意味著它已經(jīng)在代碼中發(fā)現(xiàn)意料之外的錯誤。
NSAssert(condition, desc, ...)
斷言使用的原則
- 用錯誤處理代碼來處理預期會發(fā)生的狀況缭保,用斷言來處理絕不應(yīng)該發(fā)生的狀況
-
斷言在發(fā)布環(huán)境中不會被執(zhí)行汛闸,要避免把執(zhí)行的代碼放到斷言中
如
NSAssert([self performAction], @"could't perform);
這樣寫就很危險,應(yīng)該這樣寫:
Bool performed=[self performAction];
NSAssert(performed, @"could't perform);
13. 空行的使用
1涮俄、 方法實現(xiàn)內(nèi)部蛉拙,原則上不以空行分隔,如有需要使用方法封裝來區(qū)分不同功能實現(xiàn)彻亲。
三孕锄、其他注意點
1、多語言
1)目前產(chǎn)品的本地化策略為苞尝,如果有對應(yīng)語言的翻譯畸肆,則使用中文,如果沒有則顯示英文宙址。故在新增多語言字段時轴脐,對未提供多語言的語言項,需要使用英文代替,不能不添加此字段大咱。
2)為了防止字段遺漏恬涧,在使用release方式編譯時,會使用腳本對所有多語言字段進行檢查碴巾,如果對多語言有改動溯捆,建議在修改完成后,使用release模式進行編譯厦瓢,對多語言進行檢查提揍。
2、Xib煮仇、Storyborad中l(wèi)eading/trailing的處理
由于目前的leading/trailing約束對于阿拉伯文等從右到左語言習慣的顯示會有一定問題劳跃,故所有相關(guān)約束需要使用left/right來替代。
與上面多語言處理類似浙垫,在使用release方式進行編譯時刨仑,也會進行此項檢查,建議控件改動較多時進行檢查绞呈。