全國(guó)咨詢(xún)/投訴熱線(xiàn):400-618-4000

首頁(yè)技術(shù)文章正文

iOS培訓(xùn)之開(kāi)發(fā)代碼規(guī)范

更新時(shí)間:2017-04-13 來(lái)源:黑馬程序員iOS學(xué)院 瀏覽量:


總的來(lái)說(shuō), iOS命名兩大原則是:可讀性高和防止命名沖突(通過(guò)加前綴來(lái)保證). Objective-C 的命名通常都比較長(zhǎng), 名稱(chēng)遵循駝峰式命名法. 一個(gè)好的命名標(biāo)準(zhǔn)很簡(jiǎn)單, 就是做到在開(kāi)發(fā)者一看到名字時(shí), 就能夠懂得它的含義和使用方法. 另外, 每個(gè)模塊都要加上自己的前綴, 前綴在編程接口中非常重要, 可以區(qū)分軟件的功能范疇并防止不同文件或者類(lèi)之間命名發(fā)生沖突, 比如相冊(cè)模塊(PhotoGallery)的代碼都以PG作為前綴: PGAlbumViewController, PGDataManager.
1). 常量的命名
對(duì)于常量的命名最好在前面加上字母k作為標(biāo)記. 如:
1 static const NSTimeInterval kAnimationDuration = 0.3;
定義作為NSDictionary或者Notification等的Key值字符串時(shí)加上const關(guān)鍵字, 以防止被修改. 如:
1 NSString *const UIApplicationDidEnterBackgroundNotification
Tips:
I. 若常量作用域超出編譯單元(實(shí)現(xiàn)文件), 需要在類(lèi)外可見(jiàn)時(shí), 使用extern關(guān)鍵字, 并加上該類(lèi)名作為前綴. 如 extern NSString *const PGThumbnailSize
II.全局常量(通知或者關(guān)鍵字等)盡量用const來(lái)定義. 因?yàn)槿绻褂煤甓x, 一來(lái)宏可能被重定義. 二來(lái)引用不同的文件可能會(huì)導(dǎo)致宏的不同. P.S. 對(duì)于#define也添加一下前綴k(強(qiáng)迫癥, 哈哈...)
2). 枚舉的命名
對(duì)于枚舉類(lèi)型, 經(jīng)常會(huì)看到之前的C的定義方式:
1
2
3
4
5
typedef enum : {
    CameraModeFront,
    CameraModeLeft,
    CameraModeRight,
} CameraMode;
不知道是腫么了, 每次看到這種定義方式總是感覺(jué)怪怪的, 作為一個(gè)正宗的iOS開(kāi)發(fā)者當(dāng)然要以O(shè)bjective-C的方式來(lái)定義啦, 哈哈... IOS培訓(xùn),那Objective-C是怎么定義的呢? 很簡(jiǎn)單, 到SDK里面看看Apple是怎么做滴:
1
2
3
4
5
6
7
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};
這邊需要注意的是: 枚舉類(lèi)型命名要加相關(guān)類(lèi)名前綴并且枚舉值命名要加枚舉類(lèi)型前綴.
3). 變量和對(duì)象的命名
給 一個(gè)對(duì)象命名時(shí)建議采用修飾+類(lèi)型的方式. 如果只用修飾命名會(huì)引起歧義, 比如title (這個(gè)到底是個(gè)NSString還是UILabel?). 同樣的, 如果只用類(lèi)型來(lái)命名則會(huì)缺失作用信息, 比如label (好吧, 我知道你是個(gè)UILabel, 但是我不知道它是用來(lái)做什么的呀?). So, 正確的命名方式為:
1
2
titleLabel    //表示標(biāo)題的label,  是UILabel類(lèi)型
confirmButton //表示確認(rèn)的button, 是UIButton類(lèi)型
對(duì) 于BOOL類(lèi)型, 應(yīng)加上is前綴, 比如- (BOOL)isEqualToString:(NSString *)aString這樣會(huì)更加清晰. 如果某方法返回非屬性的 BOOL 值, 那么應(yīng)根據(jù)其功能, 選用 has 或 is 當(dāng)前綴, 如- (BOOL)hasPrefix:(NSString *)aString
Tip: 如果某個(gè)命名已經(jīng)很明確了, 為了簡(jiǎn)潔可以省去類(lèi)型名. 比如scores, 很明顯是個(gè)array了, 就不必命名成scoreArray了
編碼規(guī)范
編碼規(guī)范簡(jiǎn)單來(lái)說(shuō)就是為了保證寫(xiě)出來(lái)的代碼具備三個(gè)原則:可復(fù)用, 易維護(hù), 可擴(kuò)展. 這其實(shí)也是面向?qū)ο蟮幕驹瓌t. 可復(fù)用, 簡(jiǎn)單來(lái)說(shuō)就是不要寫(xiě)重復(fù)的代碼, 有重復(fù)的部分要盡量封裝起來(lái)重用. 否則修改文件的時(shí)候得滿(mǎn)地找相同邏輯的地方...這個(gè)就用no zuo no die來(lái)描述吧, 哈哈...易維護(hù), 就是不要把代碼復(fù)雜化, 不要去寫(xiě)巨復(fù)雜邏輯的代碼, 而是把復(fù)雜的邏輯代碼拆分開(kāi)一個(gè)個(gè)小的模塊, 這也是Do one thing的概念, 每個(gè)模塊(或者函數(shù))職責(zé)要單一, 這樣的代碼會(huì)易于維護(hù), 也不容易出錯(cuò). 可擴(kuò)展則是要求寫(xiě)代碼時(shí)要考慮后面的擴(kuò)展需求, 這個(gè)屬于架構(gòu)層面的東東, 利用對(duì)應(yīng)的設(shè)計(jì)模式來(lái)保證, 后面有機(jī)會(huì)單獨(dú)寫(xiě)文探討。
編碼規(guī)范直接通過(guò)示例來(lái)介紹, 畢竟對(duì)于程序員來(lái)說(shuō)一個(gè)Demo勝過(guò)千行文字(有同感的小伙伴讓我看到你們的雙手, 哈哈O(∩_∩)O~~). 下面的部分示例選自richieyang博文, 寫(xiě)的很好的一篇文章, 推薦大家看一下, 我自己也是受益匪淺.
1). 判斷nil或者YES/NO
Preferred:
1
2
if (someObject) { ... } 
if (!someObject) { ... }
Not preferred:
1
2
if (someObject == YES) { ...} 
if (someObject != nil) { ...}
if (someObject == YES)容易誤寫(xiě)成賦值語(yǔ)句, 自己給自己挖坑了...而且if (someObject)寫(xiě)法很簡(jiǎn)潔, 何樂(lè)而不為呢?
2). 條件賦值
Preferred:
1 result = object ? : [self createObject];
Not preferred:
1 result = object ? object : [self createObject];
如果是存在就賦值本身, 那就可以這樣簡(jiǎn)寫(xiě), 多簡(jiǎn)潔啊, 哈哈...
3). 初始化方法
Preferred:
1
2
3
4
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
第一個(gè)好處還是簡(jiǎn)潔, 第二個(gè)好處是可以防止初始化進(jìn)去nil值造成crash
4). 定義屬性
Preferred:
1 @property (nonatomic, readwrite, copy) NSString *name;
建議定義屬性的時(shí)候把所有的參數(shù)寫(xiě)全, 尤其是如果想定義成只讀的(防止外面修改)那一定要加上readonly, 這也是代碼安全性的一個(gè)習(xí)慣.
如果是內(nèi)部使用的屬性, 那么就定義成私有的屬性(定義到.m的class extension里面)
對(duì)于擁有Mutable子類(lèi)型的對(duì)象(e.g. NSString, NSArray, NSDictionary)一定要定義成copy屬性. Why? 示例: NSArray的array = NSMutableArray的mArray; 如果mArray在某個(gè)地方改變了, 那array也會(huì)跟著改變. So, make sense?
盡量不要暴露mutable類(lèi)型的對(duì)象在public interface, 建議在.h定義一個(gè)Inmutable類(lèi)型的屬性, 然后在.m的get函數(shù)里面返回一個(gè)內(nèi)部定義的mutable變量. Why? For security as well!
5). BOOL賦值
Preferred:
1 BOOL isAdult = age > 18;
Not preferred:
1
2
3
4
5
6
7
8
9
BOOL isAdult;
if (age > 18)
{
    isAdult = YES;
}
else
{
    isAdult = NO;
}
為什么要這么寫(xiě)呢, 我不告訴你, 哈哈哈...
6) 拒絕死值
Preferred:
1
2
3
if (car == Car.Nissan)
or
const int adultAge = 18; if (age > adultAge) { ... }
Not preferred:
1
2
3
if (carName == "Nissan")
or
if (age > 18) { ... }
死值每次修改的時(shí)候容易被遺忘, 地方多了找起來(lái)就悲劇了. 而且定義成枚舉或者static可以讓錯(cuò)誤發(fā)生在編譯階段. 另外僅僅看到一個(gè)數(shù)字, 完全不知道這個(gè)數(shù)字代表的意義. 納尼?
7). 復(fù)雜的條件判斷
Preferred:
1
2
3
4
5
6
7
8
9
10
11
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:
1
2
3
4
5
6
7
if (job.JobState == JobState.New
    || job.JobState == JobState.Submitted
    || job.JobState == JobState.Expired
    || (job.JobTitle && job.JobTitle.length))
{
    //....
}
清晰明了, 每個(gè)函數(shù)DO ONE THING!
8). 嵌套判斷
Preferred:
1
2
3
4
5
if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;
 
return YES;
Not preferred:
1
2
3
4
5
6
7
8
9
10
11
12
BOOL isValid = NO;
if (user.UserName)
{
 
    if (user.Password)
    {
     
        if (user.Email) isValid = YES;
    }
     
}
return isValid;
一旦發(fā)現(xiàn)某個(gè)條件不符合, 立即返回, 條理更清晰
9). 參數(shù)過(guò)多
Preferred:
1
2
3
4
5
6
- (void)registerUser(User *user)
{
 
     // to do...
      
}
Not preferred:
1
2
3
4
5
6
- (void)registerUserName:(NSString *)userName
                password:(NSString *)password 
                   email:(NSString *)email
{
     // to do...
}
當(dāng)發(fā)現(xiàn)實(shí)現(xiàn)某一功能需要傳遞的參數(shù)太多時(shí), 就預(yù)示著你應(yīng)該聚合成一個(gè)model類(lèi)了...這樣代碼更整潔, 也不容易因?yàn)閰?shù)太多導(dǎo)致出錯(cuò)。
10). 回調(diào)方法
Preferred:
1 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
函數(shù)調(diào)用的可知性, 回調(diào)時(shí)被調(diào)用者要知道其調(diào)用者, 方便信息的傳遞, 所以建議在回調(diào)方法中第一個(gè)參數(shù)中加上調(diào)用者。
Well, 不知不覺(jué)已經(jīng)整理了10個(gè)了, 額, 太多了, 不知道童鞋們還有木有耐心看了, 好吧, 這一段就到此為止吧, 下面寫(xiě)一下block的編碼規(guī)范, 各位看官, 預(yù)知后事如何, 且繼續(xù)look look, 哈哈...
Block的循環(huán)引用問(wèn)題

Block確實(shí)是個(gè)好東西, 但是用起來(lái)一定要注意循環(huán)引用的問(wèn)題, 否則一不小心你就會(huì)發(fā)現(xiàn), Oh My God, 我的dealloc腫木不走了...
1
2
3
4
5
6
__weak typeof(self) weakSelf = self;
dispatch_block_t block =  ^{
    [weakSelf doSomething]; // weakSelf != nil
    // preemption, weakSelf turned nil
    [weakSelf doSomethingElse]; // weakSelf == nil
};
如 此在上面定義一個(gè)weakSelf, 然后在block體里面使用該weakSelf就可以避免循環(huán)引用的問(wèn)題. 那么問(wèn)題來(lái)了...是不是這樣就完全木有問(wèn)題了? 很不幸, 答案是NO, 還是有問(wèn)題。問(wèn)題是block體里面的self是weak的, 所以就有可能在某一個(gè)時(shí)段self已經(jīng)被釋放了, 這時(shí)block體里面再使用self那就是nil, 然后...然后就悲劇了...那么腫么辦呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
__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;
         
    }
};
解決方法很簡(jiǎn)單, 就是在block體內(nèi)define一個(gè)strong的self, 然后執(zhí)行的時(shí)候判斷下self是否還在, 如果在就繼續(xù)執(zhí)行下面的操作, 否則return或拋出異常.
什么情況下會(huì)出現(xiàn)block里面self循環(huán)引用的問(wèn)題? 這個(gè)問(wèn)題問(wèn)的好, 哈哈...簡(jiǎn)單來(lái)說(shuō)就是雙邊引用, 如果block是self類(lèi)的property (此時(shí)self已經(jīng)retain了block), 然后在block內(nèi)又引用了self, 這個(gè)情況下就肯定會(huì)循環(huán)引用了...
P.S. RAC里面有定義好的@weakify(self)和@strongify(self), 用起來(lái)灰?;页5姆奖? 勸君嘗試一下^_^
那些年遇到的Crash

  • 多線(xiàn)程同步問(wèn)題造成的Crash
這個(gè)其實(shí)還蠻常見(jiàn)的, 尤其是在多線(xiàn)程泛濫使用的今天...你可以使用多線(xiàn)程, 但你要知道保護(hù)它呀, 哈哈. 對(duì)于數(shù)據(jù)源或model類(lèi)一定要注意多線(xiàn)程同時(shí)訪(fǎng)問(wèn)的情況, 我個(gè)人比較喜歡用GCD的串行隊(duì)列來(lái)同步線(xiàn)程.
  • Observer的移除
現(xiàn)在的代碼里面很多需要用到Observer, 根據(jù)被觀察對(duì)象的狀態(tài)來(lái)相應(yīng)的Update UI或者執(zhí)行某個(gè)操作. 注冊(cè)observer很簡(jiǎn)單, 但是移除的時(shí)候就出問(wèn)題了, 要么是忘記移除observer了, 要么是移除的時(shí)機(jī)不對(duì). 如果某個(gè)被觀察對(duì)象已經(jīng)被釋放了, observer還在, 那結(jié)果只能是crash了, 所以切記至少在dealloc里面移除一下observer...
  • NSArray, NSDictionary成員的判空保護(hù)
在addObject或insertObject到NSArray或者NSDictionary時(shí)最好加一下判空保護(hù), 尤其是網(wǎng)絡(luò)相關(guān)的邏輯, 如果網(wǎng)絡(luò)返回為空(jason解析出來(lái)為空), 但你還是毅然決然的add到array里面, 那么...
最后一點(diǎn)就是commit代碼之前一定要保證木有warning, 木有內(nèi)存泄露, 確保都OK之后再上傳代碼. 其實(shí)很簡(jiǎn)單, 上傳代碼之前Command + Shift + B靜態(tài)分析一下, 看看有木有什么issue...就先寫(xiě)這么多吧, 以后遇到更多的坑后, 我一定會(huì)爬出來(lái)再過(guò)來(lái)補(bǔ)充的, to be continued...


本文版權(quán)歸黑馬程序員ios培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明作者出處。謝謝!
作者:黑馬程序員ios培訓(xùn)學(xué)院
首發(fā):http://pantone-color.com.cn/news/ios.html
 
分享到:
在線(xiàn)咨詢(xún) 我要報(bào)名
和我們?cè)诰€(xiàn)交談!