Swift开发代码规范

作者: 悟饭哪 | 来源:发表于2019-10-18 17:49 被阅读0次

    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 {
          ...
        }
    
    • 协议名
    1. 如果是单纯的协议,则采用驼峰命名法,首字母大写,后缀为"protocol"。示例:SomeProtocol
    2. 如果是用来做代理的,则采用驼峰命名法,首字母大写,后缀为"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
    • 控件名
      需要在名称中指明控件的类型,不要使用lblbtn等缩写,示例:nameLabel, confirmButton
    • 资源文件名
    1. 图片资源需要在名称中加上功能模块名,防止重复,示例:img_home_right_arrowimg_order_locate
    2. 声音资源名称表明用途即可,示例: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即可)
    • 在使用一些语句如elsecatch等紧随代码块的关键词的时候,确保代码块和关键词在同一行。下面if/elsedo/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 letguard let判断。
    • 多个可选类型拆包取值时,将多个if letguard 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.

    相关文章

      网友评论

        本文标题:Swift开发代码规范

        本文链接:https://www.haomeiwen.com/subject/vvxwmctx.html