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
    • 所有命名应该具有描述性
  • 属性应该是一个名词
//推荐 let isShow: Bool let count: Int //不推荐 let s: Bool
  • 局部变量
    • 需要遵守命名规范
    • 使用具有代表意义的名词
例如:modelitemtempdataSource //推荐 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
    • 描述能力的协议,应该使用后缀 ableible 或 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) 为不可变方法命名
  • 命名不可变方法,最好使用过去分词(通常是增加后缀 “ed”)
/// 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.isEmptyline1.intersects(line2)
  • 避免使用全局函数,转而使用方法和属性。以下情况例外
    • 没有明显的self:min(x, y, z)
  • 函数是不受限的范型函数: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
      • 功能模块1
        • 。。。
      • 功能模块2
        • 。。。
    • 标签模块2
    • 标签模块3
    • 。。。
  • Model
  • Resource
    • 标签模块
      • name.svg

控制器中枚举和结构体的声明

访问域

  • 明确属性、方法、类的访问域:privatefileprivateinternalpublicopen
  • 同访问域的方法应该通过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)? }

weak 和 unowned

//推荐 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 let footer: ControlEvent } struct Output { let state: Observable } func transform(input: Input) -> Output { let state = input.header.flatMap{ //coding } return .init(state: state) } }   class YPHomeListVC: YPBaseVMController{ 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(value: .newest) //不推荐 /// 排序类型 var sort = BehaviorRelay(value: .newest) private(set) var sort = BehaviorRelay(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

相关