命名規範程序員
總的來講, iOS命名兩大原則是:可讀性高和防止命名衝突(經過加前綴來保證). Objective-C 的命名一般都比較長, 名稱遵循駝峯式命名法. 一個好的命名標準很簡單, 就是作到在開發者一看到名字時, 就可以懂得它的含義和使用方法. 另外, 每一個模塊都要加上本身的前綴, 前綴在編程接口中很是重要, 能夠區分軟件的功能範疇並防止不一樣文件或者類之間命名發生衝突, 好比相冊模塊(PhotoGallery)的代碼都以PG做爲前綴: PGAlbumViewController, PGDataManager.編程
1). 常量的命名設計模式
對於常量的命名最好在前面加上字母k做爲標記. 如:安全
1
|
static const NSTimeInterval kAnimationDuration = 0.3;
|
定義做爲NSDictionary或者Notification等的Key值字符串時加上const關鍵字, 以防止被修改. 如:網絡
1
|
NSString *const UIApplicationDidEnterBackgroundNotification
|
Tips:多線程
I. 若常量做用域超出編譯單元(實現文件), 須要在類外可見時, 使用extern關鍵字, 並加上該類名做爲前綴. 如 extern NSString *const PGThumbnailSize架構
II.全局常量(通知或者關鍵字等)儘可能用const來定義. 由於若是使用宏定義, 一來宏可能被重定義. 二來引用不一樣的文件可能會致使宏的不一樣. P.S. 對於#define也添加一下前綴k(強迫症, 哈哈...)函數
2). 枚舉的命名ui
對於枚舉類型, 常常會看到以前的C的定義方式:編碼
1
2
3
4
5
|
typedef enum : {
CameraModeFront,
CameraModeLeft,
CameraModeRight,
} CameraMode;
|
不知道是腫麼了, 每次看到這種定義方式老是感受怪怪的, 做爲一個正宗的iOS開發者固然要以Objective-C的方式來定義啦, 哈哈... 那Objective-C是怎麼定義的呢? 很簡單, 到SDK裏面看看Apple是怎麼作滴:
1
2
3
4
5
6
7
|
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone,
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
|
這邊須要注意的是: 枚舉類型命名要加相關類名前綴而且枚舉值命名要加枚舉類型前綴.
3). 變量和對象的命名
給一個對象命名時建議採用修飾+類型的方式. 若是隻用修飾命名會引發歧義, 好比title (這個究竟是個NSString仍是UILabel?). 一樣的, 若是隻用類型來命名則會缺失做用信息, 好比label (好吧, 我知道你是個UILabel, 可是我不知道它是用來作什麼的呀?). So, 正確的命名方式爲:
1
2
|
titleLabel
//表示標題的label, 是UILabel類型
confirmButton
//表示確認的button, 是UIButton類型
|
對於BOOL類型, 應加上is前綴, 好比- (BOOL)isEqualToString:(NSString *)aString這樣會更加清晰. 若是某方法返回非屬性的 BOOL 值, 那麼應根據其功能, 選用 has 或 is 當前綴, 如- (BOOL)hasPrefix:(NSString *)aString
Tip: 若是某個命名已經很明確了, 爲了簡潔能夠省去類型名. 好比scores, 很明顯是個array了, 就沒必要命名成scoreArray了
編碼規範
編碼規範簡單來講就是爲了保證寫出來的代碼具有三個原則:可複用, 易維護, 可擴展. 這其實也是面向對象的基本原則. 可複用, 簡單來講就是不要寫重複的代碼, 有重複的部分要儘可能封裝起來重用. 不然修改文件的時候得滿地找相同邏輯的地方...這個就用no zuo no die來描述吧, 哈哈...易維護, 就是不要把代碼複雜化, 不要去寫巨複雜邏輯的代碼, 而是把複雜的邏輯代碼拆分開一個個小的模塊, 這也是Do one thing的概念, 每一個模塊(或者函數)職責要單一, 這樣的代碼會易於維護, 也不容易出錯. 可擴展則是要求寫代碼時要考慮後面的擴展需求, 這個屬於架構層面的東東, 利用對應的設計模式來保證, 後面有機會單獨寫文探討。
編碼規範直接經過示例來介紹, 畢竟對於程序員來講一個Demo賽過千行文字(有同感的小夥伴讓我看到大家的雙手, 哈哈O(∩_∩)O~~). 下面的部分示例選自richieyang博文, 寫的很好的一篇文章, 推薦你們看一下, 我本身也是受益不淺.
1). 判斷nil或者YES/NO
Preferred:
1
2
|
if
(someObject) { ... }
if
(!someObject) { ... }
|
Not preferred:
1
2
|
if
(someObject == YES) { ...}
if
(someObject != nil) { ...}
|
if (someObject == YES)容易誤寫成賦值語句, 本身給本身挖坑了...並且if (someObject)寫法很簡潔, 何樂而不爲呢?
2). 條件賦值
Preferred:
1
|
result = object ? : [self createObject];
|
Not preferred:
1
|
result = object ? object : [self createObject];
|
若是是存在就賦值自己, 那就能夠這樣簡寫, 多簡潔啊, 哈哈...
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;
|
第一個好處仍是簡潔, 第二個好處是能夠防止初始化進去nil值形成crash
4). 定義屬性
Preferred:
1
|
@property (nonatomic, readwrite, copy) NSString *name;
|
建議定義屬性的時候把全部的參數寫全, 尤爲是若是想定義成只讀的(防止外面修改)那必定要加上readonly, 這也是代碼安全性的一個習慣.
若是是內部使用的屬性, 那麼就定義成私有的屬性(定義到.m的class extension裏面)
對於擁有Mutable子類型的對象(e.g. NSString, NSArray, NSDictionary)必定要定義成copy屬性. Why? 示例: NSArray的array = NSMutableArray的mArray; 若是mArray在某個地方改變了, 那array也會跟着改變. So, make sense?
儘可能不要暴露mutable類型的對象在public interface, 建議在.h定義一個Inmutable類型的屬性, 而後在.m的get函數裏面返回一個內部定義的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;
}
|
爲何要這麼寫呢, 我不告訴你, 哈哈哈...
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) { ... }
|
死值每次修改的時候容易被遺忘, 地方多了找起來就悲劇了. 並且定義成枚舉或者static可讓錯誤發生在編譯階段. 另外僅僅看到一個數字, 徹底不知道這個數字表明的意義. 納尼?
7). 複雜的條件判斷
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))
{
//....
}
|
清晰明瞭, 每一個函數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;
|
一旦發現某個條件不符合, 當即返回, 條理更清晰
9). 參數過多
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...
}
|
當發現實現某一功能須要傳遞的參數太多時, 就預示着你應該聚合成一個model類了...這樣代碼更整潔, 也不容易由於參數太多致使出錯。
10). 回調方法
Preferred:
1
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
|
函數調用的可知性, 回調時被調用者要知道其調用者, 方便信息的傳遞, 因此建議在回調方法中第一個參數中加上調用者。
Well, 不知不覺已經整理了10個了, 額, 太多了, 不知道童鞋們還有木有耐心看了, 好吧, 這一段就到此爲止吧, 下面寫一下block的編碼規範, 各位看官, 預知後事如何, 且繼續look look, 哈哈...
Block的循環引用問題
Block確實是個好東西, 可是用起來必定要注意循環引用的問題, 不然一不當心你就會發現, 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
};
|
如此在上面定義一個weakSelf, 而後在block體裏面使用該weakSelf就能夠避免循環引用的問題. 那麼問題來了...是否是這樣就徹底木有問題了? 很不幸, 答案是NO, 仍是有問題。問題是block體裏面的self是weak的, 因此就有可能在某一個時段self已經被釋放了, 這時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
;
}
};
|
解決方法很簡單, 就是在block體內define一個strong的self, 而後執行的時候判斷下self是否還在, 若是在就繼續執行下面的操做, 不然return或拋出異常.
什麼狀況下會出現block裏面self循環引用的問題? 這個問題問的好, 哈哈...簡單來講就是雙邊引用, 若是block是self類的property (此時self已經retain了block), 而後在block內又引用了self, 這個狀況下就確定會循環引用了...
P.S. RAC裏面有定義好的@weakify(self)和@strongify(self), 用起來灰常灰常的方便, 勸君嘗試一下^_^
那些年遇到的Crash
多線程同步問題形成的Crash
這個其實還蠻常見的, 尤爲是在多線程氾濫使用的今天...你可使用多線程, 但你要知道保護它呀, 哈哈. 對於數據源或model類必定要注意多線程同時訪問的狀況, 我我的比較喜歡用GCD的串行隊列來同步線程.
Observer的移除
如今的代碼裏面不少須要用到Observer, 根據被觀察對象的狀態來相應的Update UI或者執行某個操做. 註冊observer很簡單, 可是移除的時候就出問題了, 要麼是忘記移除observer了, 要麼是移除的時機不對. 若是某個被觀察對象已經被釋放了, observer還在, 那結果只能是crash了, 因此切記至少在dealloc裏面移除一下observer...
NSArray, NSDictionary成員的判空保護
在addObject或insertObject到NSArray或者NSDictionary時最好加一下判空保護, 尤爲是網絡相關的邏輯, 若是網絡返回爲空(jason解析出來爲空), 但你仍是毅然決然的add到array裏面, 那麼...
最後一點就是commit代碼以前必定要保證木有warning, 木有內存泄露, 確保都OK以後再上傳代碼. 其實很簡單, 上傳代碼以前Command + Shift + B靜態分析一下, 看看有木有什麼issue...就先寫這麼多吧, 之後遇到更多的坑後, 我必定會爬出來再過來補充的, to be continued...