-------------------------------------編碼原則-------------------------------------
- 需求是暫時(shí)的,只有變化才是永恒的,
面向變化編程,而不是面向需求編程; - 不要過分追求技巧,從而降低
程序的可讀性; - 簡潔的代碼可以讓bug無處藏身,要
寫出明顯沒有bug的代碼,而不是沒有明顯bug的代碼; - 先解決問題,再考慮將來的擴(kuò)展問題。
-------------------------------------編碼原則-------------------------------------
代碼格式化
- 使用系統(tǒng)自帶的格式化工具,選中要格式化的代碼后使用
control + i快捷鍵進(jìn)行代碼格式化
代碼片段
- 將CodeSnippets文件夾放到這里
/Users/xxxx/Library/Developer/Xcode/UserData下面,重啟Xcode即可 - 代碼片段使用說明
| 代碼片段快捷鍵 | 片段說明 |
|---|---|
| sk_mark | 快速添加mark注釋 |
| sk_mark_controller_methods | 控制器中方法布局 |
| sk_mark_single_lifeCycle | 控制器view生命周期方法匯總 |
| sk_mark_single_intial | 頁面初始化相關(guān)方法匯總 |
| sk_mark_single_network | 網(wǎng)絡(luò)請求相關(guān)方法 |
| sk_mark_single_override | 重載的方法 |
| sk_mark_single_eventResponse | 事件和響應(yīng)方法匯總 |
| #pragma mark - <UITableViewDelegate> | 代理方法匯總 |
| sk_mark_single_public | 對外公開的方法匯總 |
| sk_mark_single_private | 私有方法匯總 |
| sk_mark_single_gettersAndSetters | 屬性集中營 |
| sk_view_Define | 自定義view |
| sk_pro_assign | 快速定義assign屬性 |
| sk_pro_copy | 快速定義copy屬性 |
| sk_pro_strong | 快速定義strong屬性 |
| sk_pro_weak | 快速定義weak屬性 |
| sk_block_var | 定義block變量,直接定義屬性或成員變量 |
| sk_block_typedef | 定義block類型 |
| sk_yy_pro_whiteList | 屬性白名單,當(dāng)不參與轉(zhuǎn)換的屬性個(gè)數(shù)遠(yuǎn)遠(yuǎn)超過參與轉(zhuǎn)換的屬性個(gè)數(shù)時(shí)可以用此方法來指定哪些屬性需要參與字典轉(zhuǎn)模型 |
| sk_yy_property_blackList | 在黑名單中屬性“不參與”字典轉(zhuǎn)模型 |
| sk_yy_propertyMapper | 屬性映射 |
| sk_yy_array_objectMapper | 指定數(shù)組屬性中的實(shí)體類型 |
| sk_request_multiple | 多個(gè)請求優(yōu)雅處理 |
| sk_single_obj | 實(shí)現(xiàn)單例對象 |
開發(fā)中遇到的規(guī)范問題總結(jié)
- 引用的頭文件需要做歸類處理,見下面關(guān)于在Controller和View中頭文件的分類規(guī)范
- 正確使用空格,不要有多余的空格
- 屬性定義要有合適的空格
@property (nonatomic, copy, readonly) NSString *userName;- 方法定義要有合適的空格
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { }- 屬性與屬性間不要有空格
@property (nonatomic, weak, nullable) id <UITableViewDataSource> dataSource; @property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;- 方法與方法之間必須有一個(gè)空格的間隙
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; - (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; - 做好屬性
歸類,eg: 視圖屬性放在一起,數(shù)據(jù)屬性放在一起等 - 頭文件引入規(guī)范
- 代理引入書寫規(guī)范,超過三個(gè)以上的代理就要按如下格式
@interface HCConversationListViewController ()
<
UITableViewDelegate,
UITableViewDataSource,
MGSwipeTableCellDelegate,
HCIMConversationListManagerDelegate
>
- 警告必須去除
- 注釋規(guī)范,不要自己敲,直接使用快捷鍵
command+option+/生成 - 私有方法命名規(guī)范,私有方法統(tǒng)一使用
下劃線開頭 - 使用
代碼片段來統(tǒng)一規(guī)范 - 少用宏,多用常量
- if層級嵌套不允許超過
三級,要善于使用return來提前返回錯(cuò)誤的情況,把最正確的情況放到最后返回 -
類不能超過1000行,方法不能超過100行 - 使用局部變量接收一個(gè)鏈?zhǔn)秸{(diào)用的屬性值,再用這個(gè)局部變量再去完成后面的邏輯
// 先用一個(gè)臨時(shí)變量接收常常的屬性值
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
// 再在下面使用keyWindow,不要使用[UIApplication sharedApplication].keyWindow
- 多處使用的字符串需要定義成常量,不允許直接使用字符串
- 字典
不允許采取@{}的方式定義,使用NSMutableDictionary的方式定義 - 不要在Controller里進(jìn)行數(shù)據(jù)加工,放到Manager里
- 不要直接使用int,而應(yīng)該使用NSInteger
-
在頭文件里說明類的用途
類的用途 - 刪除無用的注釋代碼
- 同樣的功能不能寫多份
- 不要在init和dealloc函數(shù)中使用屬性
-
subView的初始化放到getter中去做,getter和setter全部都放在類的最后 少用storyboard、xib- 萬不得已不要用繼承,優(yōu)先考慮組合
團(tuán)隊(duì)Xcode 開發(fā)統(tǒng)一開發(fā)字體
- 這里推薦
JetBrains Mono,據(jù)說 JetBrains Mono 是最適合程序員的字體之一 - JetBrains Mono字體下載
- Mac直接選擇系統(tǒng)自帶的
字體冊程序設(shè)置字體,設(shè)置如下
添加字體 - 另外一種安裝方法:將下載的字體壓縮文件的
zip后綴改成otf,然后雙擊安裝即可
名詞定義
-
大駝峰式命名:每個(gè)單詞的首字母都采用大寫字母 -
小駝峰式命名:第一個(gè)單詞首字母小寫,剩下單詞的首字母大寫
命名規(guī)范
- 命名原則
- 明確表達(dá)含義,盡量做到不需要注釋也能了解其作用,
若做不到,就加注釋; - 使用全稱,盡量不使用縮寫(
使用規(guī)范的縮寫,不要自創(chuàng)); - 公共接口不僅命名要規(guī)范,也要添加上注釋,方便使用者快速了解接口的含義,最好在注釋里
給出使用示范;
- 明確表達(dá)含義,盡量做到不需要注釋也能了解其作用,
類名
- 采取
大駝峰式命名 - 示例:XXHomeViewController,
XX是項(xiàng)目前綴,不屬于命名范疇,這里不要產(chǎn)生誤解
私有變量
- 私有成員變量在.m文件中聲明
-
以_開頭,小駝峰式命名,示例:NSString *_privateVar;或者在擴(kuò)展類中定義屬性,二者選其一即可
property變量
-
小駝峰式命名,示例:userName - @property和左括號間
有空格,有括號和類型間有空格,括號累不修飾符之間有空格,星號和變量名之間沒有空格,屬性的修飾關(guān)鍵字推薦排序:原子性,內(nèi)存管理,讀寫權(quán)限(默認(rèn)是可讀寫),示例: - iOS中不建議使用
atomic修飾符
@property (nonatomic, copy, readonly) NSString *userName;
-
NSString、NSArray、NSDictionary、block屬性應(yīng)該使用copy關(guān)鍵字;
宏常量命名
-
#define預(yù)處理定義的宏常量全部大寫,單詞間用_分隔,示例:
#define MD_SCREEN_BOUNDS [[UIScreen mainScreen] bounds]
- 宏定義中如果包含表達(dá)式或變量,表達(dá)式或變量必須用
小括號括起來,示例:
#define MD_SCALING ([[UIScreen mainScreen] bounds].size.width/375.f) // 縮放比例
類型常量命名
- 對于
僅在.m文件中使用的常量要使用static進(jìn)行定義,命名方面以字符k開頭,示例:staticNSTimeIntervalconstkAnimationDuration = 0.3; - 對于定義于
.h文件中的常量,對外部可見,則以定義該常量所在類的類名開頭(仿照蘋果風(fēng)格),在頭文件中使用UIKIT_EXTERN聲明,在.m文件中定義其值,示例:
//在.h文件中:
UIKIT_EXTERN NSString * const UIApplicationStatusBarOrientationUserInfoKey;
//在.m文件中:
NSString * const UIApplicationStatusBarOrientationUserInfoKey = @"xxxxxx";
block
- 使用
dispatch_block_t替代void (^XXXBlock)()這類型的block
枚舉
- 與類的命名規(guī)則一致,采取
大駝峰式命名,示例:UIControlState - 枚舉內(nèi)容的命名需要以該枚舉的類型名稱開頭,示例:
UIControlStateNormal -
NS_ENUM定義通用枚舉,NS_OPTIONS定義位移枚舉,詳見位移枚舉NS_OPTIONS詳解
delegate
- 用
Delegate做后綴,示例:UIScrollViewDelegate - 用
@optional修飾可選實(shí)現(xiàn)的方法,用@required(默認(rèn)值)修飾必須實(shí)現(xiàn)的方法 - 類的實(shí)例
必須為代理方法的參數(shù)之一- 如果只有
一個(gè)參數(shù),方法名要符合實(shí)際含義,示例:- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; - 如果有
兩個(gè)及以上參數(shù),以類的名字開頭,以表明此方法是屬于哪個(gè)類的,示例:- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- 如果只有
- 當(dāng)你的
代理的方法過多, 可以拆分數(shù)據(jù)部分和邏輯部分, 數(shù)據(jù)部分用DataSource做后綴,示例:UITableViewDataSource,邏輯部分以Delegate做后綴,示例:UITableViewDelegate - 代理定義中使用
did和will來表達(dá)已發(fā)生的變化 或 將要發(fā)生的變化
方法
- 方法名用小駝峰式命名
- 方法名不要使用
new作為前綴 - 不要使用
and來連接屬性參數(shù) - 如果方法描述兩種獨(dú)立的行為,使用
and來串接它們 - 方法實(shí)現(xiàn)時(shí),如果參數(shù)過長,則令每個(gè)參數(shù)占用一行,以冒號對齊
// ------------- 不要使用 and 來連接屬性參數(shù) -------------
// 錯(cuò)誤示范
- (void)openURL:(NSURL *)url andOptions:(NSDictionary<UIApplicationOpenExternalURLOptionsKey, id> *)options andCompletionHandler:(void (^ __nullable)(BOOL success))completion {
}
// 正確示范
- (void)openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenExternalURLOptionsKey, id> *)options completionHandler:(void (^ __nullable)(BOOL success))completion {
}
// ------------- 表示對象行為的方法示范 -------------
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
}
// ------------- 對象初始化方法定義示范 -------------
+ (instancetype)arrayWithObject:(id)anObject {
}
- (instancetype)initWithObject:(id)anObject {
}
// ------------- 如果方法描述兩種獨(dú)立的行為,使用and來串接它們 -------------
- (void)enumerateKeysAndObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (NS_NOESCAPE ^)(KeyType key, id obj, BOOL *stop))block {
}
// ------------- 方法實(shí)現(xiàn)時(shí),如果參數(shù)過長,則令每個(gè)參數(shù)占用一行,以冒號對齊 -------------
- (void)addObserver:(id)observer
selector:(SEL)aSelector
name:(nullable NSNotificationName)aName
object:(nullable id)anObject {
}
代碼注釋
注釋規(guī)范
- 優(yōu)秀的代碼大部分是可以自描述的,完全可以用代碼本身來表達(dá)它到底在干什么,而不需要注釋的輔助。
- 即使代碼有自描述,以下三種情況也必須寫好注釋:
- 公共接口(注釋是用來快速告訴閱讀代碼的人,當(dāng)前類實(shí)現(xiàn)什么功能)
- 涉及到專業(yè)知識的代碼(注釋要說明實(shí)現(xiàn)原理和思想)
- 容易產(chǎn)生歧義的代碼(
嚴(yán)格來說,容易讓人產(chǎn)生歧義的代碼是不允許存在的) - 備注:除了上述三種情況,如果別人還是只能依靠注釋才能讀懂你的代碼,就要反思你寫的代碼了
- 對于注釋的內(nèi)容,相對于
做了什么,更應(yīng)該說明為什么這么做
import注釋
- 如果有一個(gè)以上的
import語句,就要對這些頭文件進(jìn)行分組 - Controller中
// 分類
// 工具類
// Controllers
// Views
// Models
// Managers
- View中
// 分類
// 工具類
// Views
// Models
屬性注釋
- 使用快捷鍵
command+option+/即可自動生成注釋模板,直接看示例:
/// 賬戶名稱
@property (nonatomic, copy) NSString *account;
方法聲明的注釋
- 使用快捷鍵
command+option+/即可自動生成注釋模板,直接看示例:
/// <#Description#>
/// @param anObject <#anObject description#>
/// @param index <#index description#>
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
}
代碼塊注釋
- 單行的用
//+空格開頭 - 多行的采用
/** */注釋
TODO
- 使用
//TODO:來標(biāo)記一些未完成的地方,這里的 TODO 和 // 之間的空格就不需要了
代碼格式
指針 * 的位置
- 靠近變量,示例:NSString
*_userName;
方法的聲明(定義)
- 在 - 、+ 和 返回值 之間留一個(gè)空格,方法名和返回值之間不留空格,方法名和參數(shù)之間不留空格,示例:
- (void)insertSubview:(UIView *)viewatIndex:(NSInteger)index {};
代碼縮進(jìn)
- 在 Xcode > Preferences > Text Editing 將 Tab 和 Indent都設(shè)置為
4個(gè)空格 - 多個(gè)方法的聲明(定義),Method1 與 Method2 之間空一行
- 一元運(yùn)算符與變量之間
沒有空格,示例:!varName; - 二元運(yùn)算符與變量之間
必須有空格,示例:(邏輯運(yùn)算) ? 選項(xiàng)1 : 選項(xiàng)2;
不要出現(xiàn)一行多句代碼
controller中的方法分組
#pragma mark - Life Cycle Methods
// 這里是控制器的生命周期方法匯總
// 不要在viewDidLoad方法里寫view的初始化邏輯,封裝到屬性中,這里只做addSubViews的操作
#pragma mark - Intial Methods
// 這里是初始化方法匯總
#pragma mark - Network Methods
// 這里是網(wǎng)絡(luò)請求方法匯總
#pragma mark - Override Methods
// 這里是重載的父類的方法匯總
#pragma mark - Event Response Methods
// 這里是事件方法匯總,比如:按鈕點(diǎn)擊事件,手勢響應(yīng)方法,定時(shí)器響應(yīng)方法等
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
// 這里是代理相關(guān)的方法,可以是【系統(tǒng)代理方法】或者【自定義的代理方法】
// 這里【必須】寫上代理名稱,方便別人查看
#pragma mark - Public Methods
// 這里是對外公開的方法匯總
#pragma mark - Private Methods
// 一般情況這種私有方法分類是不應(yīng)該出現(xiàn)的,如果一個(gè)方法實(shí)在在上面的分類中找不到放的位置就放到這里吧
#pragma mark - getters and setters
// 這里是一些屬性的讀寫方法,之所以放到最后是為了不至于因?yàn)檫@里的屬性太多導(dǎo)致主要代碼被擠到很后面
- 控制器中視圖的生命周期的各個(gè)階段適合做的事情
- viewDidload
- 在此方法里
只做addSubview的事情,subView的初始化放到getter中去做,這樣職責(zé)比較清晰 -
不要在updateViewConstraints方法里做add constraints,建議在此方法創(chuàng)建Constraints并添加,但是最好將創(chuàng)建并添加Constraints封裝到一個(gè)方法(如:- (void)layoutPageSubviews{ })中,然后在viewDidload調(diào)用此方法,避免viewDidload里代碼行數(shù)太多
- 在此方法里
- viewWillAppear
- 更新Form數(shù)據(jù)
-
缺點(diǎn):pop回來也會更新數(shù)據(jù),會導(dǎo)致網(wǎng)絡(luò)請求增加
- viewWillLayoutSubviews
orviewDidLayoutSubviews- 在這里設(shè)置布局
- viewDidAppear
- 做Notification的監(jiān)聽之類的事情
- 如果控制器view是
UIScrollView及其子控件,那么viewWillLayoutSubviews和viewDidLayoutSubview會調(diào)用的非常頻繁,因?yàn)閁IScrollView及其子控件在導(dǎo)航控制器中受contentInset屬性的影響導(dǎo)致布局改變,就會調(diào)用viewWillLayoutSubviews和viewDidLayoutSubview
- viewDidload
view中的方法分組
#pragma mark - Life Cycle Methods
// 這里是view的生命周期方法匯總
#pragma mark - Intial Methods
// 這里是初始化方法匯總
#pragma mark - Override Methods
// 這里是重載的父類的方法匯總
#pragma mark - Event Response Methods
// 這里是事件方法匯總,比如:按鈕點(diǎn)擊事件,手勢響應(yīng)方法,定時(shí)器響應(yīng)方法等
#pragma mark - Public Methods
// 這里是對外公開的方法匯總
#pragma mark - Private Methods
// 一般情況這種私有方法分類是不應(yīng)該出現(xiàn)的,如果一個(gè)方法實(shí)在在上面的分類中找不到放的位置就放到這里吧
#pragma mark - getters and setters
// 這里是一些屬性的讀寫方法,之所以放到最后是為了不至于因?yàn)檫@里的屬性太多導(dǎo)致主要代碼被擠到很后面
大括號寫法規(guī)范
- 首先看一下蘋果默認(rèn)生成的代碼中大括號的寫法
- (void)viewDidLoad {
[super viewDidLoad];
}
- 對于其他使用場景左括號跟在第一行后邊,留一個(gè)空格,如下示范
if (<#condition#>) {
<#statements#>
} else {
<#statements#>
}
while (<#condition#>) {
<#statements#>
}
編碼規(guī)范
if語句
- 不要使用過多的分支,
要善于使用return來提前返回錯(cuò)誤的情況,把最正確的情況放到最后返回,示例:
image.png -
條件過多,過長的時(shí)候應(yīng)該換行
image.png -
條件語句的判斷建議:變量在右,常量在左
image.png
for語句
- 示范
for (<#type *object#> in <#collection#>) {
<#statements#>
}
- 盡量使用
for in循環(huán)語句
switch語句
- 示范
switch (<#expression#>) {
case <#constant#>:
<#statements#>
break;
default:
break;
}
- 對于switch語句中的各個(gè)case中的業(yè)務(wù)
邏輯語句大于一句的需要使用大括號闊起來
switch (<#expression#>) {
case <#constant#>: {
<#statements#>
}
break;
default: {
}
break;
}
-
使用枚舉類型時(shí),不能有default分支,因?yàn)樵?code>switch語句使用枚舉類型的時(shí)候,如果使用了default分支,新增枚舉類型時(shí)就無法通過編譯器來檢查到新增的類型 -
除了使用枚舉類型以外,都必須有default分支
函數(shù)
- 一個(gè)函數(shù)只做一件事(單一原則)
- 對輸入?yún)?shù)的正確性和有效性進(jìn)行檢查,參數(shù)錯(cuò)誤立即返回,
要善于使用return來提前返回錯(cuò)誤的情況,把最正確的情況放到最后返回 - 如果在不同的函數(shù)內(nèi)部有相同的功能,應(yīng)該把相同的功能抽取出來單獨(dú)作為另一個(gè)函數(shù)
- 將函數(shù)內(nèi)部比較復(fù)雜的邏輯提取出來作為單獨(dú)的函數(shù)
去除iOS項(xiàng)目中無用的警告
切記不是所有警告都可以直接忽略的
- 只屏蔽
無關(guān)緊要的警告,目的是讓我們更加快速找到需要修復(fù)的警告 - 有些警告是代碼編寫本身有誤,可能引起B(yǎng)ug,需要及時(shí)修復(fù)
執(zhí)行代碼診斷命令
- 在Xcode中運(yùn)行
Build & Analyze(??B)后你會得到的驚人結(jié)果 - 這是Clang 的更細(xì)膩,更深沉一面的功能
- Clang是 C / Objective-C 的前端的 LLVM 編譯器。
- Clang對 Objective-C 的語義和語法有著深刻的理解,而且更重要的原因是現(xiàn)在 Objective-C 已經(jīng)是這樣一個(gè)有能力的語言了。
打開Treat Warnings as Errors配置
- 在
Build Settings搜索Treat Warnings as Errors并設(shè)置為YES - 設(shè)置
-Weverything標(biāo)志 - 這樣設(shè)置后大部分項(xiàng)目是無法編譯過的,可以有針對性的進(jìn)行優(yōu)化,我個(gè)人支持這個(gè)建議,并鼓勵(lì)其他開發(fā)者更嚴(yán)肅的對待編譯警告
去除pod庫的警告
- 在podfile文件中添加如下配置
# 忽略所有警告
inhibit_all_warnings!
# 忽略指定庫的警告
pod 'AFNetworking', :inhibit_warnings => true
- 然后執(zhí)行:
pod install
警告:Pointer is missing a nullability type specifier
- 蘋果提供了兩個(gè)宏來去除此類警告
- 使用此宏包住多個(gè)屬性使其具備
nonnull(不可空),然后僅對需要nullable(可空)的改下就行
NS_ASSUME_NONNULL_BEGIN
coding...
NS_ASSUME_NONNULL_END
- 使用方法:在
.h文件的頭部和尾部添加兩個(gè)宏
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface XXXModel : NSObject
@end
NS_ASSUME_NONNULL_BEGIN
- 使用了上面的宏后出現(xiàn)警告
Null passed to a callee that requires a non-null argument,對需要nullable(可空)的改下就可以去除此警告
打開Treat Incompatible Pointer Type Warnings as Errors開關(guān)
- 開啟后類型不兼容會報(bào)錯(cuò),而不是警告
去除directory not found for option警告
- 在
TARGETS選在對應(yīng)的項(xiàng)目 - 在
Build Settings里找到Library Search Paths和Framework Search Paths刪除找不到的路徑即可
去除This block declaration is not a prototype警告
- 在
Build Settings里找到Strict Prototypes設(shè)置為NO即可
關(guān)閉工程中指定類型的警告的設(shè)置步驟
- 選中指定類型的警告,選擇
Reveal in Log,如下圖操作
右鍵Reveal in Log查看指定類型的警告 -
則會顯示下圖
指定類型的警告 -
[-Wshorten-64-to-32]中括號中的就是警告類型 -
-W表示打開指定類型的警告 -
-Wno-表示關(guān)閉指定類型的警告 - 將
-W換成-Wno-變成-Wno-shorten-64-to-32 - 將此類型的警告的忽略配置添加到
Other Warning Flags中,如下圖
忽略指定類型的警告
如何避免誤使用高版本API導(dǎo)致的崩潰問題
- 打開
-Wunguarded-availability在調(diào)用高版本API時(shí)候報(bào)warning,為避免warning過多而忽視,用-Werror-unguarded-availability標(biāo)記強(qiáng)制編譯不過
使用高版本API檢測配置 - 如果代碼本身安全(使用了
respondsToSelector:保護(hù)),可以用下面兩種方式去除警告- 方式一
// 忽略警告:誤使用高版本API版 #define K_Warning_Ignored_Start_ApiCheck _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wunguarded-availability\"") // 這里寫忽略警告的code #define K_Warning_Ignored_End_ApiCheck _Pragma("clang diagnostic pop")- 方式二
@available
if (@available(iOS 8.0, *)) { // 這里寫忽略警告的code } - 針對pod需要在
.podspec文件中添加compiler_flags配置








