• 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

  • 相关阅读:
    DOCTYPE和namespace
    由浅入深漫谈margin属性
    checkbox的完美用户体验
    XSL 属性模板的运用
    各浏览器里默认的表单控件(form controls)
    简单form标准化实例(二):语义结构
    zindex在IE中的迷惑(二)
    最简单的清除浮动的方法
    Default style sheet for HTML 4
    PNG透明背景图片的无界应用
  • 原文地址:https://www.cnblogs.com/supersr/p/15709987.html
Copyright © 2020-2023  润新知