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, YPScreen.screen_w, height: 190.scale))
return view
}()
//不推荐
lazy var headView: YPRecruitSendResultTopHeaderView = {
let headView = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, YPScreen.screen_w, height: 190.scale))
return headView
}()
 
lazy var headView: YPRecruitSendResultTopHeaderView = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, YPScreen.screen_w, height: 190.scale))
  • 在懒加载中不要直接addSubView
//推荐
class YPHomeListVC{
private lazy var headView: YPRecruitSendResultTopHeaderView = {
let view = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, 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, 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

原文地址:https://www.cnblogs.com/supersr/p/15709987.html