Swift开发规范
此文档与Apple官方Swift代码规范文档不冲突,只是在官方文档的基础上增加了的部分规范。
-
命名规范
-
编码风格
-
语法规范
-
框架使用
-
网络请求
命名规范
- 类名
采用驼峰命名法,首字母大写, 示例:HomeViewController
- 结构体名
采用驼峰命名法,首字母大写,示例:Coupon
- 变量名
采用驼峰命名法,首字母小写,示例:pageNumber
- 枚举名
采用驼峰命名法,首字母大写,示例:OrderStatus
- 枚举值名
采用驼峰命名法,首字母小写。一般情况下一个单词就可以。示例:
enum OrderStatus: Int {
case normal
case expired
case paid
}
- 全局常量名
采用驼峰命名法,首字母大写,并用小写的项目缩写名为前缀。如“微信”项目名的小写缩写为“wx”, 示例:
let weAnimationDuration: TimeInterval = 0.25
- 全局函数名
采用驼峰命名法,首字母小写,并用小写的项目缩写名+下划线为前缀。如“微信”项目名的小写缩写为“wx”, 前缀即“wx_”,示例:
func we_checkLogin() -> Bool {
...
}
- 协议名
- 如果是单纯的协议,则采用驼峰命名法,首字母大写,后缀为"protocol"。示例:
SomeProtocol
- 如果是用来做代理的,则采用驼峰命名法,首字母大写,后缀为"delegate"。示例:
OrderCellDelegate
- 协议方法名
采用驼峰命名法,首字母小写。在创建一个代理方法时,第一个未命名的参数应该是代理源。(UIKit中有很多这样的例子),示例:
// 推荐:
func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
// 不推荐
func didSelectName(namePicker: NamePickerViewController, name: String)
func namePickerShouldReload() -> Bool
- 可选协议方法名
使用@objc
+optional
关键字,示例:
@objc protocol OrderCellDelegate: class {
// 除了自身对象之外,还有操作的控件作为参数
func orderCell(cell: weOrderCell, didClick checkButton: UIButton)
// 只有自身对象作为参数
@objc optional func orderCellDidClickCheckButton(cell: weOrderCell)
}
- 代理变量名
统一使用"delegate",并用weak修饰(如果需要除class之外的类型实现,则可不用)。示例:
weak var delegate: OrderCellDelegate?
- 控制器类名
要求同类名,但要以“ViewController”结尾,示例:OrderViewController
- 视图类名
要求同类名,但要以“View”结尾,示例:OrderDetailView
- 模型类名
要求同类名,无需特殊后缀,示例:Order
- 控件名
需要在名称中指明控件的类型,不要使用lbl
或btn
等缩写,示例:nameLabel
,confirmButton
- 资源文件名
- 图片资源需要在名称中加上功能模块名,防止重复,示例:
img_home_right_arrow
,img_order_locate
- 声音资源名称表明用途即可,示例:
qr_sound
- 闭包名
要求同类名,但要以"closure"结尾,示例:OrderViewClosure
- 扩展文件名
文件名后加上项目缩写,示例:UILabel+Extension(we).swift
- 扩展方法名
采用驼峰命名法,首字母小写,并用小写的项目缩写名+下划线为前缀。如“微信”项目名的小写缩写为“wx”, 前缀即“wx_”,示例:
extension UIView {
/// 移除所有子控件
func we_removeAllSubviews() {
...
}
}
- 首字母缩略词在命名中一般来说都是全部大写,例外的情形是如果首字母缩略词是一个命名的开始部分,而
这个命名需要小写字母作为开头,这种情形下首字母缩略词全部小写。示例:
// "HTML" 是变量名的开头, 需要全部小写 "html"
let htmlBodyContent: String = "<p>Hello, World!</p>"
// 推荐使用 ID 而不是 Id
let profileID: Int = 1
// 推荐使用 URLFinder 而不是 UrlFinder
class URLFinder {
...
}
- 命名应该具有描述性和清晰的。
// 推荐
class RoundAnimatingButton: UIButton { /* ... */ }
// 不推荐
class CustomButton: UIButton { /* ... */ }
- 不要缩写,简写命名,或用单个字母命名。
// 推荐
class RoundAnimatingButton: UIButton {
let animationDuration: NSTimeInterval
func startAnimating() {
let firstSubview = subviews.first
}
}
// 不推荐
class RoundAnimating: UIButton {
let aniDur: NSTimeInterval
func srtAnmating() {
let v = subviews.first
}
}
编码格式
- 尽可能的多使用
let
,少使用var
。 - 如果变量类型可以依靠推断得出,不建议声明变量时指明类型。示例:
var name = "jack"
- 二元运算符(+, ==, 或->)的前后都需要添加空格,左小括号后面和右小括号前面不需要空格。
let myValue = 20 + (30 / 2) * 3
if 1 + 1 == 3 {
fatalError("The universe is broken.")
}
func pancake() -> Pancake {
...
}
- 逗号后面要加一个空格,示例:
var nums = [1, 2, 3, 4]
- 左大括号不用另起一行,并与之前的元素相隔一个空格。
class SomeClass {
func someMethod() {
if x == y {
...
} else {
...
}
}
}
- 注释的双斜杠跟注释内容之间隔一个空格。示例:
// 我是注释
- 尽量不使用
self.
,除非方法参数名与属性同名。 - 使用
// MARk: -
按功能为一个文件中的代码分块, 下面一行保留为空行。示例:
class Pirate {
// MARK: - 实例属性
private let pirateName: String
// MARK: - 初始化
init() {
/* ... */
}
}
- 使用扩展来实现协议方法。示例:
extension ViewController: OrderCellDelegate {
func orderCell(cell: OrderCell, didClick checkButton: UIButton) {
...
}
}
- 使用
@available(iOS 10.0, *)
来标明起始系统版本号 - 为同一对象的各属性赋值时,等号‘=’对齐。示例:
let my = MyClass()
my.name = "张四"
my.age = 10
my.address = "望京绿地中心"
- 尽可能避免使用强制转换和强制解包。
- 使用4个空格进行缩进。
- 每行最多160个字符,避免一行过长。(Xcode->Preferences->Text Editing->Page guide at column: 设置成160即可)
- 在使用一些语句如
else
,catch
等紧随代码块的关键词的时候,确保代码块和关键词在同一行。下面if/else
和do/catch
的例子。示例:
if someBoolean {
// something you want
} else {
// something you don't want
}
do {
let fileContents = try readFile("filename.txt")
} catch {
print(error)
}
- 推荐把访问修饰符放到第一个位置。
// 推荐
private static let kMyPrivateNumber: Int
// 不推荐
static private let kMyPrivateNumber: Int
- case 语句 应和 switch 语句左对齐,并在 标准的 default 上面。
switch problem {
case .attitude:
print("At least I don't have a hair problem.")
case .hair:
print("Your barber didn't know when to stop.")
case .hunger(let hungerLevel):
print("The hunger level is \(hungerLevel).")
}
- 当在写一个变量类型,一个字典里的主键,一个函数的参数,遵从一个协议,或一个父类,不用在分号前添加空格。
// 指定类型
let pirateViewController: PirateViewController
// 字典语法(注意这里是向左对齐而不是分号对齐)
let ninjaDictionary: [String: AnyObject] = [
"fightLikeDairyFarmer": false,
"disgusting": true
]
// 调用函数
someFunction(someArgument: "Kitten")
// 父类
class PirateViewController: UIViewController {
...
}
// 协议
extension PirateViewController: UITableViewDataSource {
...
}
语法规范
- 使用显式类型和空集合。类型在赋值操作符的左边,空实例在赋值操作符的右边。
错误示例:
var x = [String: Int]()
var y = [Double]()
var z = Set<String>()
var mySet = MyOptionSet()
正确示例:
var x: [String: Int] = [:]
var y: [Double] = []
var z: Set<String> = []
var mySet: MyOptionSet = []
- 可选类型拆包时,使用
if let
或guard let
判断。 - 多个可选类型拆包取值时,将多个
if let
或guard let
判断合并。示例:
if let name = person.name, let age = person.age {
}
- 尽量不要使用
as!
或try!
,使用if let as?
判断。示例:
if let name = person.name as? String {
}
- 非全局常量要定义在类的里面,不要定义在类的外面。示例:
class ViewController: UIViewController {
let cellID = "GirlCell"
...
}
- 跨多行函数声明缩进时,参数名左对齐(不是冒号对齐)。示例:
func myFunctionWithManyParameters(parameterOne: String,
parameterTwo: String,
parameterThree: String) {
print("\(parameterOne) \(parameterTwo) \(parameterThree)"
}
- 多if语句时的缩进,看示例:
if myFirstVariable > (mySecondVariable + myThirdVariable)
&& myFourthVariable == .SomeEnumValue {
// Xcode会自动缩进
print("Hello, World!")
}
- 基本上不要通过下标直接访问数组内容,如果可能使用如
.first
或.last
, 因为这些方法是非强制类型并不会崩溃。(如果需要通过下标访问数组内容,在使用前要做边界检查) - 推荐尽可能使用
for item in items
而不是for i in 0..<items.count
遍历数组。 - 不要使用
+=
或+
操作符给数组添加新元素,使用性能较好的.append()
或.appendContentsOf()
。 - 如果需要声明数组基于其他的数组并保持不可变类型, 使用
let myNewArray = [arr1, arr2].flatten()
,而不是let myNewArray = arr1 + arr2
。 - 总体上,我们推荐使用提前返回的策略,而不是
if
语句的嵌套。使用guard
语句可以改善代码的可读性。示例:
// 推荐
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)
}
// 禁止
if monkeyIsland == nil {
return
}
bookVacation(onIsland: monkeyIsland!)
bragAboutVacation(onIsland: monkeyIsland!)
- 如果你不确定
if
语句 和guard
语句哪一个可读性更强,建议使用guard
。
// if 语句更有可读性
if operationFailed {
return
}
// guard 语句这里有更好的可读性
guard isSuccessful else {
return
}
// 双重否定不易被理解 - 不要这么做
guard !operationFailed else {
return
}
- 如果需要在2个状态间做出选择,建议使用
if
语句,而不是使用guard
语句。
// 推荐
if isFriendly {
print("你好, 远路来的朋友!")
} else {
print("穷小子, 哪儿来的?")
}
// 不推荐
guard isFriendly else {
print("穷小子, 哪儿来的?")
return
}
print("你好, 远路来的朋友!")
- 使用类型推断上下文,使用编译器推断上下文来编写简洁的代码。
// 推荐
let selector = #selector(viewDidLoad)
view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)
// 不推荐
let selector = #selector(ViewController.viewDidLoad)
view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)
框架使用
- 项目中所有控件(UIButton、UILabel等)必须使用DJExtension中封装的初始化方法。
// 带布局
let checkAllLabel = UILabel(text: "查看全部", font: dj_semiboldFont(14), color: .white, alignment: .right, superView: originalView) { (make) in
make.center.equalTo(thirdImageView)
make.width.equalTo(56)
make.height.equalTo(20)
}
// 不带布局
let checkAllLabel = UILabel(text: "查看全部", font: dj_semiboldFont(14), color: .white, alignment: .right)
- 项目中所有view或控制器相关操作,必须使用DJExtension中封装的扩展方法。
// UIView
view.dj_addBottomLine() // 在底部添加分割线
view.dj_addShadow() // 添加阴影
view.dj_removeAllSubviews() // 移除所有子控件
view.dj_getParentViewController() // 获取父控制器
...
// 控制器
vc.dj_push(TestViewController())
vc.dj_present(TestViewController())
vc.dj_pop()
vc.dj_showActionSheet()
...
- 项目中所有常用的设置值、获取某个值或判断,使用DJExtension中封装的公共函数。如果没有,可以完善DJExtension库。
Functions | Comment |
---|---|
dj_hexColor("00ff00") |
get a color with a hex value. |
dj_pingSemiboldFont(15) |
get a font from the font family "PingFangSC-Semibold". |
dj_isCameraAllowed() |
the camera authorization is allowed or not. |
dj_navigationBarHeight() |
get the navigation bar height(adapted iPhone X or later). |
dj_isEmpty(obj) |
an object is empty or not. |
dj_isIPhoneX() |
the phone is iPhone X,Xs,Xs Max or not. |
dj_toJson(obj) |
convert an object to json string. |
dj_callPhone("13288889990") |
call a number |
dj_postNotification("LoginSuccessNotification") |
post a notification. |
more... |
网络请求
- 项目中的网络请求数据架构采用Alamofire + Moya + SwiftyJSON + ObjectMapper。
- 项目中处理网络请求的类,统一命名为: 'xxService',比如‘HomeService’。
- 用来解析数据的模型优先使用
Struct
,Struct不能满足要求时,使用Class
。
后记
- 此规范主要是自己工作时的总结,可以提高代码可读性、可维护性,并可提高开发效率。如果有建议或发现有问题的地方,欢迎批评指正。
Have fun.