Swift 编码规范

一、说明

  • 本代码规范主要包含可以快速执行的内容,还会不断完善和修正,但已存在的部分不会有大的修改,主要是增变化主要在于增加新的内容。

  • 本代码规范默认为必须执行,只是建议而非强制执行的会用“(不强制)”文字标出。

  • 在新版本迭代开发过程,新加代码应严格按照本规范执行,在对之前文件进行修改时,也要积极主动的根据目前代码规范对文件进行局部重构,提高代码质量和可读性。

二、代码格式化

1.行数和列数的限制

1.1 一个函数的长度不应该超过60行 (不强制)

当函数长度超过80行后,应该将内部一些复杂的逻辑提炼出来,形成新的函数,然后调用之,微型重构工作也应该无处不在,而不是等项目完成后再来重构。

1.2 每行代码长度建议不超过80 (不强制)

建议每一行代码的长度超过80字符时做折行处理,处理时请以结构清晰为原则。通过“Xcode => Preferences => TextEditing => 勾选Show PageGuide / 输入80 => OK”来设置提醒。

1.3 每个类原则上不超过600行 (不强制)

一个类不应该将很多复杂的逻辑揉合到一起来实现,我们约定当文件超过600行时,要考虑将这个文件进行拆分,可以使用协议方法来分离功能代码。如果逻辑过于复杂,则应该考虑从设计上将一些内部可以独立的逻辑提炼出来,形成新的类,以减轻单一类的复杂度。

2.代码分段的使用

推荐使用 // MARK: 将代码不同的处理段分隔开,分段名称的首母大写,如写中文也是可行,目的是可读性强,让别人一眼知道你的分段是干嘛的。方便在编辑器中快速定位到需要查看的代码。以下是一些建议:

2.1 Lifecycle分段

在UIViewController的子类实现文件中,建议添加一个 // MARK: - LifeCycle分段,该分段包含,init、loadView、viewDidLoad、viewWillAppear、viewDidAppear、viewWillDisappear、viewDidDisappear、viewWillLayoutSubviews、deinit等方法,并建议将deinit方法放到实现文件最前面。

建议将Lifecycle分段放到最前端,因为对一个类的阅读,往往是从init、viewDidLoad等方法开始的。示例:

    // MARK: - LifeCycle
    deinit {

    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    ...

2.2 代理分段

建议为每一个代理添加一个分段,并且分段的名称应该是代理名称,代理名称务必正确拼写,这样可以通过command+单击来查看代理的定义,示例:

    // MARK: - UITableViewDataSource
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    }

2.3 Events 分段

为button的点击事件,geture的响应方法,KVO的回调,Notification的回调方法添加一个events分段,在该分段的方法中,调用对应业务的方法,对应业务的方法放在对应业务的分段。示例:

     // MARK: - Events
    func submit(_ sender:UIButton) {

    }

2.4 业务分段

在一个类中,为相对比较独立的业务,或者是实现相对独立功能的方法进行分段,同一个类中业务不宜过多,业务过多时就应该考虑分拆该类了。示例:

     // MARK: - LoginLogic
     ...
     // MARK: - RegisterLogic
     ...

在UIViewController的实现文件中建议的分段及顺序如下图(根据情况选择需要哪些分段,不需要总是包含以下所有分段):

    // MARK: - LifeCycle

    // MARK: - Events

    // MARK: - Delegate

    // MARK: - xxx 按照业务划分

2.4 属性分段

在一个类中,属性一般分为跟view相关和跟数据相关属性,跟view相关一般是subviews这种,比如UILable,与数据相关一般是辅助性,比如 var isCheck: Bool = false 。示例:

    // MARK: - Data
    private var url: String = ""

    // MARK: - Subviews
    private lazy var webView: MQWKWebView = {
        let webView = MQWKWebView.init()
        webView.backgroundColor = UIColor.white
        return webView
    }()

三、代码规范

一、命名

1.类型

  • 类型名称(如 struct, enum, class, typedef, associatedtype, protocol 等)使用大驼峰命名法命名。

  • 变量和常量则以小驼峰命名法命名。

    enum Planet {
      case mercury, venus, earth, mars, jupiter, saturn, uranus,neptune
    }
    
    struct Person {
      var mobileNo: String
    }

2.协议

根据苹果接口设计指导准则,协议名称用来描述一些东西是什么的时候是名词,例如:Collection, WidgetFactory。若协议名称用来描述能力应该以 -ing, -able, 或 -ible 结尾,例如:Equatable, Resizing。

3.类前缀

  • Swift中类别(类,结构体)在编译时会把模块设置为默认的命名空间,但是为了不冲突,相关项目默认使用。
委托

在定义委托方法时,第一个未命名参数应是委托数据源。

正确代码:

func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)

func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool

错误代码:

func didSelectName(namePicker: NamePickerViewController, name: String)

func namePickerShouldReload() -> Bool
泛型

泛型类参数应具有描述性,遵守“大骆驼命名法”。如果一个参数名没有具体的含义,可以使用传统单大写字符,如T, U, 或V等。

正确代码:

struct Stack<Element>{ ... }
func writeTo <Target: OutputStream>(to Target: inout Target)
func swap(_ a: inout T, _ b: inout T) 

错误代码:

struct Stack<T>{ ... }
func write<target: OutputStream> (to target: inout target)
func swap<Thing>(_ a: inout Thing, _ b: inout Thing)

二、代码逻辑

1.self推断

  • 让编译器在所有允许的地方推断 self 。
  • 在 init 中设置参数时显性地使用 self 。
  • 在 non-escaping closures 中显性地使用 self 。
  • 在避免命名冲突时使用 self 。
    例:
class BoardLocation  {
    let row: Int, column: Int
    init(row: Int, column: Int)  {
        self.row = row
        self.column = column
        let closure = {
            print(self.row)    
         }  
    }
}

2.常量

  • 类常量应该在类型里声明为static。使用static修饰常量可以允许他们在被引用的时候不需要实例化类型。
  • 除了单例以外,应尽量避免生成全局常量。
  • 定义常量使用 let 关键字,定义变量使用 var 关键字, 如果变量的值未来不会发生变化要使用常量。
struct PhysicsModel {
    static var speedOfLightInAVacuum = 299_792_458
}

class Spaceship {
    static let topSpeed = PhysicsModel.speedOfLightInAVacuum
    var speed: Double

    func fullSpeedAhead() {
        speed = Spaceship.topSpeed
    }
}

3.计算型类型属性

  • 当只需要继承 getter 方法时,返回简单的 Computed 属性即可。

正确代码:

class Example {
    var age: UInt32 {
        return arc4random()
    }
}

错误代码:

class Example {
    var age: UInt32 {
        get {
            return arc4random()
        }
    }
}
  • 如果你在属性中添加了 set 或者 didSet ,那么你应该显示地提供 get 方法。
class Person {
    var age: Int {
        get {
            return Int(arc4random())
        }
        set {
            print("That's not your age.")
        }
    }
}

4.单例

Swift中单例很简单,Swift 的 runtime 会保证单例的创建并且采用线程安全的方式访问:

class ControversyManager {
    static let sharedInstance = ControversyManager()
    private init() {}
}

单例通常只需要访问"sharedInstance"的静态属性,除非你有不得已的原因去重命名它。注意,不要用静态函数或者全局函数去访问你的单例。

5.错误处理

可以使用do/try/catch机制,避免使用try!try?

6.可选值

  • 声明一个函数的某个参数可以为 nil 时,用
  • 当你确定某个变量在使用时已经确定不是 nil 时,在后面加!

7.扩展声明周期

用 [weak self] 和 guard let strongSelf = self else { return } 模式扩展生命周期。用 [weak self] 比 [unowned self] 更好。
正确代码:

resource.request().onComplete { 
    [weak self] response in
    guard let `self` = self else { return }
    let model = self.updateModel(response)
    self.updateUI(model)
}

resource.request().onComplete { [weak self] response in
  guard let strongSelf = self else { return }
  let model = strongSelf.updateModel(response)
  strongSelf.updateUI(model)
}

错误代码:

// might crash if self is released before response returns
resource.request().onComplete { [unowned self] response in
  let model = self.updateModel(response)
  self.updateUI(model)
}

// deallocate could happen between updating the model and updating UI
resource.request().onComplete { [weak self] response in
  let model = self?.updateModel(response)
  self?.updateUI(model)
}

三、代码结构

1.注释

  • 使用Xcode8自带的注释功能,快捷键Option+Command+/
  • 使用// MARK:分隔代码(类似于OC中的#pragma mark

2.缩进

遵守Xcode内置的缩进格式

选中 control + i Xcode 自动缩进

3.类型

1.类型声明时:紧跟属性,与类型之间加空格,等号两边须加一个空格

正确代码:

let width = 120.0 
let height: Double = 120.0 
var title: String

错误代码:

let width=1.0
let height:Double  =  1.0
var title:String
2.优先使用Swift原生类型,可以根据需要使用Objective-C提供的方法,因为Swift提供了到Objective-C的桥接。

正确代码:

let width: Double = 120.0  // Double
let widthString = (width as NSNumber).stringValue // String

错误代码:

let width: NSNumber = 120.0 // NSNumber
let widthString: NSString=width.stringValue  // NSString

4.协议一致性 (不强制)

当一个对象要实现协议一致性时,推荐使用 extension 隔离协议中的方法集,这样让相关方法和协议集中显示在一起,也简化了类支持一个协议和实现相关方法的流程。
正确代码:

class MyViewcontroller: UIViewController {
      // 方法
} 

extension MyViewcontroller: UITableViewDataSource {
      // UITableViewDataSource 方法
}

extension MyViewcontroller: UIScrollViewDelegate {
      // UIScrollViewDelegate 方法
} 

错误代码:

class MyViewcontroller: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
       // 所有的方法
}

5.良好的类定义

  • 属性,变量,常量和参数等在声明定义时,其中: 符号后有空格,而: 符号前没有空格,比如x: Int, 和Circle: Shape
  • 定义多个变量/数据结构时,出于相同的目的和上下文,可以定义在同一行。(不强制)
  • 缩进 getter,setter 的定义和属性观察器的定义。
  • 不需要添加internal这样的默认的修饰符。也不需要在重写一个方法时添加访问修饰符。
  • 给那些不打算被继承的类使用final修饰符
final class Circle: Shape {
  var x: Int, y: Int
  var radius: Double
  var diameter: Double{
      get {
        returnradius * 2
     } 
     set {      
        radius=newValue/2
    }  
  }
  init(x: Int, y: Int, radius: Double) {
      self.x = x
      self.y = y
      self.radius = radius  
  }

  convenience init(x: Int, y: Int, diameter: Double) {
     self.init(x: x, y: y, radius: diameter/2)  
  }

  func describe() -> String{
    return"I am a circle at\(centerString())with an area of\(computeArea())"
  }
  override func computeArea() -> Double{
    return M_PI * radius * radius  
  }

  private func centerString()->String{
    return "(\(x),\(y))"
  }
}

6.控制流程

  • 循环使用for-in表达式,而不使用 while 表达式。
    正确代码:
for _ in 0..<3 {
  print("Hello three times")
}

for(index, person) in attendeeList.enumerate() {
    print("\(person)is at position #\(index)")
}

for index in 0.stride(from: 0, to: items.count, by: 2) {
  print(index)
}
for index in (0...3).reverse() {
    print(index)
}

错误代码:

var i=0 
while i<3 {
  print("Hello three times") 
  i+=1
}

var i=0
while i<3 {
  let person = attendeeList[i]
  print("\(person)is at position #\(i)")  
  i+=1
}
  • Switch 模块中不用显式使用break。

7.黄金路径 (不强制)

当编码遇到条件判断时,左边的距离是黄金路径或幸福路径,因为路径越短,速度越快。不要嵌套if循环,多个返回语句是可以的。guard 就为此而生的。
正确代码:

func computeFFT(context: Context?, inputData: InputData?)  throws -> Frequencies {
    guard let context = context else {throwFFTError.NoContext }

    guard let inputData = inputData else { throwFFTError.NoInputData }

    //计算frequencies
    return frequencies
}

错误代码:

func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
if let context = context {
    if let inputData = inputData {
           // 计算frequencies
          return frequencies    
    } else {
         throwFFTError.NoInputData    
     }  
   } else {
      throwFFTError.NoContext  
   }
}

当有多个条件需要用 guard 或 if let 解包,可用复合语句避免嵌套。

guard let number1=number1, number2=number2, number3=number3 else{
    fatalError("impossible") 
}
// 处理number

8.语法糖

//对空的数据和字典,使用类型注解
var names:  [String] = []
var lookup:  [String: Int] = [:]

var deviceModels: [String]
var employees: [Int:String]
var faxNumber: Int?

9.函数

自由函数不依附于任何类或类型,应该节制地使用。
正确代码:

let sorted = items.mergeSort() // 易发现性
rocket.launch()  // 可读性

错误代码:

let sorted = mergeSort(items)// 不易被发现
launch(&rocket)

天然的自由函数:

let value = max(x,y,z)  // another free function that feels natural

10.用扩展只读属性 代替 单参数函数(不强制)

正确代码:

extension Int {
    var doubleValue: Double {
        return Double(self)
    }
}

//调用
intValue.doubleValue

错误代码:

func doubleValue(_ value: Int) -> Double {
    return Double(value)
}

//调用
doubleValue(intValue)
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!