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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 一. 格式规范 1.1 使用4个空格进行缩进 推荐 1.2 二元运算符(+, ==, 或->)的前后都需要添加空格...
    与伟大LEE同行阅读 3,553评论 2 22
  • 持续更新,欢迎大家评论建议结合现在项目的原因,统一的规范在一个项目上是必须的,有利于后来人代码阅读,也有利于自身的...
    JasonL阅读 212评论 0 1
  • Android编码规范 源文件基础 文件名 源文件以其最顶层的类名来命名,大小写敏感,文件扩展名为.java。 文...
    呼呼哥阅读 928评论 0 0
  • web开发约束 项目结构: vue项目结构 vue命名约束 语义化和命名 应以功能或内容命名,不以表现形式命名;命...
    侬姝沁儿阅读 4,131评论 0 7
  • 些许的伤感也是如初见的醉,情不在意岁月的痕迹,只容纳能够相处的点滴,遗忘在不经意间的角落留下,偶尔也会路过,或者不...
    云郎阅读 134评论 0 0