iOS端編碼規(guī)范
Object-C 篇
基本格式
- 由于 Xcode 自動補(bǔ)全功能,在Object-C中,命名的清晰比簡潔更重要,所以命名應(yīng)盡可能的清晰
- 使用
#import引入oc/oc++頭文件,使用#include引入c/c++頭文件 - 盡量精簡你的公開api接口。無需公開的方法應(yīng)為私有
- 任意函數(shù)長度不得超過50行
- 任意行代碼不得超過80字符
- 在定義函數(shù)的行前留白一行
- 功能相近的代碼放在相鄰的地方
- 使用 #pragma Mark 區(qū)分不同功能的代碼塊
- 常量使用字母 k 開頭
- 使用駝峰命名法定義變量,首字母小寫,后續(xù)單詞首字母大寫,變量名字母數(shù)量不超20個(gè)
- 命名需要和真實(shí)意義相關(guān),不能使用無關(guān)的單詞或無意義的字母命名
- 命名實(shí)例變量在變量前加上
_前綴 - 定義一組相關(guān)的常量時(shí),盡量使用枚舉類型 建議使用
NS_ENUM和NS_OPTIONS宏來定義枚舉類型 - 使用
const定義浮點(diǎn)型或單個(gè)的整數(shù)型常量 - 二元運(yùn)算符和參數(shù)之間需要放置一個(gè)空格,一元運(yùn)算符、強(qiáng)制類型轉(zhuǎn)換和參數(shù)之間不放置空格。關(guān)鍵字之后圓括號之前需要放置一個(gè)空格
- 函數(shù)名參數(shù)過多時(shí),按照
:對齊分行顯示 - 定義長的字面值時(shí)同函數(shù)名規(guī)范一致,按照
,分行顯示,如:數(shù)組 字典 等 - 宏定義字母全部大寫
- 類名的定義應(yīng)與實(shí)際相關(guān)并有所區(qū)分,自定義視圖類在結(jié)尾處加上對應(yīng)的類型,如:自定義
UITableViewCell簡寫為xxxxCell.h,UICollectionViewCell簡寫為xxxxCCell - Object-C 沒有命名空間的概念,通常在每個(gè)類名前加兩個(gè)或不超過三個(gè)自定義固定大寫字母來代替
- 修飾符
*&等應(yīng)緊靠變量,例如:NSString *testString,NSArray *testArray等 - 構(gòu)造字典時(shí),字典的Key和Value與中間的冒號:都要留有一個(gè)空格,多行書寫時(shí),也可以將Value對齊
- 如果構(gòu)造代碼不寫在一行內(nèi),構(gòu)造元素需要使用兩個(gè)空格來進(jìn)行縮進(jìn),右括號]或者}寫在新的一行,并且與調(diào)用語法糖那行代碼的第一個(gè)非空字符對齊
代碼風(fēng)格
注釋
- 方法、函數(shù)、類、協(xié)議、類別的定義都需要注釋,推薦采用Apple的標(biāo)準(zhǔn)注釋風(fēng)格,快捷鍵
cmd + Alt + /自動彈出 - 定義在頭文件里的接口方法、屬性必須要有注釋!
帶參無返回值函數(shù)注釋:
/**
* 說明這個(gè)函數(shù)的主要作用
*
* @param type type參數(shù)的說明
* @param macAddress address參數(shù)的說明
*/
- (void)refreshConnectorWithConnectType:(IPCConnectType)type Mac:(NSString *)macAddress;
無參無返回值函數(shù)注釋:
/**
* 說明這個(gè)函數(shù)的主要作用
*/
-(void)stopRunning;
帶參有返回值函數(shù)注釋:
/**
* 說明這個(gè)函數(shù)的主要作用
*
* @param macAddress 參數(shù)的說明.
*
* @return 返回值的說明
*/
-(IPCCloudDevice *)getCloudDeviceWithMac:(NSString *)macAddress;
協(xié)議、委托的注釋要明確說明起觸發(fā)的條件:
/** 代理:說明該方法觸發(fā)的條件,如:xxx驗(yàn)證失敗時(shí)發(fā)送 */
-(void)initConnectionDidFailed:(IPCConnectHandler *)handler;
如在注釋中要引用參數(shù)名或者方法函數(shù)名, 使用 || 將參數(shù)或者方法括起來以避免歧義
// Sometimes we need |count| to be less than zero.
// Remember to call |StringWithoutSpaces("foo bar baz")|
編碼風(fēng)格
- 非必要盡量不要嵌套 if 語句,提前使用 return 可以避免增加循環(huán)的復(fù)雜度 并提高代碼的可讀性
*** 推薦寫法 ***
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
//Do something important
}
*** 不推薦 ***
- (void)someMethod {
if ([someOther boolValue]) {
//Do something important
}
}
- 復(fù)雜的 if 語句名或表達(dá)式,應(yīng)先提取賦值給一個(gè) BOOL 變量,這樣可以使邏輯更清晰,并能讓每個(gè)子句的意義體現(xiàn)出來
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2014;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
- 三元運(yùn)算符中的 ? 應(yīng)只用在它能讓代碼更加清楚的地方
result = a > b ? x : y;
- 使用
#pragma mark -來分離不同功能組的方法、protocols 的實(shí)現(xiàn)、對父類方法的重寫
- (void)dealloc { /* ... */ }
- (instancetype)init { /* ... */ }
#pragma mark - View Lifecycle (View 的生命周期)
- (void)viewDidLoad { /* ... */ }
- (void)viewWillAppear:(BOOL)animated { /* ... */ }
- (void)didReceiveMemoryWarning { /* ... */ }
#pragma mark - Custom Accessors (自定義訪問器)
- (void)setCustomProperty:(id)value { /* ... */ }
- (id)customProperty { /* ... */ }
#pragma mark - IBActions
- (IBAction)submitData:(id)sender { /* ... */ }
#pragma mark - Public
- (void)publicMethod { /* ... */ }
#pragma mark - Private
- (void)zoc_privateMethod { /* ... */ }
#pragma mark - UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ }
#pragma mark - ZOCSuperclass
// ... 重載來自 ZOCSuperclass 的方法
#pragma mark - NSObject
- (NSString *)description { /* ... */ }
- 初始化類時(shí)盡量避免使用 new 方法,采用
alloc init - 使用
dispatch_once來生成單例, 單例類方法名命名保持一致性。
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- Block 正確使用 __weakSelf, 參考如下:
/// 單語句使用
__weak __typeof(self) weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
[weakSelf doSomethingWithData:data];
}];
/// 多語句使用
__weak __typeof(self)weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomethingWithData:data];
[strongSelf doSomethingWithData:data];
}
}];
- 對于參數(shù)過多的函數(shù),如不能保持所有參數(shù)在同一行時(shí),應(yīng)每行一個(gè)參數(shù),以冒號對齊,如:
-(id)initWithModel:(IPCModle)model
ConnectType:(IPCConnectType)connectType
Resolution:(IPCResolution)resolution
AuthName:(NSString *)authName
Password:(NSString *)password
MAC:(NSString *)mac
AzIp:(NSString *)az_ip
AzDns:(NSString *)az_dns
Token:(NSString *)token
Email:(NSString *)email
Delegate:(id<IPCConnectHandlerDelegate>)delegate;
- 在分行時(shí),如果第一段名稱過短,后續(xù)名稱可以以Tab的長度(4個(gè)空格)為單位進(jìn)行縮進(jìn):
- (void)short:(GTMFoo *)theFoo
longKeyword:(NSRect)theRect
evenLongerKeyword:(float)theInterval
error:(NSError **)theError {
...
}
- 函數(shù)調(diào)用的格式和書寫差不多,可以按照函數(shù)的長短來選擇寫在一行或者分成多行
- 每一個(gè)文件中只創(chuàng)建或者實(shí)現(xiàn)一個(gè)類, 同一個(gè)文件中不能存在多個(gè)類
- Protocol單獨(dú)用一個(gè)文件來創(chuàng)建。盡量不要與相關(guān)類混在一個(gè)文件中
- 協(xié)議 Protocol 應(yīng)明確實(shí)是否必須或可選,列出
@required,@optional - 類的私有變量以
_開頭 - 外部引用對象,當(dāng)外部不會發(fā)生 set 操作的對象,使用 readonly 屬性。比如:自定義界面創(chuàng)建界面元素時(shí)
- 使用
UITableView和UITableViewCell時(shí)考慮cell被復(fù)用的情況,盡量不要在delegate中為cell添加View,推薦使用子類化 利于Cell充用和對Cell內(nèi)新添加的子View的布局 -
Objective-C中向nil對象發(fā)送命令是不會拋出異?;蛘邔?dǎo)致崩潰的,只是完全的“什么都不干” 所以nil檢查應(yīng)參照如下:
//正確,直接判斷
if (!objc) {
...
}
//錯(cuò)誤,不要使用nil == Object的形式
if (nil == objc) {
...
}
- 代理要使用
weak弱引用 - 推薦的代碼縮進(jìn)和換行方式如下:
if (user.isHappy) {
//Do something
} else {
//Do something else
}
/// 不推薦如下縮進(jìn):
if (user.isHappy)
{
//Do something
}
else {
//Do something else
}
Swift 篇
基礎(chǔ)格式
- 聲明變量和常量的數(shù)量一行一個(gè),不可同一行聲明兩個(gè)變量或常量,以利于添加注釋
- 變量或常量的數(shù)據(jù)類型,應(yīng)盡可能采用類型推斷,使代碼簡潔,如不是默認(rèn)數(shù)據(jù)類型時(shí),需要明確聲明變量或常量的數(shù)據(jù)類型
- 在聲明變量指定數(shù)據(jù)類型時(shí),
:冒號與變量名之間沒有空格,與數(shù)據(jù)類型之間保留一個(gè)空格 - 常量,變量,函數(shù),方法的命名規(guī)則使用小駝峰規(guī)則,首字母小寫
- 類別名稱(類、結(jié)構(gòu)體、枚舉和協(xié)議)使用大駝峰規(guī)則,首字母大寫。
- 宏名采用全大寫 + 下劃線分割單詞的風(fēng)格
- 使用前綴 k + 大駱駝命名法 為所有非單例的靜態(tài)常量命名。
- bool類型變量命名時(shí),建議以is作為前綴
- 在創(chuàng)建自定義代理方法時(shí),第一個(gè)未命名的參數(shù)應(yīng)該是代理源
- 縮寫和簡寫只能使用常用的或者約定俗成的縮寫,縮寫和簡寫中的所有字符的大小寫要一致
- 推薦使用編譯器推斷的上下文來編寫更加簡短清晰的代碼,如設(shè)置顏色時(shí)直接使用
.red,而不是UIColor.red - 使用懶加載來細(xì)致地控制對象的生命周期
- 可選類型拆包取值時(shí),先使用
if let判斷 - 多個(gè)可選類型拆包取值時(shí),將多個(gè)
if let判斷合并 - 盡量不要使用 as! 或 try! ,對于可選類型Optional多使用as? ,?? 可以給變量設(shè)置默認(rèn)值
- 數(shù)組和字典變量定義時(shí)需要標(biāo)明泛型類型,并使用更簡潔清晰的語法
- 常量定義,建議盡可能定義在類型里面,避免污染全局命名空間,如果是其他地方有可能復(fù)用的可以定義在類型外面
- 優(yōu)先使用guard來確保條件判斷的簡短
- 逗號后面加一個(gè)空格,如數(shù)組: let array = [1, 2, 3, 4, 5]
- 字典的書寫規(guī)范為: 與冒號前的key不留空格,而與Value應(yīng)保留一個(gè)空格,逗號后同樣保留一個(gè)空格,多個(gè)鍵值對時(shí),應(yīng)分行顯示,以第一個(gè)key的分號對齊
- 方法、if、switch等左大括號不要另起一行,跟隨語句放在行末,前置1空格,右大括號獨(dú)占一行,除非后面跟著統(tǒng)一語句的剩余部分(do while、if else 等)
- 判斷語句不用加括號
- 盡量不使用self,除非形參與屬性同名
- 訪問枚舉類型時(shí),使用更簡潔的點(diǎn)語法
- 推薦使用// MARK: - ,按功能、協(xié)議、代理等分組
- 函數(shù)頭注釋時(shí)推薦使用 Xcode 注釋快捷鍵 (??/)
- 代碼注釋放于對應(yīng)代碼的上方或者右邊,注釋符與注釋內(nèi)容間空1格,右置注釋與前面代碼空1格,代碼上方的注釋,應(yīng)與對應(yīng)代碼保持一樣的縮進(jìn)。
- 函數(shù)的設(shè)計(jì)應(yīng)避免函數(shù)過長,盡量不能超過50行,參數(shù)建議不超過5個(gè),并應(yīng)避免代碼塊嵌套過深,建議不要超過4層
- 當(dāng)調(diào)用的函數(shù)有多個(gè)參數(shù)時(shí),每一個(gè)參數(shù)另起一行,并比函數(shù)名多一個(gè)縮進(jìn)
- 當(dāng)數(shù)組或字典內(nèi)容較多需要多行顯示時(shí),需把
[與數(shù)組名或字典名同行,[前與:保留一個(gè)空格 ,結(jié)尾的]做單獨(dú)一行處理 - 在調(diào)用閉包時(shí),為避免循環(huán)引用,閉包內(nèi)使用弱引用;為避免弱引用被提前釋放,多次引用前使用強(qiáng)引用轉(zhuǎn)換
- 當(dāng)閉包是函數(shù)的最后一個(gè)參數(shù),采用尾隨閉包寫法
- 當(dāng)單個(gè)閉包表達(dá)式上下文清晰時(shí),使用隱式的返回值
- 單行最多不超過100個(gè)字符,超出時(shí)應(yīng)換行展示
- Switch 模塊中不用顯式使用break
代碼風(fēng)格
- 當(dāng)需要遍歷一個(gè)集合并變形成另一個(gè)集合時(shí),推薦使用函數(shù) map, filter 和 reduce
// 推薦
let stringOfInts = [1, 2, 3].flatMap { String($0) }
// ["1", "2", "3"]
// 不推薦
var stringOfInts: [String] = []
for integer in [1, 2, 3] {
stringOfInts.append(String(integer))
}
// 推薦
let evenNumbers = [4, 8, 15, 16, 23, 42].filter { $0 % 2 == 0 }
// [4, 8, 16, 42]
// 不推薦
var evenNumbers: [Int] = []
for integer in [4, 8, 15, 16, 23, 42] {
if integer % 2 == 0 {
evenNumbers(integer)
}
}
- 如果需要把 訪問修飾符 放到第一個(gè)位置,如:
// 推薦
private static let kMyPrivateNumber: Int
// 不推薦
static private let kMyPrivateNumber: Int
- 訪問修飾符不應(yīng)單獨(dú)另起一行,應(yīng)和訪問修飾符描述的對象保持在同一行
// 推薦
public class Pirate {
/* ... */
}
// 不推薦
public
class Pirate {
/* ... */
}
- 使用 Switch 時(shí),不需要使用 break 關(guān)鍵詞,如果default 的選項(xiàng)不應(yīng)該觸發(fā),可以拋出錯(cuò)誤 或 斷言類似的做法
func handleDigit(digit: Int) throws {
case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9:
print( "Yes, \(digit) is a digit!" )
default :
throw Error(message: "The given number was not a digit." )
}
- 對于willSet/didSet 和 set 中的舊值和新值雖然可以自定義名稱,但推薦使用默認(rèn)標(biāo)準(zhǔn)名稱 newValue/oldValue
var computedProperty: String {
get {
if someBool {
return "I'm a mighty pirate!"
}
return "I'm selling these fine leather jackets."
}
set {
computedProperty = newValue
}
willSet {
print( "will set to \(newValue)" )
}
didSet {
print( "did set from \(oldValue) to \(newValue)" )
}
}
- 聲明單例屬性可以通過下面方式進(jìn)行:
class PirateManager {
static let sharedInstance = PirateManager()
/* ... */
}
- 在創(chuàng)建類常量的時(shí)候,使用 static 關(guān)鍵詞修飾
class MyTableViewCell: UITableViewCell {
static let kReuseIdentifier = String(MyTableViewCell)
static let kCellHeight: CGFloat = 80.0
}
- 使用 guard 語句提前返回的策略替代 if 語句的嵌套 來改善代碼的可讀性
// 推薦
func eatDoughnut(atIndex index: Int) {
guard index >= 0 && index < doughnuts else {
// 如果 index 超出允許范圍,提前返回。
return
}
let doughnut = doughnuts[index]
eat(doughnut)
}
// 不推薦
func eatDoughnuts(atIndex index: Int) {
if index >= 0 && index < donuts.count {
let doughnut = doughnuts[index]
eat(doughnut)
}
}
- 同樣在解析可選類型時(shí),推薦使用 guard 語句,而不是 if 語句,因?yàn)?guard 語句可以減少不必要的嵌套縮進(jìn)
// 推薦
guard let monkeyIsland = monkeyIsland else {
return
}
bookVacation(onIsland: monkeyIsland)
bragAboutVacation(onIsland: monkeyIsland)
// 不推薦
if let monkeyIsland = monkeyIsland {
bookVacation(onIsland: monkeyIsland)
bragAboutVacation(onIsland: monkeyIsland)
}
- 如果需要在2個(gè)狀態(tài)間做出選擇,建議使用if 語句,而不是使用 guard 語句
// 推薦
if isFriendly {
print( "你好!" )
} else {
print("你哪兒來的?")
}
// 不推薦
guard isFriendly else {
print( "你哪兒來的?" )
return
}
print( "你好!" )
- 遇到使用 guard 語句拆包多個(gè)可選值時(shí),如果所有拆包失敗的錯(cuò)誤處理都一致可以把拆包組合到一起 (如 return, break, continue,throw 等). 依據(jù)使用場景而決定。
// 組合在一起因?yàn)榭赡芰⒓捶祷?guard let thingOne = thingOne,
let thingTwo = thingTwo,
let thingThree = thingThree else {
return
}
// 使用獨(dú)立的語句 因?yàn)槊總€(gè)場景返回不同的錯(cuò)誤
guard let thingOne = thingOne else {
throw Error(message: "Unwrapping thingOne failed." )
}
guard let thingTwo = thingTwo else {
throw Error(message: "Unwrapping thingTwo failed." )
}
guard let thingThree = thingThree else {
throw Error(message: "Unwrapping thingThree failed." )
}