原則
- 長的席揽,描述性的方法和變量命名是好的汗盘。不要使用簡寫,除非是一些大家都知道的場景比如 VIP猾蒂。不要使用 bgView均唉,推薦使用 backgroundView
- 見名知意。含義清楚肚菠,做好不加注釋代碼自我表述能力強舔箭。(前提是代碼足夠規(guī)范)
- 不要過分追求技巧,降低代碼可讀性
- 刪除沒必要的代碼蚊逢。比如我們新建一個控制器层扶,里面會有一些不會用到的代碼,或者注釋起來的代碼烙荷,如果這些代碼不需要镜会,那就刪除它,留著偷懶嗎终抽?下次需要自己手寫
- 在方法內(nèi)部不要重復(fù)計算某個值戳表,適當(dāng)?shù)那闆r下可以將計算結(jié)果緩存起來
- 盡量減少單例的使用。
- 提供一個統(tǒng)一的數(shù)據(jù)管理入口昼伴,不管是 MVC匾旭、MVVM、MVP 模塊內(nèi)提供一個統(tǒng)一的數(shù)據(jù)管理入口會使得代碼變得更容易管理和維護亩码。
- 除了 .m 文件中方法季率,其他的地方"{"不需要另起一行。
- (void)getGooodsList
{
// ...
}
- (void)doHomework
{
if (self.hungry) {
return;
}
if (self.thirsty) {
return;
}
if (self.tired) {
return;
}
papapa.then.over;
}
變量
- 一個變量最好只有一個作用描沟,切勿為了節(jié)省代碼行數(shù)飒泻,覺得一個變量可以做多個用途鞭光。(單一原則)
- 方法內(nèi)部如果有局部變量,那么局部變量應(yīng)該靠近在使用的地方泞遗,而不是全部在頂部聲明全部的局部變量惰许。
- 變量名必須使用駝峰格式
類,協(xié)議使用大駝峰:
HomePageViewController.h
<HeaderViewDelegate>
對象等局部變量使用小駝峰:
NSString *personName = @"";
NSUInteger totalCount = 0;
- 變量的名稱必須同時包含功能與類型
UIButton *addBtn //添加按鈕
UILabel *nameLbl //名字標(biāo)簽
NSString *addressStr//地址字符串
- 系統(tǒng)常用類作實例變量聲明時加入后綴
UIViewController ---- VC
UILabel ---- Lbl
NSMutableArray ----- Marray
NSMutableDictionary ---- Mdict
常量
- 常量以相關(guān)類名作為前綴
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
不推薦這樣寫:
static const NSTimeInterval fadeOutTime = 0.4;
- 建議使用類型常量史辙,不建議使用#define預(yù)處理命令
首先比較一下這兩種聲明常量的區(qū)別:
- 預(yù)處理命令:簡單的文本替換汹买,不包括類型信息,并且可被任意修改
- 類型常量:包括類型信息聊倔,并且可以設(shè)置其使用范圍晦毙,而且不可被修改。
使用預(yù)處理雖然能達到替換文本的目的耙蔑,但是本身還是有局限性的: - 不具備類型信息见妒。
- 可以被任意修改。
- 對外公開某個常量:
如果我們需要發(fā)送通知甸陌,那么就需要在不同的地方拿到通知的“頻道”字符串(通知的名稱)须揣,那么顯然這個字符串是不能被輕易更改,而且可以在不同的地方獲取钱豁。這個時候就需要定義一個外界可見的字符串常量耻卡。
//頭文件
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
//實現(xiàn)文件
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
不推薦這樣寫:
#define CompanyName @"Apple Inc."
#define magicNumber 42
運算符
- 1元運算符和變量之間不需要空格。例如:++n
- 2元運算符與變量之間需要空格隔開牲尺。例如: containerWidth = 0.3 * Screen_Width
- 當(dāng)有多個運算符的時候需要使用括號來明確正確的順序卵酪,可讀性較好。例如: 2 << (1 + 2 * 3 - 4)
條件表達式
- 當(dāng)有條件過多秸谢、過長的時候需要換行凛澎,為了代碼看起來整齊些
//good
if (condition1() &&
condition2() &&
condition3() &&
condition4()) {
// Do something
}
//bad
if (condition1() && condition2() && condition3() && condition4()) { // Do something }
- 在一個代碼塊里面有個可能的情況時善于使用 return 來結(jié)束異常的情況。
- (void)doHomework
{
if (self.hungry) {
return;
}
if (self.thirsty) {
return;
}
if (self.tired) {
return;
}
papapa.then.over;
}
- 每個分支的實現(xiàn)都必須使用 {} 包含估蹄。
// bad
if (self.hungry) self.eat()
// good
if (self.hungry) {
self.eat()
}
- 條件判斷的時候應(yīng)該是變量在左,條件在右沫换。 if ( currentCursor == 2 ) { //... }
- switch 語句后面的每個分支都需要用大括號括起來臭蚁。
- switch 語句后面的 default 分支必須存在,除非是在對枚舉進行 switch讯赏。
類名
- 大寫駝峰式命名垮兑。每個單詞首字母大寫。比如「申請記錄控制器」
ApplyRecordsViewController - 每個類型的命名以該類型結(jié)尾漱挎。
- ViewController:使用 ViewController 結(jié)尾系枪。例子:ApplyRecordsViewController
- View:使用 View 結(jié)尾。例子:分界線:boundaryView
- NSArray:使用 s 結(jié)尾磕谅。比如商品分類數(shù)據(jù)源私爷。categories
- UITableViewCell:使用 Cell 結(jié)尾雾棺。比如 MyProfileCell
- Protocol:使用 Delegate 或者 Datasource 結(jié)尾。比如 XQScanViewDelegate
- Tool:工具類
- 代理類:Delegate
- Service 類:Service
類的注釋
有時候我們需要為我們創(chuàng)建的類設(shè)置一些注釋衬浑。我們可以在類的下面添加捌浩。
枚舉
枚舉的命名和類的命名相近。
typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) {
UIControlContentVerticalAlignmentCenter = 0,
UIControlContentVerticalAlignmentTop = 1,
UIControlContentVerticalAlignmentBottom = 2,
UIControlContentVerticalAlignmentFill = 3,
};
宏
- 全部大寫工秩,單詞與單詞之間用 _ 連接尸饺。
- 以 K 開頭。后面遵循大寫駝峰命名助币±颂「不帶參數(shù)」
#define HOME_PAGE_DID_SCROLL @"com.xq.home.page.tableview.did.scroll"
#define KHomePageDidScroll @"com.xq.home.page.tableview.did.scroll"
- 宏定義中如果包含表達式或變量,表達式和變量必須用小括號括起來眉菱。
#define MY_MIN(A, B) ((A)>(B)?(B):(A))
屬性
書寫規(guī)則馋辈,基本上就是 @property 之后空一格,括號倍谜,里面的 線程修飾詞迈螟、內(nèi)存修飾詞、讀寫修飾詞尔崔,空一格 類 對象名稱 根據(jù)不同的場景選擇合適的修飾符答毫。
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, assign, readonly) BOOL loading;
@property (nonatomic, weak) id<#delegate#> delegate;
@property (nonatomic, copy) <#returnType#> (^<#Block#>)(<#parType#>);
- 屬性的關(guān)鍵字推薦按照 原子性,讀寫季春,內(nèi)存管理的順序排列
@property (nonatomic, readwrite, copy) NSString *name;
@property (nonatomic, readonly, copy) NSString *gender;
@property (nonatomic, readwrite, strong) UIView *headerView;
- 形容詞性的BOOL屬性的getter應(yīng)該加上is前綴
@property (assign, getter=isEditable) BOOL editable;
- 使用getter方法做懶加載
實例化一個對象是需要耗費資源的洗搂,如果這個對象里的某個屬性的實例化要調(diào)用很多配置和計算贪嫂,就需要懶加載它旬陡,在使用它的前一刻對它進行實例化:
- (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;
}
但是也有對這種做法的爭議:getter方法可能會產(chǎn)生某些副作用犀变,例如如果它修改了全局變量沉噩,可能會產(chǎn)生難以排查的錯誤倚舀。
- 除了init和dealloc方法抱虐,建議都使用點語法訪問屬性
使用點語法的好處:
setter:1. setter會遵守內(nèi)存管理語義(strong, copy, weak)当凡。2. 通過在內(nèi)部設(shè)置斷點著觉,有助于調(diào)試bug逞刷。3. 可以過濾一些外部傳入的值嘉涌。4. 捕捉KVO通知。
getter:1. 允許子類化夸浅。2. 通過在內(nèi)部設(shè)置斷點仑最,有助于調(diào)試bug。3. 實現(xiàn)懶加載(lazy initialization)帆喇。
注意 : 1. 懶加載的屬性警医,必須通過點語法來讀取數(shù)據(jù)。因為懶加載是通過重寫getter方法來初始化實例變量的坯钦,如果不通過屬性來讀取該實例變量预皇,那么這個實例變量就永遠(yuǎn)不會被初始化侈玄。
2 在init和dealloc方法里面使用點語法的后果是:因為沒有繞過setter和getter,在setter和getter里面可能會有很多其他的操作深啤。而且如果它的子類重載了它的setter和getter方法拗馒,那么就可能導(dǎo)致該子類調(diào)用其他的方法。
- 不要濫用點語法溯街,要區(qū)分好方法調(diào)用和屬性訪問
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
不推薦這樣寫:
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
- 盡量使用不可變對象
建議盡量把對外公布出來的屬性設(shè)置為只讀诱桂,在實現(xiàn)文件內(nèi)部設(shè)為讀寫。具體做法是:
- 在頭文件中呈昔,設(shè)置對象屬性為readonly挥等。
- 在實現(xiàn)文件中設(shè)置為readwrite。
這樣一來堤尾,在外部就只能讀取該數(shù)據(jù)肝劲,而不能修改它,使得這個類的實例所持有的數(shù)據(jù)更加安全郭宝。而且辞槐,對于集合類的對象,更應(yīng)該仔細(xì)考慮是否可以將其設(shè)為可變的粘室。
如果在公開部分只能設(shè)置其為只讀屬性榄檬,那么就在非公開部分存儲一個可變型。所以當(dāng)在外部獲取這個屬性時衔统,獲取的只是內(nèi)部可變型的一個不可變版本,例如:
在公共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屬性設(shè)置為不可變的set。然后锦爵,提供了來增加和刪除這個set里的元素的公共接口舱殿。
在實現(xiàn)文件里:
@interface EOCPerson ()
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
@end
@implementation EOCPerson {
NSMutableSet *_internalFriends; //實現(xiàn)文件里的可變集合
}
- (NSSet*)friends
{
return [_internalFriends copy]; //get方法返回的永遠(yuǎn)是可變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;
}
我們可以看到,在實現(xiàn)文件里险掀,保存一個可變set來記錄外部的增刪操作沪袭。
這里最重要的代碼是:
- (NSSet*)friends
{
return [_internalFriends copy];
}
這個是friends屬性的獲取方法:它將當(dāng)前保存的可變set復(fù)制了一不可變的set并返回。因此迷郑,外部讀取到的set都將是不可變的版本枝恋。
單例
單例適合全局管理狀態(tài)或者事件的場景。一旦創(chuàng)建嗡害,對象的指針保存在靜態(tài)區(qū),單例對象在堆內(nèi)存中分配的內(nèi)存空間只有程序銷毀的時候才會釋放畦攘“悦茫基于這種特點,那么我們類似 UIApplication 對象知押,需要全局訪問唯一一個對象的情況才適合單例叹螟,或者訪問頻次較高的情況鹃骂。我們的功能模塊的生命周期肯定小于 App 的生命周期,如果多個單例對象的話罢绽,勢必 App 的開銷會很大畏线,糟糕的情況系統(tǒng)會殺死 App。如果覺得非要用單例比較好良价,那么注意需要在合適的場合 tearDown 掉寝殴。
單例的使用場景概括如下:
- 控制資源的使用,通過線程同步來控制資源的并發(fā)訪問明垢。
- 控制實例的產(chǎn)生蚣常,以達到節(jié)約資源的目的。
- 控制數(shù)據(jù)的共享痊银,在不建立直接關(guān)聯(lián)的條件下抵蚊,讓多個不相關(guān)的進程或線程之間實現(xiàn)通信。
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//because has rewrited allocWithZone use NULL avoid endless loop lol.
_sharedInstance = [[super allocWithZone:NULL] init];
});
return _sharedInstance;
}
+ (id)allocWithZone:(struct _NSZone *)zone
{
return [TestNSObject sharedInstance];
}
+ (instancetype)alloc
{
return [TestNSObject sharedInstance];
}
- (id)copy
{
return self;
}
- (id)mutableCopy
{
return self;
}
- (id)copyWithZone:(struct _NSZone *)zone
{
return self;
}
私有變量
推薦以 _ 開頭溯革,寫在 .m 文件中贞绳。例如 NSString * _somePrivateVariable
代理方法
- 類的實例必須作為方法的參數(shù)之一。
- 對于一些連續(xù)的狀態(tài)的致稀,可以加一些 will(將要)冈闭、did(已經(jīng))
- 以類的名稱開頭
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
方法
- 方法與方法之間間隔一行
- 大量的方法盡量要以組的形式放在一起,比如生命周期函數(shù)豺裆、公有方法拒秘、私有方法、setter && getter臭猜、代理方法..
- 方法最后面的括號需要另起一行躺酒。遵循 Apple 的規(guī)范
- 對于其他場景的括號,括號不需要單獨換行蔑歌。比如 if 后面的括號羹应。
- 如果方法參數(shù)過多過長,建議多行書寫次屠。用冒號進行對齊园匹。
- 一個方法內(nèi)的代碼最好保持在50行以內(nèi),一般經(jīng)驗來看如果一個方法里面的代碼行數(shù)過多劫灶,代碼的閱讀體驗就很差(別問為什么裸违,做過重構(gòu)代碼行數(shù)很長的人都有類似的心情)
- 一個函數(shù)只做一個事情,做到單一原則本昏。所有的類供汛、方法設(shè)計好后就可以類似搭積木一樣實現(xiàn)一個系統(tǒng)。
- 對于有返回值的函數(shù),且函數(shù)內(nèi)有分支情況怔昨。確保每個分支都有返回值雀久。
- 函數(shù)如果有多個參數(shù),外部傳入的參數(shù)需要檢驗參數(shù)的非空趁舀、數(shù)據(jù)類型的合法性赖捌,參數(shù)錯誤做一些措施:立即返回、斷言矮烹。
- 多個函數(shù)如果有邏輯重復(fù)的代碼越庇,建議將重復(fù)的部分抽取出來,成為獨立的函數(shù)進行調(diào)用
- (instancetype)init
{
self = [super init];
if (self) {
<#statements#>
}
return self;
}
- (void)doHomework:(NSString *)name
period:(NSInteger)second
score:(NSInteger)score;
- 方法如果有多個參數(shù)的情況下需要注意是否需要介詞和連詞擂送。很多時候在不知道如何抉擇測時候思考下蘋果的一些 API 的方法命名悦荒。
//good
- (instancetype)initWithAge:(NSInteger)age name:(NSString *)name;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
//bad
- (instancetype)initWithAge:(NSInteger)age andName:(NSString *)name;
- (void)tableView:(UITableView *)tableView :(NSIndexPath *)indexPath;
- .m 文件中的私有方法需要在頂部進行聲明
- 方法組之間也有個順序問題。
- 在文件最頂部實現(xiàn)屬性的聲明嘹吨、私有方法的聲明(很多人省去這一步搬味,問題不大,但是蠻多第三方的庫都寫了蟀拷,看起來還是會很方便碰纬,建議書寫)。
- 在生命周期的方法里面问芬,比如 viewDidLoad 里面只做界面的添加悦析,而不是做界面的初始化,所有的 view 初始化建議放在 getter 里面去做此衅。往往 view 的初始化的代碼長度會比較長强戴、且一般會有多個 view 所以 getter 和 setter 一般建議放在最下面,這樣子頂部就可以很清楚的看到代碼的主要邏輯挡鞍。
- 所有button骑歹、gestureRecognizer 的響應(yīng)事件都放在這個區(qū)域里面,不要到處亂放墨微。
文件基本上就是
#import "ViewController.h"
/*ViewController*/
/*View&&Util*/
/*model*/
/*NetWork InterFace*/
/*Vender*/
@interface ViewController ()
@end
@implementation ViewController
#pragma mark - life cycle
- (void)viewWillAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = @"標(biāo)準(zhǔn)模版";
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)dealloc
{
NSLog(@"%s",__func__);
}
#pragma mark - public Method
#pragma mark - private method
#pragma mark - event response
#pragma mark - UITableViewDelegate
#pragma mark - UITableViewDataSource
//...(多個代理方法依次往下寫)
#pragma mark - getters and setters
@end
14 .私有方法應(yīng)該在實現(xiàn)文件中申明道媚。
@interface ViewController ()
- (void)basicConfiguration;
@end
@implementation ViewController
- (void)basicConfiguration
{
//Do some basic configuration
}
@end
圖片資源
- 單個文件的命名
文件資源的命名也需要一定的規(guī)范,形式為:功能模塊名類別功能_狀態(tài)@nx.png
Setting_Button_search_selected@2x.png翘县、Setting_Button_search_selected@3x.png
Setting_Button_search_unselected@2x.png最域、Setting_Button_search_unselected@3x.png
2、 資源的文件夾命名 最好也參考 App 按照功能模塊建立對應(yīng)的實體文件夾目錄锈麸,最后到對應(yīng)的目錄下添加相應(yīng)的資源文件镀脂。
注釋
優(yōu)秀的代碼大部分是可以自描述的,我們完全可以用程代碼本身來表達它到底在干什么忘伞,而不需要注釋的輔助狗热。
但并不是說一定不能寫注釋钞馁,有以下三種情況比較適合寫注釋:
- 公共接口(注釋要告訴閱讀代碼的人虑省,當(dāng)前類能實現(xiàn)什么功能)匿刮。
- 涉及到比較深層專業(yè)知識的代碼(注釋要體現(xiàn)出實現(xiàn)原理和思想)。
- 容易產(chǎn)生歧義的代碼(但是嚴(yán)格來說探颈,容易讓人產(chǎn)生歧義的代碼是不允許存在的)熟丸。
除了上述這三種情況,如果別人只能依靠注釋才能讀懂你的代碼的時候伪节,就要反思代碼出現(xiàn)了什么問題光羞。
最后,對于注釋的內(nèi)容怀大,相對于“做了什么”纱兑,更應(yīng)該說明“為什么這么做”。
- 對于類的注釋寫在當(dāng)前類文件的頂部
- 對于屬性的注釋需要寫在屬性后面的地方化借。 //*<userId/
- 對于 .h 文件中方法的注釋潜慎,一律按快捷鍵 command+option+/。三個快捷鍵解決蓖康。按需在旁邊對方法進行說明解釋铐炫、返回值、參數(shù)的說明和解釋
- 對于 .m 文件中的方法的注釋蒜焊,在方法的旁邊添加 //倒信。
- 注釋符和注釋內(nèi)容需要間隔一個空格。 例如: // fetch goods list
版本規(guī)范
采用 A.B.C 三位數(shù)字命名泳梆,比如:1.0.2鳖悠,當(dāng)有更新的情況下按照下面的依據(jù)
- 屬于重大內(nèi)容的更新 1.0.2 -> 2.0.0
- 屬于小部分內(nèi)容的更新 1.0.2 -> 1.1.1
- 屬于補丁更新 1.0.2 -> 1.0.3
改進
我們知道了平時在使用 Xcode 開發(fā)的過程中使用的系統(tǒng)提供的代碼塊所在的地址和新建控制器、模型优妙、view等的文件模版的存放文件夾地址后乘综,我們就可以設(shè)想下我們是否可以定制自己團隊風(fēng)格的控制器模版、是否可以打造和維護自己團隊的高頻使用的代碼塊鳞溉?
答案是可以的瘾带。
Xcode 代碼塊的存放地址:~/Library/Developer/Xcode/UserData/CodeSnippets
Xcode 文件模版的存放地址:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/File Templates/
代碼塊的改造
我們可以將屬性、控制器生命周期方法熟菲、單例構(gòu)造一個對象的方法看政、代理方法、block抄罕、GCD允蚣、UITableView 懶加載、UITableViewCell 注冊呆贿、UITableView 代理方法的實現(xiàn)嚷兔、UICollectionVIew 懶加載森渐、UICollectionVIewCell 注冊、UICollectionView 的代理方法實現(xiàn)等等組織為 codesnippets
思考
- 封裝好 codesnippets 之后團隊除了你 編寫這個項目的人如何使用冒晰?如何知道是否有這個代碼塊同衣?
方案:先在團隊內(nèi)召開代碼規(guī)范會議,大家都統(tǒng)一知道這個事情在壶运。之后大家共同維護 codesnippets耐齐。用法見下
屬性:通過 Property_類型 開頭,回車鍵自動補全蒋情。比如 Strong 類型埠况,編寫代碼通過 Property_Strong 回車鍵自動補全成如下格式
@property (nonatomic, strong) <#Class#> *<#object#>;
方法:以 Method_關(guān)鍵詞 回車鍵確認(rèn),自動補全棵癣。比如 Method_UIScrollViewDelegate 回車鍵自動補全成 如下格式
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
}
各種常見的 Mark:以 Mark_關(guān)鍵詞 回車確認(rèn)辕翰,自動補全。比如 Method_MethodsGroup 回車鍵自動補全成 如下格式
#pragma mark - life cycle
#pragma mark - public Method
#pragma mark - private method
#pragma mark - event response
#pragma mark - UITableViewDelegate
#pragma mark - UITableViewDataSource
#pragma mark - getters and setters
- 封裝好 codesnippets 之后團隊內(nèi)如何統(tǒng)一狈谊?想到一個方案喜命,可以將團隊內(nèi)的 codesnippets 共享到 git,團隊內(nèi)的其他成員再從云端拉取同步的畴。這樣的話團隊內(nèi)的每個成員都可以使用最新的 codesnippets 來編碼渊抄。
編寫 shell 腳本。幾個關(guān)鍵步驟:
- 給系統(tǒng)文件夾授權(quán)
- 在腳本所在文件夾新建存放代碼塊的文件夾
- 將系統(tǒng)文件夾下面的代碼塊復(fù)制到步驟2創(chuàng)建的文件夾下面
- 將當(dāng)前的所有文件提交到 Git 倉庫
文件模版的改造
我們觀察系統(tǒng)文件模版的特點丧裁,和在 Xcode 新建文件模版對應(yīng)护桦。
所以我們新建 Custom 文件夾,將系統(tǒng) Source 文件夾下面的 Cocoa Touch Class.xctemplate 復(fù)制到 Custom 文件夾下煎娇。重命名為我們需要的名字二庵,我這里以“Power”為例
進入 PowerViewController.xctemplate/PowerViewControllerObjective-C
修改 FILEBASENAME.h 和 FILEBASENAME.m 文件內(nèi)容
在替換 .h 文件內(nèi)容的時候后面改為 UIViewController,不然其他開發(fā)者新建文件模版的時候出現(xiàn)的不是 UIViewController 而是我們的 PowerViewController
修改 TemplateInfo.plist
- 如何使用
商量好一個標(biāo)識(“Power”)缓呛。比如我新建了單例催享、控制器、Model哟绊、UIView4個模版因妙,都以為 Power 開頭。 - 如何共享
以 shell 腳本為工具票髓。使用腳本將 git 云端的代碼模版同步到本地 Xcode 文件夾對應(yīng)的位置就可以使用了攀涵。關(guān)鍵步驟:
- git clone 代碼到腳本所在文件夾
- 進入存放 codesnippets 的文件夾將內(nèi)容復(fù)制到系統(tǒng)存放 codesnippets 的地方
- 進入存放 file template 的文件夾將內(nèi)容復(fù)制到系統(tǒng)存放 file template 的地方
./syncSnippets.sh // 同步git云端代碼塊和文件模版到本地
./uploadMySnippets.sh //將本地的代碼塊和文件模版同步到云端
補充
if語句
1。 必須列出所有分支(窮舉所有的情況)洽沟,而且每個分支都必須給出明確的結(jié)果以故。
var hintStr;
if (count < 3) {
hintStr = "Good";
} else {
hintStr = "";
}
不推薦這樣寫:
var hintStr;
if (count < 3) {
hintStr = "Good";
}
- 不要使用過多的分支,要善于使用return來提前返回錯誤的情況
- (void)someMethod {
if (!goodCondition) {
return;
}
//Do something
}
不推薦這樣寫:
- (void)someMethod {
if (goodCondition) {
//Do something
}
}
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError)err{
//方法1. 參數(shù)為nil
if (!dict) {
if (err) *err = [JSONModelError errorInputIsNil];
return nil;
}
//方法2. 參數(shù)不是nil裆操,但也不是字典
if (![dict isKindOfClass:[NSDictionary class]]) {
if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];
return nil;
}
//方法3. 初始化
self = [self init];
if (!self) {
//初始化失敗
if (err) *err = [JSONModelError errorModelIsInvalid];
return nil;
}
//方法4. 檢查用戶定義的模型里的屬性集合是否大于傳入的字典里的key集合(如果大于怒详,則返回NO)
if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
return nil;
}
//方法5. 核心方法:字典的key與模型的屬性的映射
if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
return nil;
}
//方法6. 可以重寫[self validate:err]方法并返回NO炉媒,讓用戶自定義錯誤并阻攔model的返回
if (![self validate:err]) {
return nil;
}
//方法7. 終于通過了!成功返回model
return self;
}
可以看到昆烁,在這里吊骤,首先判斷出各種錯誤的情況然后提前返回,把最正確的情況放到最后返回善玫。
- 條件表達式如果很長水援,則需要將他們提取出來賦給一個BOOL值
let nameContainsSwift = sessionName.hasPrefix("Swift")
let isCurrentYear = sessionDateCompontents.year == 2014
let isSwiftSession = nameContainsSwift && isCurrentYear
if (isSwiftSession) {
// Do something
}
不推薦這樣寫:
if ( sessionName.hasPrefix("Swift") && (sessionDateCompontents.year == 2014) ) {
// Do something
}
- 條件語句的判斷應(yīng)該是變量在左,常量在右
if ( count == 6) {
}
if ( object == nil) {
}
不推薦這樣寫:
if ( 6 == count) {
}
if ( nil == object ) {
}
for語句
- 不可在for循環(huán)內(nèi)修改循環(huán)變量茅郎,防止for循環(huán)失去控制。
for (int index = 0; index < 10; index++){
...
logicToChange(index)
}
- 避免使用continue和break或渤。
ontinue和break所描述的是“什么時候不做什么”系冗,所以為了讀懂二者所在的代碼,我們需要在頭腦里將他們?nèi)》础?/li>
其實最好不要讓這兩個東西出現(xiàn)薪鹦,因為我們的代碼只要體現(xiàn)出“什么時候做什么”就好了掌敬,而且通過適當(dāng)?shù)姆椒ǎ强梢詫⑦@兩個東西消滅掉的:
2.1 如果出現(xiàn)了continue池磁,只需要把continue的條件取反即可
var filteredProducts = Array<String>()
for level in products {
if level.hasPrefix("bad") {
continue
}
filteredProducts.append(level)
}
我們可以看到奔害,通過判斷字符串里是否含有“bad”這個prefix來過濾掉一些值。其實我們是可以通過取反地熄,來避免使用continue的:
for level in products {
if !level.hasPrefix("bad") {
filteredProducts.append(level)
}
}
Switch語句
- 每個分支都必須用大括號括起來
switch (integer) {
case 1: {
// ...
}
break;
case 2: {
// ...
break;
}
case 3: {
// ...
break;
}
default:{
// ...
break;
}
}
- 使用枚舉類型時华临,不能有default分支, 除了使用枚舉類型以外端考,都必須有default分支
RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;
switch (menuType) {
case RWTLeftMenuTopItemMain: {
// ...
break;
}
case RWTLeftMenuTopItemShows: {
// ...
break;
}
case RWTLeftMenuTopItemSchedule: {
// ...
break;
}
}
在Switch語句使用枚舉類型的時候雅潭,如果使用了default分支,在將來就無法通過編譯器來檢查新增的枚舉類型了却特。
函數(shù)
1. 一個函數(shù)的長度必須限制在50行以內(nèi)
通常來說扶供,在閱讀一個函數(shù)的時候,如果視需要跨過很長的垂直距離會非常影響代碼的閱讀體驗裂明。如果需要來回滾動眼球或代碼才能看全一個方法椿浓,就會很影響思維的連貫性,對閱讀代碼的速度造成比較大的影響闽晦。最好的情況是在不滾動眼球或代碼的情況下一眼就能將該方法的全部代碼映入眼簾扳碍。
2. 一個函數(shù)只做一件事(單一原則)
每個函數(shù)的職責(zé)都應(yīng)該劃分的很明確(就像類一樣)。
dataConfiguration()
viewConfiguration()
不推薦這樣寫:
void dataConfiguration()
{
...
viewConfiguration()
}
3. 對于有返回值的函數(shù)(方法)尼荆,每一個分支都必須有返回值
int function()
{
if(condition1){
return count1
}else if(condition2){
return count2
}else{
return defaultCount
}
}
不推薦這樣寫:
int function()
{
if(condition1){
return count1
}else if(condition2){
return count2
}
}
4. 對輸入?yún)?shù)的正確性和有效性進行檢查左腔,參數(shù)錯誤立即返回
void function(param1,param2)
{
if(param1 is unavailable){
return;
}
if(param2 is unavailable){
return;
}
//Do some right thing
}
5. 如果在不同的函數(shù)內(nèi)部有相同的功能,應(yīng)該把相同的功能抽取出來單獨作為另一個函數(shù)
6. 將函數(shù)內(nèi)部比較復(fù)雜的邏輯提取出來作為單獨的函數(shù)
一個函數(shù)內(nèi)的不清晰(邏輯判斷比較多捅儒,行數(shù)較多)的那片代碼液样,往往可以被提取出去振亮,構(gòu)成一個新的函數(shù),然后在原來的地方調(diào)用它這樣你就可以使用有意義的函數(shù)名來代替注釋鞭莽,增加程序的可讀性坊秸。
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();
8. 避免使用全局變量澎怒,類成員(class member)來傳遞信息褒搔,盡量使用局部變量和參數(shù)。
在一個類里面喷面,經(jīng)常會有傳遞某些變量的情況星瘾。而如果需要傳遞的變量是某個全局變量或者屬性的時候,有些朋友不喜歡將它們作為參數(shù)惧辈,而是在方法內(nèi)部就直接訪問了:
class A {
var x;
func updateX()
{
...
x = ...;
}
func printX()
{
updateX();
print(x);
}
}
我們可以看到琳状,在printX方法里面,updateX和print方法之間并沒有值的傳遞盒齿,乍一看我們可能不知道x從哪里來的念逞,導(dǎo)致程序的可讀性降低了。
而如果你使用局部變量而不是類成員來傳遞信息边翁,那么這兩個函數(shù)就不需要依賴于某一個類翎承,而且更加容易理解,不易出錯:
func updateX() -> String
{
x = ...;
return x;
}
func printX()
{
String x = updateX();
print(x);
}
CGRect函數(shù)
其實iOS內(nèi)部已經(jīng)提供了相應(yīng)的獲取CGRect各個部分的函數(shù)了符匾,它們的可讀性比較高叨咖,而且簡短,推薦使用:
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 };
范型
建議在定義NSArray和NSDictionary時使用泛型待讳,可以保證程序的安全性:
NSArray<NSString *> *testArr = [NSArray arrayWithObjects:@"Hello", @"world", nil];
NSDictionary<NSString *, NSNumber *> *dic = @{@"key":@(1), @"age":@(10)};
Block
- Block屬性應(yīng)該使用copy關(guān)鍵字
typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);
@property (nonatomic, readwrite, copy) ErrorCodeBlock errorBlock;//將block拷貝到堆中
為常用的Block類型創(chuàng)建typedef
如果我們需要重復(fù)創(chuàng)建某種block(相同參數(shù)芒澜,返回值)的變量,我們就可以通過typedef來給某一種塊定義屬于它自己的新類型
int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value)
{
// Implementation
return someInt;
}
這個Block有一個bool參數(shù)和一個int參數(shù)创淡,并返回int類型痴晦。我們可以給它定義類型:
typedef int(^EOCSomeBlock)(BOOL flag, int value);
再次定義的時候,就可以通過簡單的賦值來實現(xiàn):
EOCSomeBlock block = ^(BOOL flag, int value){
// Implementation
};
定義作為參數(shù)的Block
- (void)startWithCompletionHandler: (void(^)(NSData *data, NSError *error))completion;
這里的Block有一個NSData參數(shù)琳彩,一個NSError參數(shù)并沒有返回值
typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);
- (void)startWithCompletionHandler:(EOCCompletionHandler)completion;”
字面量語法
盡量使用字面量值來創(chuàng)建 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];
面向協(xié)議編程
如果某些功能(方法)具備可復(fù)用性誊酌,我們就需要將它們抽取出來放入一個抽象接口文件中(在iOS中,抽象接口即協(xié)議)露乏,讓不同類型的對象遵循這個協(xié)議碧浊,從而擁有相同的功能。
因為協(xié)議是不依賴于某個對象的瘟仿,所以通過協(xié)議箱锐,我們可以解開兩個對象之間的耦合。如何理解呢劳较?我們來看一下下面這個例子:
現(xiàn)在有一個需求:在一個UITableViewController里面拉取feed并展示出來驹止。
方案一:
定義一個拉取feed的類ZOCFeedParser浩聋,這個類有一些代理方法實現(xiàn)feed相關(guān)功能:
@protocol ZOCFeedParserDelegate <NSObject>
@optional
- (void)feedParserDidStart:(ZOCFeedParser *)parser;
- (void)feedParser:(ZOCFeedParser *)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info;
- (void)feedParser:(ZOCFeedParser *)parser didParseFeedItem:(ZOCFeedItemDTO *)item;
- (void)feedParserDidFinish:(ZOCFeedParser *)parser;
- (void)feedParser:(ZOCFeedParser *)parser didFailWithError:(NSError *)error;@end
@interface ZOCFeedParser : NSObject
@property (nonatomic, weak) id <ZOCFeedParserDelegate> delegate;
@property (nonatomic, strong) NSURL *url;
- (id)initWithURL:(NSURL *)url;
- (BOOL)start;
- (void)stop;
@end
然后在ZOCTableViewController里面?zhèn)魅隯OCFeedParser,并遵循其代理方法臊恋,實現(xiàn)feed的拉取功能衣洁。
@interface ZOCTableViewController : UITableViewController<ZOCFeedParserDelegate>
- (instancetype)initWithFeedParser:(ZOCFeedParser *)feedParser;
@end
具體應(yīng)用:
NSURL *feedURL = [NSURL URLWithString:@"http://bbc.co.uk/feed.rss"];
ZOCFeedParser *feedParser = [[ZOCFeedParser alloc] initWithURL:feedURL];
ZOCTableViewController *tableViewController = [[ZOCTableViewController alloc] initWithFeedParser:feedParser];
feedParser.delegate = tableViewController;
OK,現(xiàn)在我們實現(xiàn)了需求:在ZOCTableViewController里面存放了一個ZOCFeedParser對象來處理feed的拉取功能抖仅。
但這里有一個嚴(yán)重的耦合問題:ZOCTableViewController只能通過ZOCFeedParser對象來處理feed的拉取功能坊夫。
于是我們重新審視一下這個需求:其實我們實際上只需要ZOCTableViewController拉取feed就可以了,而具體是由哪個對象來拉取撤卢,ZOCTableViewController并不需要關(guān)心环凿。
也就是說,我們需要提供給ZOCTableViewController的是一個更范型的對象凸丸,這個對象具備了拉取feed的功能就好了拷邢,而不應(yīng)該僅僅局限于某個具體的對象(ZOCFeedParser)。所以屎慢,剛才的設(shè)計需要重新做一次修改:
方案二
首先需要在一個接口文件ZOCFeedParserProtocol.h里面定義抽象的,具有拉取feed功能的協(xié)議:
@protocol ZOCFeedParserDelegate <NSObject>
@optional
- (void)feedParserDidStart:(id<ZOCFeedParserProtocol>)parser;
- (void)feedParser:(id<ZOCFeedParserProtocol>)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info;
- (void)feedParser:(id<ZOCFeedParserProtocol>)parser didParseFeedItem:(ZOCFeedItemDTO *)item;
- (void)feedParserDidFinish:(id<ZOCFeedParserProtocol>)parser;
- (void)feedParser:(id<ZOCFeedParserProtocol>)parser didFailWithError:(NSError *)error;@end
@protocol ZOCFeedParserProtocol <NSObject>
@property (nonatomic, weak) id <ZOCFeedParserDelegate> delegate;
@property (nonatomic, strong) NSURL *url;
- (BOOL)start;
- (void)stop;
@end
而原來的ZOCFeedParser僅僅是需要遵循上面這個協(xié)議就具備了拉取feed的功能:
@interface ZOCFeedParser : NSObject <ZOCFeedParserProtocol>
- (id)initWithURL:(NSURL *)url;//僅僅需要通過傳入url即可忽洛,其他事情都交給ZOCFeedParserProtocol@end
而且腻惠,ZOCTableViewController也不直接依賴于ZOCFeedParser對象,我們只需要傳給它一個遵循<ZOCFeedParserProtocol>的對象即可欲虚。
@interface ZOCTableViewController : UITableViewController <ZOCFeedParserDelegate>
- (instancetype)initWithFeedParser:(id<ZOCFeedParserProtocol>)feedParser;
@end
這樣一來集灌,ZOCTableViewController和ZOCFeedParser之間就沒有直接的關(guān)系了。以后复哆,如果我們想:
- 給這個feed拉取器增加新的功能:僅需要修改ZOCFeedParserProtocol.h文件欣喧。
- 更換一個feed拉取器實例:創(chuàng)建一個新類型來遵循ZOCFeedParserProtocol.h即可。