編寫的公司iOS端代碼規(guī)范 (已經(jīng)脫敏了)
注釋規(guī)范
好的注釋可以大大的提高代碼的閱讀性邻奠、也能讓自己快速的看懂自己幾個(gè)月前寫的代碼(……_)
-
類注釋需要在 interface 之上,這樣可以在其他地方看到該類的注釋
/****************************************************************************************************************************** 這里寫注釋 ******************************************************************************************************************************/ @interface ViewController : UIViewController
-
UI類注釋 在.h文件頭部添加描述和當(dāng)前UI的雛形,"X" 代表不隱藏, “O”代碼隱藏;當(dāng)該cell有多種排版時(shí),需要一一畫出來
/** 列表模板視圖2: -------------------------- | XXXXXXXXXXXXX ----- | | XXXXX | | | | xxxxxxxxxxx | | | | ----- | | xx OO OOO xxx | -------------------------- */
-
功能類注釋 除了UI界面外,其他的類最好在.h文件頭部中添加一個(gè)描述,闡述一下功能和思路,如下
// // xxxxxx.h // bookclub // // Created by youhui on 2018/11/21. // Copyright ? 2018年 luke.chen. All rights reserved. // /****************************************************************************************************************************** 功能: 用來解決vc生命周期已經(jīng)執(zhí)行,但是卻被其他圖層擋住,導(dǎo)致無法準(zhǔn)確的拿到頁面“可見化”的時(shí)機(jī) 通過傳入target 在debut、each 兩個(gè)block中可以分別拿到首次为居、每次的“可見化”回調(diào) 思路: 1碌宴、采用CADisplayLink 這個(gè)和屏幕同頻率的定時(shí)器,監(jiān)聽keywindow 是否被占用.通過block 傳回給調(diào)用方的頁面 debut(初次出現(xiàn)),each(每次出現(xiàn)),eachDismiss(每次消失)的生命周期時(shí)機(jī) 2、判定keywindow是否被占用: 存在不是tabBar的容器但卻覆蓋屏幕的圖層,則判定為window被占用 (有可能存在兩個(gè)視圖拼接起來覆蓋屏幕的圖層,當(dāng)前版本尚未發(fā)現(xiàn),后續(xù)優(yōu)化) ******************************************************************************************************************************/
-
函數(shù)注釋--.h文件
/** 開始監(jiān)聽位置(一次),并在block中把 地址/經(jīng)緯度傳回來 @param needAreaId NO:不會(huì)請求區(qū)域碼 @param addressBlock callBackBlock */ - (void)xxxxxx:(BOOL)needAreaId addressBlock:(BCLocationManagerAddressBlock)addressBlock;
-
函數(shù)注釋--.m文件 一般情況下,在.m文件中,不需要那么詳細(xì)的注釋,簡單注釋能看明白函數(shù)的作用即可,太長會(huì)影響可讀性,需要查看詳細(xì)的細(xì)節(jié),可以到.h中查看
//開始監(jiān)聽位置(一次),并在block中把 地址/經(jīng)緯度傳回來 - (void)xxxxxx:(BOOL)needAreaId addressBlock:(BCLocationManagerAddressBlock)addressBlock { }
-
屬性注釋--.h文件 如果必須要傳,要在注釋的首行加上 “@required”
/** @required 每次block @param times “可見化”次數(shù) */ typedef void(^BCLifeCircleManagerEachBlock)(long long times);
-
屬性注釋--.m文件 緊貼著屬性的定義后面注釋
@property (nonatomic, readwrite, strong) CADisplayLink *displayLink;//屏幕刷新同步器
-
業(yè)務(wù)代碼注釋 在編寫業(yè)務(wù)代碼時(shí),往往會(huì)是一串又長又臭的代碼,(原則上,一個(gè)函數(shù)最好不要超過100行)這個(gè)時(shí)候,需要對代碼進(jìn)行模塊話的注釋,同一類功能添加一個(gè)注釋.如下所示
//用戶角色改變 - (void)xxxxx:(NSNotification *)noti { //重置 xxxxx //重新請求 xxxxx //清除數(shù)據(jù) xxxxx xxxxx //音頻重置 xxxx xxxx //視頻重置 xxxxx xxxxx }
頁面規(guī)范
- 推薦使用懶加載自動(dòng)懶加載插件,具有高可閱讀性的優(yōu)點(diǎn)
- 復(fù)制以下代碼 在Xcode 中 添加一個(gè)用戶自定義代碼塊蒙畴,支持快捷縮寫關(guān)聯(lián)出頁面腳手架
-
ViewController
//生命周期 #pragma mark - Lifecycle - (void)dealloc { NSLog(@"%@dealloc",self); } - (void)viewDidLoad { [super viewDidLoad]; [self loadSubviews]; // Do any additional setup after loading the view. } //視圖的初始化贰镣,層級結(jié)構(gòu)都在這里 #pragma mark - LoadSubViews - (void)loadSubviews { } //視圖的布局,刷新 #pragma mark - LayoutSubViews - (void)layout { } //重寫父類放這里 #pragma mark - Overwrite //公開方法 #pragma mark - Public Method //隱私方法 #pragma mark - Privacy Method //網(wǎng)絡(luò)請求 #pragma mark - Request <#netRequest#> //通知方法 #pragma mark - Notification Obverser Method //代理 #pragma mark - Delegate //get set 懶加載 #pragma mark - Get and Set
-
View
//生命周期 #pragma mark - Lifecycle - (void)dealloc { NSLog(@"%@dealloc",self); } //視圖的初始化膳凝,層級結(jié)構(gòu)都在這里 #pragma mark - LoadSubViews - (void)loadSubviews { } //視圖的布局碑隆,刷新 #pragma mark - LayoutSubViews // tell UIKit that you are using AutoLayout + (BOOL)requiresConstraintBasedLayout { return YES; } // this is Apple's recommended place for adding/updating constraints - (void)updateConstraints { [self layout]; [super updateConstraints]; } //布局 - (void)layout { } //重寫父類放這里 #pragma mark - Overwrite //公開方法 #pragma mark - Public Method //隱私方法 #pragma mark - Privacy Method //網(wǎng)絡(luò)請求 #pragma mark - Request <#netRequest#> //通知方法 #pragma mark - Notification Obverser Method //代理 #pragma mark - Delegate //get set 懶加載 #pragma mark - Get and Set
-
Model
//生命周期 #pragma mark - Lifecycle //重寫父類放這里 #pragma mark - Overwrite //公開方法 #pragma mark - Public Method //隱私方法 #pragma mark - Privacy Method //網(wǎng)絡(luò)請求 #pragma mark - Request <#netRequest#> //代理 #pragma mark - Delegate //get set 懶加載 #pragma mark - Get and Set
-
屬性規(guī)范 (屬性方面,主要關(guān)注 .h 暴露出來的屬性蹬音。)
- 寫代碼中最難的一個(gè)步驟之一可以說是命名,在給屬性命名時(shí),在不是非常的長的情況下可以適當(dāng)?shù)淖龅奖M量做到顧名思義,如需要給view 傳遞一個(gè)數(shù)據(jù)源,下面給出了三種命名:
array
dataArray
interfaceDataArray(**最優(yōu),能一眼就看出該數(shù)組的作用**)
- 在.h文件的頭尾分別加上 NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END干跛, 在這兩個(gè)宏之間的代碼,所有簡單指針對象都被假定為nonnull
- "," ")" "@property" 之后需要加 空格 祟绊; "*****"之前需要加 空格
@property空格(nonatomic,空格readwrite,空格strong)空格NSArray空格*interfaceDataArray;
- 不可變的屬性使用Copy, 可變的屬性使用Strong(在需要保護(hù)封裝性的前提下)
- 如果必須要賦值,要在注釋中使用 @required注明
- 功能上不需要就不暴露哥捕,暴露了就需要足夠的信息或備注來說明牧抽,如下,interfaceDataArray 屬性可以說明
- 盡量使用Fundation 框架的數(shù)據(jù)類型遥赚,如使用CGFloat 來替換float
-
第一檔:只有一個(gè)簡單的nonatomic 和 strong 的來修飾
@property (nonatomic, strong) NSArray *interfaceDataArray;
-
第二檔:除了nonatomic 和 strong 之外 還多了一個(gè)readOnly字段扬舒,表明了該屬性為“只讀”屬性,你并不需要費(fèi)勁心思的給他賦值
@property (nonatomic, readOnly, strong) NSArray *interfaceDataArray;
-
第三檔:nullable類型的屬性可以說明支持 nil
@property (nonatomic, readwrite, strong, nullable) NSArray *interfaceDataArray;
-
第四檔:容器屬性的 內(nèi)部值類型凫佛,最好能一目了然讲坎,這樣寫就可以很直觀的看出該屬性是一個(gè)“包含了字符串?dāng)?shù)組的一個(gè)大數(shù)組”
@property (nonatomic, readwrite, strong, nullable) NSArray<NSArray<NSString *> *> *interfaceDataArray;
-
第五檔:這里用copy替換掉了strong孕惜,被其他數(shù)組賦值時(shí),會(huì)重新拷貝一份晨炕,不會(huì)和賦值的數(shù)組內(nèi)存地址一致衫画,從而保護(hù)了該屬性的封裝性。
@property (nonatomic, readwrite, copy, nullable) NSArray<NSArray<NSString *> *> *interfaceDataArray;
-
第六檔:如果你有一個(gè)很詳細(xì)的注釋瓮栗,那么你完全可以忘記之前的五種類型~
/** @required 外部傳入數(shù)據(jù)源 在外部數(shù)據(jù)更新時(shí)需要對該屬性進(jìn)行賦值刷新 */ @property (nonatomic, readwrite, copy, nullable) NSArray<NSArray<NSString *> *> *interfaceDataArray;
-
函數(shù)規(guī)范
- 遵守駝峰命名
- 一個(gè)文件里面統(tǒng)一使用一種括號(hào)模式
- (void)aaa {
}
- 不使用 with削罩、and 來連接各個(gè)參數(shù)
- 盡量采用 instancetype 來替換 id
- 提供初始化方法時(shí),盡量考慮到使用方的便利费奸,可以提供多個(gè)不同入?yún)⒌姆椒?例如AF框架中的AFHTTPSessionManager 是這樣定義初始化函數(shù)的:
- (instancetype)init {
return [self initWithBaseURL:nil];
}
- (instancetype)initWithBaseURL:(NSURL *)url {
return [self initWithBaseURL:url sessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
return [self initWithBaseURL:nil sessionConfiguration:configuration];
}
- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration {
......
}
宏定義規(guī)范
- NSUserDefault (BC_ + UDF_ + 命名),如下所示
#define BC_UDF_xxxxx @"BC_UDF_xxxxx"http://購買專輯成功
- 通知 (BC_ + NOTI_ + 命名),如下所示
#define BC_NOTI_aaaaa @"BC_NOTI_aaaaa"http://節(jié)目列表頁面購買成功
- 常數(shù) (BC_ + K_ + 命名),如下所示 (包括字符串弥激、數(shù)字等固定常量)
#define BC_K_bbbbb @"2"http://每日薦聽
- 計(jì)算 存在計(jì)算的宏定義,需要對計(jì)算部分添加括號(hào),否則在用該宏參與計(jì)算的時(shí)候會(huì)因?yàn)榉?hào)優(yōu)先級不一定能得到想要的結(jié)果,如下所示
#define BC_K_GetSum(a,b) a + b
BC_K_GetSum(1,2) * 5 //11
//所以應(yīng)該加上括號(hào)
#define BC_K_GetSum(a,b) (a + b)
BC_K_GetSum(1,2) * 5 //得到想要的15
- 生產(chǎn)環(huán)境禁止Log (需要在xcode中添加DEBUG 配置,并復(fù)制以下代碼到項(xiàng)目對應(yīng)的文件中)
#ifdef DEBUG
#define NSLog(...) NSLog(__VA_ARGS__)
#define debugMethod() NSLog(@"%s", __func__)
#else
#define NSLog(...)
#define debugMethod()
#endif
- Weak / Strong
#define WeakObj(o) autoreleasepool{} __weak typeof(o) o##Weak = o;
#define StrongObj(o) autoreleasepool{} __strong typeof(o) o = o##Weak;
//例如
@WeakObj(self);
self.block = ^{
@StrongObj(self);
[self xxxx];
}];
枚舉規(guī)范
-
NS_ENUM
- NSInteger/NSUInteger 類型,直接賦值
typedef NS_ENUM(NSInteger, BCHttpResponseType) { BCHttpResponseTypeA = 0, //這里是注釋 BCHttpResponseTypeB = 1, //這里是注釋 BCHttpResponseTypeC = -1, //這里是注釋 BCHttpResponseTypeD = -2, //這里是注釋 BCHttpResponseTypeE = -3, //這里是注釋 BCHttpResponseTypeF = -4, //這里是注釋 BCHttpResponseTypeG = -5, //這里是注釋 };
-
NS_OPTIONS
- 固定 NSUInteger 類型,一般用來進(jìn)行二進(jìn)制左右位移計(jì)算,當(dāng)然也可以直接賦值.使用 NS_OPTIONS 的優(yōu)點(diǎn)是可以通過 “|” 符號(hào)來鏈接多個(gè)枚舉,達(dá)到多選的效果.
//顆粒化視頻遮罩層的組件愿阐,可用|自由組合不同層微服,高度可定制 typedef NS_OPTIONS(NSUInteger, BCVideoMaskLayer) { BCVideoMaskLayerA = 0,//這里是注釋 BCVideoMaskLayerB = 1 << 0,//這里是注釋 BCVideoMaskLayerC = 1 << 1,//這里是注釋 BCVideoMaskLayerD = 1 << 2,//這里是注釋 BCVideoMaskLayerE = 1 << 3,//這里是注釋 BCVideoMaskLayerF = 1 << 4,//這里是注釋 BCVideoMaskLayerG = 1 << 5,//這里是注釋 BCVideoMaskLayerH = 1 << 6,//這里是注釋 BCVideoMaskLayerI = 1 << 8,//這里是注釋 BCVideoMaskLayerJ = 1 << 9,//這里是注釋 BCVideoMaskLayerK = 1 << 10,//這里是注釋 BCVideoMaskLayerL = 1 << 11,//這里是注釋 BCVideoMaskLayerM = 1 << 12,//這里是注釋 BCVideoMaskLayerN = 1 << 13,//這里是注釋 BCVideoMaskLayerO = 1 << 14,//這里是注釋 BCVideoMaskLayerP = 1 << 15,//這里是注釋 BCVideoMaskLayerQ = 1 << 16,//這里是注釋 BCVideoMaskLayerR = 1 << 17,//這里是注釋 BCVideoMaskLayerS = 1 << 18,//這里是注釋 BCVideoMaskLayerT = 1 << 19,//這里是注釋 };
內(nèi)存規(guī)范
Dealloc 一定要執(zhí)行
頁面:在頁面的腳手架中的Lifecycle分類,需要實(shí)現(xiàn)dealloc 函數(shù),并打印當(dāng)前的類,在頁面功能開發(fā)完成之后,最好要測試一下頁面的dealloc 是否被調(diào)用.如果沒有被及時(shí)調(diào)用,那么就存在內(nèi)存泄漏,需要對頁面的代碼進(jìn)行排查.-
添加的監(jiān)聽需要在合適的地方進(jìn)行釋放
- 如通知監(jiān)聽,定時(shí)器等
-
block的循環(huán)引用.
- 原因 在block中很容易因?yàn)楫?dāng)前類持有該block,而block中的代碼又引用當(dāng)前類,產(chǎn)生循環(huán)引用,無法釋放.
- 處理 在block外部定義弱引用(self),在block內(nèi)部強(qiáng)引用(self)
-
delegate 沒有主動(dòng)釋放
- 原因 如果當(dāng)前類持有了如友盟單例等代理,那么會(huì)因?yàn)閱卫恢贝嬖?導(dǎo)致單例會(huì)一直對當(dāng)前類進(jìn)行持有,導(dǎo)致無法釋放.
- 處理 需要在頁面退出時(shí)或者其他合適的時(shí)機(jī),手動(dòng)注銷delegate
-
(例子)正常點(diǎn)擊 A 的返回按鈕退出
這里想要如期所愿的釋放 B、C,那么需要移除B缨历、C 身上的所有引用,如上圖所示,B身上有兩個(gè)箭頭,代表兩個(gè)引用.那么當(dāng)A釋放時(shí),B身上只剩下一個(gè)引用,因?yàn)锳釋放,D身上的唯一引用也被移除,D也隨之被釋放了,所以間接移除了D對B的引用,B在A釋放的同時(shí)可以正常的被釋放;
那么問題來了,那么當(dāng)A釋放時(shí),按照上面的邏輯,D和A對C的引用也會(huì)被隨之移除,但是因?yàn)镋還保持著對C的引用,所以C并沒有隨A的釋放而釋放.所以,想要讓C也跟隨A一起釋放,可以在A的dealloc 中手動(dòng)解除E對C的持有即可.
graph LR A(SuperVC-A) -->|持有|B((ChildVC-B)) A -->|持有|C((ChildVC-C)) A -->|持有|D((View-D)) D -->|持有|B D -->|持有|C E(ShareInstance-E) -->|持有|C
-
因系統(tǒng)或者其他情況導(dǎo)致的特殊情況.
-
(例子)在子頁面內(nèi)點(diǎn)擊按鈕直接返回到RootVc,且切換到其他tab分欄,結(jié)構(gòu)圖如下所示
這里和上面點(diǎn)擊返回情況不同的是,因在pop到RootVc的同時(shí)切換到其他tab,導(dǎo)致系統(tǒng)無法及時(shí)對RootVc下的一些內(nèi)存進(jìn)行回收.導(dǎo)致在切回到RootVc所在分欄之前,D一直沒有被回收,也就是通常所見的野指針.那么這種情況下,如果還想讓B以蕴、C隨A一直釋放,可以在A釋放的同時(shí),額外手動(dòng)釋放D對的B、C的持有即可
graph LR A(SuperVC-A) -->|持有|B((ChildVC-B)) A -->|持有|C((ChildVC-C)) A -->|持有|D((View-D)) D -->|持有|B D -->|持有|C E(ShareInstance-E) -->|持有|C H(野指針) -->|在點(diǎn)擊按鈕直接退出到root且切換tab的情況下,導(dǎo)致的沒有被釋放|D
-
(例子)在子頁面內(nèi)點(diǎn)擊按鈕直接返回到RootVc,且切換到其他tab分欄,結(jié)構(gòu)圖如下所示
當(dāng)以上的一些方法都找不出問題時(shí),可以通過profile的instruments的來進(jìn)行調(diào)試定位到具體的代碼.
布局規(guī)范
RunLoop 中 蘋果注冊了一個(gè) Observer 監(jiān)聽 BeforeWaiting(即將進(jìn)入休眠) 和 Exit (即將退出Loop) 事件戈二,回調(diào)去執(zhí)行一個(gè)很長的函數(shù),_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()舒裤。這個(gè)函數(shù)里會(huì)遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實(shí)際的繪制和調(diào)整,并更新 UI 界面,而約束布局的視圖一般和這幾個(gè)函數(shù)相關(guān),從而進(jìn)行約束觉吭、布局腾供、繪制 (具體)
[圖片上傳失敗...(image-6a68ff-1560592762925)]-
優(yōu)先使用自動(dòng)布局(masonry)
- ViewController : 可以在viewDidLoad 生命周期中/或者接口返回后手動(dòng)調(diào)用layout
- (void)viewDidLoad { [super viewDidLoad]; [self layout]; } - (void)layout { //此處為布局代碼 }
- View : 使用上面提到的腳手架 ,其中重寫了 requiresConstraintBasedLayout 和 updateConstraints 方法,并在layout 函數(shù)中 進(jìn)行代碼自動(dòng)布局,在需要更新布局時(shí),執(zhí)行[self setNeedsUpdateConstraints] 即可,無需在其他地方主動(dòng)執(zhí)行 layout函數(shù)
#pragma mark - LayoutSubViews // tell UIKit that you are using AutoLayout + (BOOL)requiresConstraintBasedLayout { return YES; } // this is Apple's recommended place for adding/updating constraints - (void)updateConstraints { [self layout]; [super updateConstraints]; } //布局 - (void)layout { //此處為布局代碼 } //在需要更新布局時(shí),調(diào)用setNeedsUpdateConstraints 即可 - (void)xxx { [self setNeedsUpdateConstraints]; }
-
在某些場景可以使用frame布局,如下
- layer層參與布局 (因?yàn)閘ayer 不支持自動(dòng)布局)
- 動(dòng)畫和自動(dòng)布局沖突時(shí),如下,這樣使用就會(huì)發(fā)生一些奇怪的問題
[UIView animateWithDuration:.5 animations:^{ [xxx mas_updateConstraints:^(MASConstraintMaker *make) { xxxx }]; //[xxx layoutIfNeeded]; }];
資源規(guī)范
- Assets 中應(yīng)該建有和模塊相應(yīng)的文件夾,存放該模塊中需要的icon
- icon名字需要統(tǒng)一改為英文名字,并在文件名結(jié)尾標(biāo)明 @2x,@3x ,系統(tǒng)能自動(dòng)識(shí)別兩種資源.獲取對應(yīng)的資源
- gif、plist 等其他文件,因?yàn)椴荒艽娣旁贏ssets中,需要在對應(yīng)的下模塊新建Resource文件夾存放
- 放入資源之前,需要提前檢索是否已經(jīng)有同名的資源
- 不需要的資源,需要定期刪除(可以在該版本結(jié)束時(shí),進(jìn)行刪除,包括不需要的代碼)
第三方庫規(guī)范
- 使用cocoaPods進(jìn)行管理
- 在cocoaPods 的 ".gitignore" 屏蔽文件中,需要加上 Pods/ 鲜滩、Podfile.lock (pod只在本地管理,有其他同事新增時(shí)可以直接pod udpate 新的)
- pods中的每個(gè)三方庫都需要注明作用,如下
# Uncomment the next line to define a global platform for your project
platform :ios, '8.0'
target 'xxxx' do
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
# use_frameworks!
# Pods for xxx
pod 'AFNetworking', '3.2.0' #網(wǎng)絡(luò)
pod 'MBProgressHUD', '1.1.0' #提示框
....
target 'xxxTest' do
inherit! :search_paths
# Pods for testing
end
target 'xxxUITests' do
inherit! :search_paths
# Pods for testing
end
end
MVVM規(guī)范
-
文件夾
m_4752a2380b3175fcbb6d92de2dd9fbbd_r.jpg -
基本規(guī)則
- view 或 vc 持有 viewModel 伴鳖、viewModel 持有 model
- viewModel 不持有view,且最好不引入U(xiǎn)IKit框架
- view 不持有 model
graph LR
A(View/ViewController)-->|持有|B(ViewModel)
B-->|持有|C(Model)
B---|不引入|D(UIKit框架)
解析規(guī)范
- 盡量使用model 來承載數(shù)據(jù),使用 ParseToModel 工具類來解析數(shù)據(jù)(如下代碼所示)
- (xxxxActivityModel *)xxxxModelWithResp:(NSDictionary *)response {
NSDictionary *activityInfo = [response objectForKey:@"bbbb"];
xxxxActivityModel *activityModel = [ParseToModel parseDictionary:activityInfo byClassName:@"xxxxActivityModel"];
activityModel.aaa = [[response objectForKey:@"aaaa"] longLongValue];
return activityModel;
}
- 解析出的數(shù)據(jù)可能是null,在需要使用時(shí),需要進(jìn)行判空處理(如下代碼所示)
NSString *xxxx = [hotBook bc_jsonString:@"xxxx"]?[hotBook bc_jsonString:@"xxxx"]:@"";
NSString *aaaa = [hotBook bc_jsonString:@"aaaa"]?[hotBook bc_jsonString:@"aaaa"]:@"";
return @{@"xxxx":xxxx, @"aaaa":aaaa, @"cccc": @"c"};
跳轉(zhuǎn)規(guī)范
- 在路由中有配置的跳轉(zhuǎn)使用路由跳轉(zhuǎn)
- 如果路由中沒有配置,那么放在對應(yīng)的vc中,盡量別放在view/viewModel/model 中
ps:喜歡的點(diǎn)個(gè)贊哈,或者贊賞支持~