Objective-C代碼編寫規(guī)范
1. 命名規(guī)范
我們盡可能遵守Apple的命名約定,其推薦使用長的、描述性強(qiáng)的方法名和變量名,使其閱讀起來更加清晰易懂。不能隨意使用縮寫,導(dǎo)致其他人員閱讀代碼困難。 附:The Coding Guide for Cocoa
1.1 前綴
項(xiàng)目名稱、類名、文件名都應(yīng)該保持一致的前綴名。根據(jù)Apple Guide的建議,類名前綴應(yīng)該使用2個(gè)英文字母以上最好,因?yàn)锳pple寫的框架都是使用2個(gè)英文字母開頭,使用3個(gè)字母能有效防止類名重復(fù),避免影響工程。
1.2 方法命名規(guī)范
方法一般不能用init、set開頭進(jìn)行命名,如果不是寫初始化方法不要用init進(jìn)行開頭;如果不是屬性的set方法,不要用set作為方法的前綴。
根據(jù)Cocoa命名方法規(guī)則,我們應(yīng)該遵守這幾個(gè)點(diǎn):
a. 使用小寫字母開頭,后面嵌套連接的字母使用大寫開頭。不過在寫 Category Method 的時(shí)候,我們比較習(xí)慣使用 JSD_method 的方式,而非遵守所有的方法命名規(guī)則 jsd_method,這個(gè)我覺得只要統(tǒng)一起來就好了。
b. 對于采取動作行為的方法,使用動詞開頭,但是不要直接使用do或者does;
c. 每個(gè)方法參數(shù)前必須帶有相同或者能清晰表達(dá)其原意的關(guān)鍵字;
d. 子類創(chuàng)建相對父類更加詳細(xì)功能的方法時(shí)應(yīng)該把新增參數(shù)添加在原有方法的后面;
e. 假如方法名過長的時(shí)候可以采用每個(gè)參數(shù)單獨(dú)占一行的規(guī)則,并保持每個(gè)參數(shù)分號:對齊的方式排列;
f. 實(shí)例方法和類方法 (-/+) 符號后面應(yīng)該保持一個(gè)空格,如:- (void)。
1.3 控件命名規(guī)范
對于命名一定不要簡寫,UILabel結(jié)尾加上Label,UIImageView結(jié)尾加上ImageView等等讓其他人看名字就知道變量的用法和屬于什么控件。
1.4 Block 的命名規(guī)范
之前研究過很多的第三方命名,對于Apple官方的沒找到,有CallBack、Complete、Block結(jié)尾的,還有CompletionHandler結(jié)尾的。我看到很多的結(jié)尾都是用CompletionHandler,大部分命名是Block,我們按照Block來命名。
例子:
eg 1. 對于#define宏命名,單詞應(yīng)全部大寫,單詞之間用_分割。
建議的寫法:
#define NS_AVAILABLE_IOS 3.0
不建議的寫法:
#define NSAvailableIos 3.0
eg 2. 類型常量
多用類型常量,少用#define。
建議的寫法:
const NSTimeInterval kAnimationDuration = 0.3;
不建議的寫法:
#define ANIMATION_DURATION 0.3
當(dāng)使用類型常量定義時(shí),若常量局限于某編譯單元,也就是實(shí)現(xiàn)文件里面,則使用k作為前綴,如:kCountdownTime;
若常量在類之外公開出來,則需要使用規(guī)定的類名作為前綴。如:SettingCountdownTime。
約定:在我們自己定義NSNotification的時(shí)候應(yīng)該把通知的名字定義為一個(gè)字符串常量,就像把我們暴露給其他類的字符串常量一樣。使用extern關(guān)鍵字將其在.h文件聲明,并且在.m文件對其定義。
.h聲明:
UIKIT_EXTERN NSString *const BWWillUpdateListNotification;
UIKIT_EXTERN const NSInteger MaxLeadCharCount;
.m實(shí)現(xiàn):
NSString *const BWWillUpdateListNotification = @"kWillUpdateListNotification";
const NSInteger MaxLeadCharCount = 44;
- 關(guān)于通知名稱,推薦使用
Did/Will這樣的動詞連接表示最好;- 如果導(dǎo)入的是
UIKit類,就使用UIKIT_EXTERN;Foundation類,就使用FOUNDATION_EXTERN;如果只在本類使用,只用寫實(shí)現(xiàn),不用寫聲明。對于只在內(nèi)部聲明的
const,需要添加static,這個(gè)我覺得可以不加,但是無法看到蘋果的實(shí)現(xiàn),不知道蘋果的規(guī)范怎么寫的。
2. 代碼格式
2.1 間距
a. 方法的大括號和其他的大括號(if/else/switch/while等等)應(yīng)始終和聲明在同一行開始,在新的一行結(jié)束;
b. 方法之間應(yīng)該正好空一行,這樣有助于視覺清晰度和代碼組織性。在方法中的功能塊之間也應(yīng)該使用空白行分開。
c. switch-case中,case后的代碼如果多于一行,則需要用{}包裹,建議所有case和default后的代碼塊均用{}包裹。
建議的寫法:
if (user.isHappy) {
// Do something cool
} else {
// Do something else
}
不建議的寫法:
if (user.isHappy)
{
// Do something cool
}
else
{
// Do something else
}
2.2 屬性關(guān)鍵字首個(gè)應(yīng)該是原子性,再到內(nèi)存管理關(guān)鍵詞。如果需要讀寫關(guān)鍵字的話,其排在第二位。
建議的寫法:
@property (nonatomic, readonly, copy) NSString *name;
2.3 推薦使用三元運(yùn)算符進(jìn)行運(yùn)算,它能使代碼更加簡潔、清晰。
當(dāng)三元運(yùn)算符的第二個(gè)參數(shù)(也就是if分支)返回的對象和條件語句中已經(jīng)檢查的對象一樣的時(shí)候,下面的表達(dá)方式更靈巧:
建議的寫法:
result = object ? : [self createObject];
不建議的寫法:
result = object ? object : [self createObject];
2.4 黃金大道規(guī)則(Golden Path)
在使用條件語句編程時(shí),盡管會遇到邏輯復(fù)雜的代碼,我們也應(yīng)該盡量避免其嵌套導(dǎo)致閱讀困難。
盡量使用return將不符合邏輯的直接忽略掉,然后將要執(zhí)行的代碼放到判斷語句外面,減少嵌套。
建議的寫法:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
// Do something important
}
不建議的寫法:
- (void)someMethod {
if ([someOther boolValue]) {
// Do something important
}
}
2.5 避免尤達(dá)表達(dá)式
不要使用尤達(dá)表達(dá)式,尤達(dá)表達(dá)式是指拿一個(gè)常量去和變量比較。
建議的寫法:
if ([myValue isEqual:@42]) {
}
不建議的寫法:
if ([@42 isEqual:myValue]) {
}
3. 文件引入方式
在.h文件中盡量使用@class聲明文件,直到.m文件中真正需要的時(shí)候再使用#improt進(jìn)行引用,這樣能有效的防止相互引用、編譯失敗、不容易查找的bug等;這樣做的缺點(diǎn)是:.m文件還要#import其他類。
建議的寫法:
@class UIView, UIImage;
不建議的寫法:
@class UIView;
@class UIImage;
#improt頭文件順序:可以先引入系統(tǒng)文件,依次到Public.h,最后才到我們自己編寫的文件(可以大家一起商量一下順序)。記得檢查引用文件名稱,避免引入了沒有使用到的文件,發(fā)現(xiàn)后應(yīng)及時(shí)清除。
盡量按照系統(tǒng)類、第三方類、自己寫的類順序?qū)?,中間不能有空行。
寫法模板:
#import <系統(tǒng)庫>
#import <第三方庫>
#import "其他類"
建議的寫法:
#import <UIKit/UIKit.h>
#import <Google/Analytics.h>
#import "GBOrderEmptyView.h"
不建議的寫法:
#import "GBOrderEmptyView.h"
#import <UIKit/UIKit.h>
#import <Google/Analytics.h>
4. 不允許外界修改的屬性要設(shè)置 readonly
大家平時(shí)設(shè)置屬性默認(rèn)是可讀可寫的,但是這樣很容易對別人造成誤解,以為可以賦值。因此,對于只能獲取的屬性,一定寫readonly。
@property (nonatomic, readonly, copy) NSString *name;
5. BOOL 類型屬性的聲明
屬性set不要帶is,get要帶is。
建議的寫法:
@property (nonatomic, assign, getter=isUserLogin) BOOL userLogin;
不建議的寫法:
@property (nonatomic, assign) BOOL userLogin;
6. 對于初始化,一定要使用類對應(yīng)的初始化方法
a. UIView的對應(yīng)初始化方法為:- (instancetype)initWithFrame:(CGRect)initWithFrame
b. UIViewController的對應(yīng)初始化方法為:- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil
防止初始化用init、new等,沒經(jīng)過系統(tǒng)進(jìn)行設(shè)置一些默認(rèn)屬性而造成bug。
c. 對于局部變量,盡量進(jìn)行初始化。局部變量要初始化,屬性有默認(rèn)的值,所以我們不必須對屬性進(jìn)行初始化。
建議的寫法:
int index = 0;
不建議的寫法:
int index;
7. 對于一些狀態(tài)、選項(xiàng),使用枚舉,盡量少用數(shù)字、字符串判斷狀態(tài)。
建議的寫法:
typedef NS_ENUM(NSUInteger, HomeViewState) {
HomeViewStateNoData,
HomeViewStateFailure,
HomeViewStateItemList,
HomeViewStateBannerList,
};
if (state == HomeViewStateNoData) { // 顯示無數(shù)據(jù)
} else if (state == HomeViewStateFailure) { // 顯示請求錯(cuò)誤
} else if (state == HomeViewStateItemList) { // 顯示商品的列表
} else if (state == HomeViewStateBannerList) { // 顯示banner列表
}
不建議的寫法:
if (state == 0) { // 顯示無數(shù)據(jù)
} else if (state == 1) { // 顯示請求錯(cuò)誤
} else if (state == 2) { // 顯示商品的列表
} else if (state == 3) { // 顯示banner列表
}
8. 可變對象的使用
OC存在很多可變的對象,比如:NSMutableString、NSMutableArray、NSMutableDictionary等等,對于一些不允許改變的,直接使用不可變對象,可以節(jié)省對象開支,還可以防止別人修改數(shù)據(jù)造成bug。
建議的寫法:
NSArray *sexList = @[@"Man", @"Woman"];
不建議的寫法:
NSMutableArray *sexList = [NSMutableArray arrayWithArray:@[@"Man", @"Woman"]];
9. 遍歷的寫法
a. 如果只需要遍歷數(shù)組或字典,用forin。
建議的寫法:
for (NSString *name in names) {
// Do something very cool
}
不建議的寫法:
for (int i = 0; i < names.length, i++) {
NSString *name = names[i];
}
b. 如果需要遍歷數(shù)組或字典的內(nèi)容,并且需要索引時(shí)使用enumerator。
建議的寫法:
[names enumerateObjectsUsingBlock:^(NSString * _Nonnull name, NSUInteger idx, BOOL * _Nonnull stop) {
// Do something very cool
}];
不建議的寫法:
for (int i = 0; i < names.length, i++) {
NSString *name = names[i];
}
10. 復(fù)雜的表達(dá)式
建議的寫法:
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2014;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
不建議的寫法:
if ([sessionName containsString:@"Swift"] && [sessionDateCompontents year] == 2014) {
// Do something very cool
}
11. NSUserDefaults的代碼使用規(guī)范
因?yàn)橛玫?code>NSUserDefaults無非是保存和讀取數(shù)據(jù),所以先創(chuàng)建一個(gè)對象,可以精簡代碼,當(dāng)執(zhí)行方法很多,用變量替換。
對于我們?nèi)≈岛痛嬷档膋ey要定義一下,定義一下key方便我們使用,并且方便之后改名字。
建議的寫法:
NSString *user_id = @"user_id";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:@"1" forKey:user_id];
不建議的寫法:
[[NSUserDefaults standardUserDefaults] setObject:@"1" forKey:@"user_id"];
12. 通知的移除
通知在dealloc要移除(記得在dealloc時(shí)釋放注冊的通知和 KVO 的監(jiān)聽)。
建議的寫法:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
不建議的寫法:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:name1 object:nil];
}
13. 創(chuàng)建單例的方法
正確的寫法:
+ (instancetype)sharedInstance {
static BTSCustomView *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[BTSCustomView alloc] init];
});
return instance;
}
不建議的寫法:
+ (instancetype)sharedInstance {
static BTSCustomView *instance = nil;
if (!instance) {
instance = [[BTSCustomView alloc] init];
}
return instance;
}
14. OC方法的代碼量
一個(gè)方法內(nèi)部盡量控制在最多50行,如果超過就精簡代碼,分開方法寫,方便之后進(jìn)行熱修復(fù)、代碼重構(gòu);注釋一定要寫,自己管理的類一定要注釋屬性用途、方法的用途、參數(shù)說明。
a> 屬性如果設(shè)置默認(rèn)值,一定要注明默認(rèn)值是什么;
b> 如果方法內(nèi)部存在邏輯判斷、方法跳轉(zhuǎn),一定注釋邏輯判斷的用途、方法跳轉(zhuǎn)的用途;
c> 除了初始化操作,其他聲明變量、賦值、判斷,應(yīng)該注釋用途。
15. #pragma mark的使用
對于屬性的不同作用,可以進(jìn)行分組,比如設(shè)置顏色的、設(shè)置字體的、設(shè)置其他樣式的;
對于方法的不同作用,可以進(jìn)行分類,比如添加功能、刪除功能的;
對于其他的代理方法、Get、Set方法、Init初始化方法等,可以進(jìn)行分類。
建議的寫法:
#pragma mark - Init
#pragma mark - Request
#pragma mark - Delegate
#pragma mark - DataSource
#pragma mark - Setter
#pragma mark - Getter
16. 注釋的寫法
<1> 類注釋
/**
訂單的cell
*/
@interface OrderCell : UITableViewCell
<2> 屬性注釋
/**
當(dāng)前訂單cell
*/
@property (nonatomic, strong) OrderCell *cell;
<3> 方法注釋
/**
顯示倒計(jì)時(shí)文本
@param timerLabel 倒計(jì)時(shí)文本
@param countTime 剩余時(shí)間
@return 是否完成
*/
- (BOOL)timerLabel:(MZTimerLabel *)timerLabel finishedCountDownTimerWithTime:(NSTimeInterval)countTime;
<4> 局部變量和全局變量注釋
BOOL _isOfflinePay; // 是否是離線支付
<5> block注釋
/**
驗(yàn)證輸入的是否正確
@param inputText 輸入的文本
@return 如果返回值存在就代表驗(yàn)證失?。环駝t,就代表成功
*/
typedef NSString *(^ATFVValidateInputCorrectComplete)(NSString *inputText);
<6> 枚舉(NS_ENUM) 注釋
/**
當(dāng)前的狀態(tài)
- HomeViewStateNoData: 顯示無數(shù)據(jù)
- HomeViewStateFailure: 顯示請求錯(cuò)誤
- HomeViewStateItemList: 顯示商品的列表
- HomeViewStateBannerList: 顯示banner列表
*/
typedef NS_ENUM(NSUInteger, HomeViewState) {
HomeViewStateNoData,
HomeViewStateFailure,
HomeViewStateItemList,
HomeViewStateBannerList,
};
17. CGRect使用
當(dāng)需要訪問CGRect中某個(gè)成員變量時(shí),應(yīng)該使用CGGeometry函數(shù)來直接訪問,而不是使用.語法來獲取。
建議的寫法:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
不建議的寫法:
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;
18. 錯(cuò)誤處理
很多系統(tǒng)方法通過error返回指針的形式來表示錯(cuò)誤,我們應(yīng)該針對其返回值判斷,而非錯(cuò)誤變量。
建議的寫法:
NSError *error;
if (![self trySomethingWithError:&error]) {
// 處理錯(cuò)誤
}
不建議的寫法:
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// 處理錯(cuò)誤
}
一些蘋果API在成功的情況下會寫一些垃圾值給錯(cuò)誤參數(shù)(如果非空),所以針對錯(cuò)誤變量可能會造成虛假結(jié)果以及接下來的崩潰。
其他
- 下面是整理的一些常用對仗詞,大家可以參考使用。
add/delete 添加/刪除 add/remove 添加/移除
begin/end 開始/結(jié)束
create/destroy 創(chuàng)建/銷毀
first/last 第一個(gè)/最后一個(gè)
get/release 獲取/釋放 get/set 取出/設(shè)置
increment/decrement 增加/減少 insert/delete 插入/刪除
lock/unlock 鎖/解鎖
next/previous 下一個(gè)/前一個(gè)
old/new 舊的/新的 open/close 打開/關(guān)閉
pop/push 出棧/入棧 put/get 放入/取出
send/receive 發(fā)送/接收 show/hide 顯示/隱藏 source/target 源/目標(biāo)
source/sink 來源/接收器 source/destination 源/目的地 start/stop 開始/停止
store/query 存儲/查詢
up/down 向上/向下
visible/invisible 可見/不可見
settings 配置
traversal 遍歷
Proactor 設(shè)計(jì)模式
adapter 適配器
listener 監(jiān)聽器
trigger 觸發(fā)器
acceptor 接收器
connector 連接器
dispatch 調(diào)度/分派/分發(fā)
dispatcher 分派器
reactor 反應(yīng)器
executor 執(zhí)行器
parser 解析器
builder 生成器/構(gòu)造器
handle 句柄/處理
handler 處理器
invoke 調(diào)用
invoker 調(diào)用方
masterplate 模板
- iPhone屏幕適配比例
// 屏幕寬高 寬度 高度
3.5 320x480 100 / 414 * 320 ~= 77.3 100 / 736 * 480 ~= 65.2
4.0 320x568 100 / 414 * 320 ~= 77.3 100 / 736 * 568 ~= 77.2
4.7 375x667 100 / 414 * 375 ~= 90.6 100 / 736 * 667 ~= 90.6
5.5 414x736 100 / 414 * 414 ~= 100 100 / 736 * 736 ~= 100
5.8 375x812 100 / 414 * 375 ~= 90.6 100 / 736 * 812 ~= 110.3
iPhone 6 和 iPhone X 屏幕尺寸對比:
iPhone 6 iPhone X
375x667 375x812
************ ************
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
************ * *
* *
* *
************
寬度相同,高度不同:
667 = 20 + 44 + 554 + 49
812 = 44 + 44 + 641 + 49 + 34
Device Screen Sizes
| 設(shè)備 | 屏幕尺寸 (英寸) | 點(diǎn)分辨率 (pt) | 像素分辨率 (px) | PPI (DPI) | 渲染后 |
|---|---|---|---|---|---|
| iPhone 3GS | 3.5 | 320 x 480 | 320 x 480 | 163 | 空 |
| iPhone 4 / 4s | 3.5 | 320 x 480 | 640 x 960 | 326 | 空 |
| iPhone 5 / 5s / SE | 4.0 | 320 x 568 | 640 x 1136 | 326 | 空 |
| iPhone 6 / 6s / 7 / 8 | 4.7 | 375 x 667 | 750 x 1334 | 326 | 空 |
| iPhone 6 Plus / 6s Plus / 7 Plus / 8 Plus | 5.5 | 414 x 736 | 1242 x 2208 | 401 | 1080 x 1920 |
| iPhone X / Xs / 11 Pro | 5.8 | 375 x 812 | 1125 x 2436 | 458 | 空 |
| iPhone XR / 11 | 6.1 | 414 x 896 | 828 x 1792 | 326 | 空 |
| iPhone Xs Max / 11 Pro Max | 6.5 | 414 x 896 | 1242 x 2688 | 458 | 空 |