swift代码统一编码规范
编码规范
背景
随着团队扩大,人员增多。需要统一编码规范
规范
命名-明确的使用含义
- 请使用驼峰命名规则
//推荐
class UserInfo{
var userInfo: UserInfo?
}
//不推荐
class Userinfo{
var user_info: Userinfo?
}
- 首字母大写
//推荐
class UserInfo{}
//不推荐
class userinfo{}
- 用统一的标识开头:
YP
//推荐
class YPUserInfo{}
//不推荐
class userinfo{}
- 控制器
VC
结尾
//推荐
class YPUserInfoVC{}
class YPUserViewVC{}
//不推荐
class YPUserInfo{}
class YPUserInfoViewContrller{}
- 命名应该具有标识性
- 不能使用拼音
- 不能使用过于简单的缩写
//推荐
class YPRecruitVC{} //招工控制器
class YPRecruitListVC{} //招工列表控制器
class YPRecruitDetailVC{} //招工详情控制器
//不推荐
class YPAViewVC{}
class YPZhaoGonVC{}
class YPZGVC{}
- 视图命名
- 常规以View结尾:
UIContentView
- tableView的cell以TCell为后缀:
YPBaseTCell
- UICollectionView的cell以Cell为后缀:
YPBaseCCell
- vm
- 所有命名应该具有描述性
- 常规以View结尾:
- 属性应该是一个名词
//推荐
let isShow: Bool
let count: Int
//不推荐
let s: Bool
- 局部变量
- 需要遵守命名规范
- 使用具有代表意义的名词
例如:
model
、item
、temp
、dataSource
//推荐
for i in dataSource {}
为了减少不必要的属性申明
for model in models{}
models.foreach{$0.name = ""}
models.foreach{ model in
model.name = "1"
model.id = "2"
}
//不推荐
for a in dataSource {}
for m in models{}
models.foreach{ model in
model.name = ""
}
models.foreach{ f in
f.name = ""
f.id = ""
}
- 常量
- 常量的名字需要大写首字母并保持驼峰:
KLastChoosedOccsInRecruitList
- 避免使用全局常量,转而使用结构体和类
- 常量的名字需要大写首字母并保持驼峰:
/// 推荐
struct YPConfig{
static let
KLastChoosedOccsInRecruitList
= "KLastChoosedOccsInRecruitList
"}
//不推荐
let
KLastChoosedOccsInRecruitList
= "KLastChoosedOccsInRecruitList
"- 枚举
- 以enum结尾
- Case的命名
- 小写字母开头
- 名词或者动词
- 驼峰规则
//推荐
enum YPOperationEnum{
case add
case remove
}
//不推荐
enum YPOperation{
case Add
case add_user
case auser
}
- 类型
- 根据变量、参数、关联类型的作用来命名,而不是基于它们的类型
//推荐
var greeting = "Hello"
protocol ViewController {
associatedtype ContentView : View
}
class ProductionLine {
func restock(from supplier: WidgetFactory)
}
//不推荐
var string = "Hello"
protocol ViewController {
associatedtype ViewType : View
}class ProductionLine {
func restock(from widgetFactory: WidgetFactory)
}
- 协议
- 描述事物的协议,读起来应该像名词(例如,
Collection
) - 描述能力的协议,应该使用后缀
able
,ible
或ing
- 描述事物的协议,读起来应该像名词(例如,
例如,Equatable,ProgressReporting
- 协议方法,第一个未命名参数应该是委托数据源
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
方法
- 方法或者函数名最好能在调用处形成符合语法规范的英语短语
//推荐
x.insert(y, at: z) // “x, insert y at z”
x.subViews(havingColor: y) //“x's subviews having color y”
x.capitalizingNouns() //“x, capitalizing nouns”
//不推荐
x.insert(y, position: z)
x.subViews(color: y)
x.nounCapitalize()
- 省略无用的单词。每个单词都需要传达出相应的关键信息
//推荐
public mutating func remove(_ member: Element) -> Element?
//不推荐
public mutating func removeElement(_ member: Element) -> Element?
- 为了使用起来更流畅,可以从第二个或者第三个参数开始降低命名要求,前提是这些参数不影响整个 API 的语义
AudioUnit.instantiate(with: description, options: [.inProcess], completionHandler: stopProgressBar)
- 工厂方法用
make
开头:x.makeWidget(cogCount: 47)
- 构造器和工厂方法的第一个参数命名不应该考虑方法名,应该独立命名,如:
x.makeWidget(cogCount: 47)
//推荐
let foreground = Color(red: 32, green: 64, blue: 128)
let newPart = factory.makeWidget(gears: 42, spindles: 14)
let ref = Link(target: destination)
//不推荐,下面的例子中,试图将第一个参数名和方法名拼成连续的短语
let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
let ref = Link(to: destination)
- 没有副作用的方法和函数读起来应该像名词短语
例如,
x.distance(to: y)
,i.successor()
- 有副作用的方法和函数读起来应该像祈使动词
例如,
print(x)
,x.sort()
,x.append(y)
-
可变/不可变方法的命名要成对出现。一个可变方法通常都有一个不可变方法与之对应,二者的语义相近,区别在于前者更新实例,而后者返回一个新值
- 当一项操作恰好能够被一个动词描述时,使用动词原形为可变方法命名;使用动词的过去分词 (ed) 或现在分词 (ing) 为不可变方法命名
/// Reverses `self` in-place.
mutating func reverse()
/// Returns a reversed copy of `self`.
func reversed() -> Self
...
x.reverse()let y = x.reversed()
- 如果由于动词后面直接跟随一个对象,无法添加 “ed” 时,使用现在分词命名不可变方法,即后缀 “ing”
/// Strips all the newlines from `self`
mutating func stripNewlines()
/// Returns a copy of `self` with all the newlines stripped.
func strippingNewlines() -> String
...
s.stripNewlines()let oneLine = t.strippingNewlines()
- 当一项操作恰好能够被一个名词描述时,使用名词本身为不可变方法命名;使用名词前加 “form” 的方式为可变方法命名。
-
x.isEmpty
,line1.intersects(line2)
。
- 避免使用全局函数,转而使用方法和属性。以下情况例外
- 没有明显的self:
min(x, y, z)
- 没有明显的self:
- 函数是不受限的范型函数:
print(x)
- 在特定的领域中已经有约定俗成函数语法在:
sin(x)
- 对于没有参数的方法
//推荐
var method: Elenum{
//coding
}
//不推荐
func method(){
//coding
}
注释
/// 类注释[会在代码提示中显示]:用户信息模型
class YPUserInfoModel{
/// 属性注释[会在代码提示中显示]:用户id
var id: String?
/// 属性注释[会在代码提示中显示]:用户昵称
var name: String?
}
//MARK: - 代码模块注释[在文件目录中显示]
extension YPUserInfoModel{
/// 方法注释[会在代码提示中显示]:更新用户昵称
/// - Parameters:
/// - userName: 用户昵称
/// - Returns: model
func update(userName: String) -> Self{
//内部说明: 逻辑说明
name = userName
}
}
class YPHomeListVC: UIViewController{
//MARK: 业务属性
/// 操作类型
let operation: Operation = .normal
/// 页码
var page: Int = 0
...
//MARK: UI属性[懒加载]
/// 头视图
lazy var headView: UIView = {
let view = UIView()
//coding
return view
}()
/// 表格
lazy var tableView: UITableView = {
let view = UITableView()
//coding
return view
}()
}
- 所有的类必须添加类注释
- 所有属性必须加注释
- 方法必须加注释,方法中的参数和返回需要有注释
- 方法内部,复杂逻辑需要添加逻辑说明
- 方法中的参数需要添加明确的作用说明,如果有返回值也需要说明
- 复杂逻辑 注释在代码处
代码组织结构
目录结构
- Main
- 标签模块1
- Home
- View
- Cell
- Controller
- ViewModel
- View
- 功能模块1
- 。。。
- 功能模块2
- 。。。
- Home
- 标签模块2
- 标签模块3
- 。。。
- 标签模块1
- Model
- Resource
- 标签模块
- name.svg
- 标签模块
控制器中枚举和结构体的声明
访问域
- 明确属性、方法、类的访问域:
private
、fileprivate
、internal
、public
和open
- 同访问域的方法应该通过
extension
的代码块进行整合【不合理】
class YPContentView{
private var isShowAlert: Bool = false
}
private extension YPContentView{
private func method1(){}
private func method2(){}
}
extension YPContentView{
func method3(){}
func method4(){}
}
public extension YPContentView{
func method5(){}
func method6(){}
}
- 只开放get的权限
// 推荐
private(set) var name: String?
//不推荐
private var _name: String?
var name: String?{return name}
属性申明
- 对于不需要修改的内容使用
let
class YPBaseTCell: UITableViewCell{
private let operation: YPOperationEnum
init(operation: YPOperationEnum){
self. operation = operation
super.ini()
}
}
代码
空格
- 等号前后需有空格
//推荐
isHidden = false
//不推荐
isHidden=false
- if的判断条件前后需有空格
//推荐
if let temp = [].first {
}
if true {
}
guard true else {return}
//不推荐
if let temp = [].first{
}
if true{
}
guard true else{return}
换行
- 代码块
//推荐
func method {
//coding
}
for i in [1,2] {
//coding
}
if true {
//coding
}
guard true
else {
//coding
}
//不推荐
func method
{
//coding
}
for i in [1,2]
{
//coding
}
if true
{
//coding
}
guard true else{
//coding
}
写法
- 应该使用 +=, -=, *=, /=
var lookedNum: Int = 0
//推荐
lookedNum += 1
//不推荐
lookedNum = lookedNum + 1
懒加载
- controller中的UI必须使用懒加载
- 懒加载的内部视图 统一使用view,不要与其本身相同
- 请添加合适且明确的访问域
//推荐
private lazy var headView: YPRecruitSendResultTopHeaderView = {
let view = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, width: YPScreen.screen_w, height: 190.scale))
return view
}()
//不推荐
lazy var headView: YPRecruitSendResultTopHeaderView = {
let headView = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, width: YPScreen.screen_w, height: 190.scale))
return headView
}()
lazy var headView: YPRecruitSendResultTopHeaderView = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, width: YPScreen.screen_w, height: 190.scale))
- 在懒加载中不要直接
addSubView
//推荐
class YPHomeListVC{
private lazy var headView: YPRecruitSendResultTopHeaderView = {
let view = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, width: YPScreen.screen_w, height: 190.scale))
return view
}()
override func makeUI() {
super.makeUI()
view.addSubview(headView)
headView { make in
make.edges.equalToSuperview()
}
}
}
//不推荐
class YPHomeListVC{
private lazy var headView: YPRecruitSendResultTopHeaderView = {
let view = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, width: YPScreen.screen_w, height: 190.scale))
self.view.
addSubView
(view)return view
}()
}
内存
Block
- 类型申明
//推荐
class YPHomeListVC{
//
typealias Call = () -> Void
//
var call: Call?
}
//不推荐
class YPHomeListVC{
var call: (() -> Void)?
}
//推荐
let call: Call = {[weak self] in
guard let weakSelf = self else {return}
//coding
}
//不推荐
let call: Call = {[weak self] in
guard let self = self else {return}
//coding
}
podfile
- 接入的第三方库,必须直接指定版本
弹窗
- fYPProgressHUD
- YPLableAlertView
- YPStateAlertView
MVVM
input、output、transform
为了减少不必要的属性申明,在VC和VM的交互中。部分逻辑和 事件应该通过下面的方式进行交互
不支持在 Docs 外粘贴 block
f
- VC不需要引用目标,例如提交方法、登录方法
class YPHomeListVM: YPBaseViewModel{
}
extension YPHomeListVM: ViewModelType{
struct Input {
let header: ControlEvent<Void>
let footer: ControlEvent<Void>
}
struct Output {
let state: Observable<YPRefreshState>
}
func transform(input: Input) -> Output {
let state = input.header.flatMap{
//coding
}
return .init(state: state)
}
}
class YPHomeListVC: YPBaseVMController<YPHomeListVM>{
override func bindViewModel() {
let out = viewModel.transform(input: .init(header: tableView.rx.header, footer: tableView.rx.footer))
out.state.bind(to: tableView.rx.endRefresh).disposed(by: viewModel.rx.disposeBag)
}
}
如上所示,YPHomeListVC不需要在其它地方访问刷新的状态。VM不需要在其它地方监听下拉和上拉时间,这种情况就通过input和output进行交互
VM中的Rx
- 使用let
//推荐
/// 排序类型
let sort = BehaviorRelay<SortEnum>(value: .newest)
//不推荐
/// 排序类型
var sort = BehaviorRelay<SortEnum>(value: .newest)
private(set) var sort = BehaviorRelay<SortEnum>(value: .newest)
- 避免对controller的引用,需要使用controller的时候,请用回调和Rx的方式放在controller中
class YPHomeListVM: YPBaseViewModel{
//不推荐
weak var hostVC: UIViewController?
}
业务
网络库
errCode
在业务场景中,建议不要直接使用字符串,改用枚举类型
/// 推荐
if YPWhiteListEnum.paidIssue.rawValue == response.errCode {// "paid_issue" 付费发布提示
paidSendAlert(response: response)
}
/// 不推荐
if "integral_lack" == response.errCode {// 付费发布积分不足
integralLackAlert(response: response)
}
推荐
推荐使用isEmpty
//推荐
"sadasdsa".isEmpty
[1,2].isEmpty
//不推荐
"sadasdsa".count == 0
[1,2].count == 0
禁止强制解包
//推荐
guard let value = values2 as? String else {return}
//不推荐
let value = value2 as! String
数组取值,需要判断数组是否下标越界
//推荐
let source: [String] = ["1"]
if source.count > 1{
let value: String = source[1]
}
let value: String? = source.safe(idx: 1)
//不推荐
let value: String = source[1]
获取系统版本号,禁止强制直接转数值类型
let versionString = "14.2.1"
//推荐
let versions:[Int] = versionString.components(separatedBy: '.').map{Int($0) ?? 0}
//不推荐
let vaersion: CGFloat = CGFloat(versionString)
不推荐使用public、fileprivate等修饰符 修饰cextension扩展
//推荐
extension YPHomeViewModel{
fileprivate func medthod(){}
fileprivate func medthod(){}
}
//不推荐
fileprivate extension YPHomeViewModel{
func medthod(){}
func medthod(){}
}
SnpKit
- 约束的代码尽量精简
//推荐
openNoticeContentView.snp.makeConstraints { (make) in
make.left.right.equalToSuperview()
make.top.equalTo(advertisingContentView.snp.bottom)
make.height.equalTo(0)
}
//不推荐
openNoticeContentView.snp.makeConstraints { (make) in
make.left.equalTo(0)
make.right.equalTo(0)
make.top.equalTo(advertisingContentView.snp.bottom).offset(0)
make.height.equalTo(0)
}
- 适配刘海屏
- 路由
//不推荐
aaa_aaaa_aaaa
//推荐
aaa/aaa/aaa