iOS端编码规范
Object-C 篇
基本格式
- 由于 Xcode 自动补全功能,在Object-C中,命名的清晰比简洁更重要,所以命名应尽可能的清晰
- 使用
#import
引入oc/oc++
头文件,使用#include
引入c/c++
头文件 - 尽量精简你的公开api接口。无需公开的方法应为私有
- 任意函数长度不得超过50行
- 任意行代码不得超过80字符
- 在定义函数的行前留白一行
- 功能相近的代码放在相邻的地方
- 使用 #pragma Mark 区分不同功能的代码块
- 常量使用字母 k 开头
- 使用驼峰命名法定义变量,首字母小写,后续单词首字母大写,变量名字母数量不超20个
- 命名需要和真实意义相关,不能使用无关的单词或无意义的字母命名
- 命名实例变量在变量前加上
_
前缀 - 定义一组相关的常量时,尽量使用枚举类型 建议使用
NS_ENUM
和NS_OPTIONS
宏来定义枚举类型 - 使用
const
定义浮点型或单个的整数型常量 - 二元运算符和参数之间需要放置一个空格,一元运算符、强制类型转换和参数之间不放置空格。关键字之后圆括号之前需要放置一个空格
- 函数名参数过多时,按照
:
对齐分行显示 - 定义长的字面值时同函数名规范一致,按照
,
分行显示,如:数组 字典 等 - 宏定义字母全部大写
- 类名的定义应与实际相关并有所区分,自定义视图类在结尾处加上对应的类型,如:自定义
UITableViewCell
简写为xxxxCell.h
,UICollectionViewCell
简写为xxxxCCell
- Object-C 没有命名空间的概念,通常在每个类名前加两个或不超过三个自定义固定大写字母来代替
- 修饰符
*
&
等应紧靠变量,例如:NSString *testString
,NSArray *testArray
等 - 构造字典时,字典的Key和Value与中间的冒号:都要留有一个空格,多行书写时,也可以将Value对齐
- 如果构造代码不写在一行内,构造元素需要使用两个空格来进行缩进,右括号]或者}写在新的一行,并且与调用语法糖那行代码的第一个非空字符对齐
代码风格
注释
- 方法、函数、类、协议、类别的定义都需要注释,推荐采用Apple的标准注释风格,快捷键
cmd + Alt + /
自动弹出 - 定义在头文件里的接口方法、属性必须要有注释!
带参无返回值函数注释:
/**
* 说明这个函数的主要作用
*
* @param type type参数的说明
* @param macAddress address参数的说明
*/
- (void)refreshConnectorWithConnectType:(IPCConnectType)type Mac:(NSString *)macAddress;
无参无返回值函数注释:
/**
* 说明这个函数的主要作用
*/
-(void)stopRunning;
带参有返回值函数注释:
/**
* 说明这个函数的主要作用
*
* @param macAddress 参数的说明.
*
* @return 返回值的说明
*/
-(IPCCloudDevice *)getCloudDeviceWithMac:(NSString *)macAddress;
协议、委托的注释要明确说明起触发的条件:
/** 代理:说明该方法触发的条件,如:xxx验证失败时发送 */
-(void)initConnectionDidFailed:(IPCConnectHandler *)handler;
如在注释中要引用参数名或者方法函数名, 使用 || 将参数或者方法括起来以避免歧义
// Sometimes we need |count| to be less than zero.
// Remember to call |StringWithoutSpaces("foo bar baz")|
编码风格
- 非必要尽量不要嵌套 if 语句,提前使用 return 可以避免增加循环的复杂度 并提高代码的可读性
*** 推荐写法 ***
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
//Do something important
}
*** 不推荐 ***
- (void)someMethod {
if ([someOther boolValue]) {
//Do something important
}
}
- 复杂的 if 语句名或表达式,应先提取赋值给一个 BOOL 变量,这样可以使逻辑更清晰,并能让每个子句的意义体现出来
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2014;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
- 三元运算符中的 ? 应只用在它能让代码更加清楚的地方
result = a > b ? x : y;
- 使用
#pragma mark -
来分离不同功能组的方法、protocols 的实现、对父类方法的重写
- (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 { /* ... */ }
- 初始化类时尽量避免使用 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];
}
}];
- 对于参数过多的函数,如不能保持所有参数在同一行时,应每行一个参数,以冒号对齐,如:
-(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;
- 在分行时,如果第一段名称过短,后续名称可以以Tab的长度(4个空格)为单位进行缩进:
- (void)short:(GTMFoo *)theFoo
longKeyword:(NSRect)theRect
evenLongerKeyword:(float)theInterval
error:(NSError **)theError {
...
}
- 函数调用的格式和书写差不多,可以按照函数的长短来选择写在一行或者分成多行
- 每一个文件中只创建或者实现一个类, 同一个文件中不能存在多个类
- Protocol单独用一个文件来创建。尽量不要与相关类混在一个文件中
- 协议 Protocol 应明确实是否必须或可选,列出
@required
,@optional
- 类的私有变量以
_
开头 - 外部引用对象,当外部不会发生 set 操作的对象,使用 readonly 属性。比如:自定义界面创建界面元素时
- 使用
UITableView
和UITableViewCell
时考虑cell
被复用的情况,尽量不要在delegate
中为cell
添加View
,推荐使用子类化 利于Cell
充用和对Cell
内新添加的子View
的布局 -
Objective-C
中向nil
对象发送命令是不会抛出异常或者导致崩溃的,只是完全的“什么都不干” 所以nil
检查应参照如下:
//正确,直接判断
if (!objc) {
...
}
//错误,不要使用nil == Object的形式
if (nil == objc) {
...
}
- 代理要使用
weak
弱引用 - 推荐的代码缩进和换行方式如下:
if (user.isHappy) {
//Do something
} else {
//Do something else
}
/// 不推荐如下缩进:
if (user.isHappy)
{
//Do something
}
else {
//Do something else
}
Swift 篇
基础格式
- 声明变量和常量的数量一行一个,不可同一行声明两个变量或常量,以利于添加注释
- 变量或常量的数据类型,应尽可能采用类型推断,使代码简洁,如不是默认数据类型时,需要明确声明变量或常量的数据类型
- 在声明变量指定数据类型时,
:
冒号与变量名之间没有空格,与数据类型之间保留一个空格 - 常量,变量,函数,方法的命名规则使用小驼峰规则,首字母小写
- 类别名称(类、结构体、枚举和协议)使用大驼峰规则,首字母大写。
- 宏名采用全大写 + 下划线分割单词的风格
- 使用前缀 k + 大骆驼命名法 为所有非单例的静态常量命名。
- bool类型变量命名时,建议以is作为前缀
- 在创建自定义代理方法时,第一个未命名的参数应该是代理源
- 缩写和简写只能使用常用的或者约定俗成的缩写,缩写和简写中的所有字符的大小写要一致
- 推荐使用编译器推断的上下文来编写更加简短清晰的代码,如设置颜色时直接使用
.red
,而不是UIColor.red
- 使用懒加载来细致地控制对象的生命周期
- 可选类型拆包取值时,先使用
if let
判断 - 多个可选类型拆包取值时,将多个
if let
判断合并 - 尽量不要使用 as! 或 try! ,对于可选类型Optional多使用as? ,?? 可以给变量设置默认值
- 数组和字典变量定义时需要标明泛型类型,并使用更简洁清晰的语法
- 常量定义,建议尽可能定义在类型里面,避免污染全局命名空间,如果是其他地方有可能复用的可以定义在类型外面
- 优先使用guard来确保条件判断的简短
- 逗号后面加一个空格,如数组: let array = [1, 2, 3, 4, 5]
- 字典的书写规范为: 与冒号前的key不留空格,而与Value应保留一个空格,逗号后同样保留一个空格,多个键值对时,应分行显示,以第一个key的分号对齐
- 方法、if、switch等左大括号不要另起一行,跟随语句放在行末,前置1空格,右大括号独占一行,除非后面跟着统一语句的剩余部分(do while、if else 等)
- 判断语句不用加括号
- 尽量不使用self,除非形参与属性同名
- 访问枚举类型时,使用更简洁的点语法
- 推荐使用// MARK: - ,按功能、协议、代理等分组
- 函数头注释时推荐使用 Xcode 注释快捷键 (⌘⌥/)
- 代码注释放于对应代码的上方或者右边,注释符与注释内容间空1格,右置注释与前面代码空1格,代码上方的注释,应与对应代码保持一样的缩进。
- 函数的设计应避免函数过长,尽量不能超过50行,参数建议不超过5个,并应避免代码块嵌套过深,建议不要超过4层
- 当调用的函数有多个参数时,每一个参数另起一行,并比函数名多一个缩进
- 当数组或字典内容较多需要多行显示时,需把
[
与数组名或字典名同行,[
前与:
保留一个空格 ,结尾的]
做单独一行处理 - 在调用闭包时,为避免循环引用,闭包内使用弱引用;为避免弱引用被提前释放,多次引用前使用强引用转换
- 当闭包是函数的最后一个参数,采用尾随闭包写法
- 当单个闭包表达式上下文清晰时,使用隐式的返回值
- 单行最多不超过100个字符,超出时应换行展示
- Switch 模块中不用显式使用break
代码风格
- 当需要遍历一个集合并变形成另一个集合时,推荐使用函数 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)
}
}
- 如果需要把 访问修饰符 放到第一个位置,如:
// 推荐
private static let kMyPrivateNumber: Int
// 不推荐
static private let kMyPrivateNumber: Int
- 访问修饰符不应单独另起一行,应和访问修饰符描述的对象保持在同一行
// 推荐
public class Pirate {
/* ... */
}
// 不推荐
public
class Pirate {
/* ... */
}
- 使用 Switch 时,不需要使用 break 关键词,如果default 的选项不应该触发,可以抛出错误 或 断言类似的做法
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 中的旧值和新值虽然可以自定义名称,但推荐使用默认标准名称 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)" )
}
}
- 声明单例属性可以通过下面方式进行:
class PirateManager {
static let sharedInstance = PirateManager()
/* ... */
}
- 在创建类常量的时候,使用 static 关键词修饰
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)
}
}
- 同样在解析可选类型时,推荐使用 guard 语句,而不是 if 语句,因为 guard 语句可以减少不必要的嵌套缩进
// 推荐
guard let monkeyIsland = monkeyIsland else {
return
}
bookVacation(onIsland: monkeyIsland)
bragAboutVacation(onIsland: monkeyIsland)
// 不推荐
if let monkeyIsland = monkeyIsland {
bookVacation(onIsland: monkeyIsland)
bragAboutVacation(onIsland: monkeyIsland)
}
- 如果需要在2个状态间做出选择,建议使用if 语句,而不是使用 guard 语句
// 推荐
if isFriendly {
print( "你好!" )
} else {
print("你哪儿来的?")
}
// 不推荐
guard isFriendly else {
print( "你哪儿来的?" )
return
}
print( "你好!" )
- 遇到使用 guard 语句拆包多个可选值时,如果所有拆包失败的错误处理都一致可以把拆包组合到一起 (如 return, break, continue,throw 等). 依据使用场景而决定。
// 组合在一起因为可能立即返回
guard let thingOne = thingOne,
let thingTwo = thingTwo,
let thingThree = thingThree else {
return
}
// 使用独立的语句 因为每个场景返回不同的错误
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." )
}