Swift开发代码规范

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 = "

Hello, World!

" // 推荐使用 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()
  var mySet = MyOptionSet()

正确示例:

  var x: [String: Int] = [:]
  var y: [Double] = []
  var z: Set = []
  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.. 遍历数组。
  • 不要使用 +=+ 操作符给数组添加新元素,使用性能较好的.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开发代码规范)