美文网首页Swift
Swift开发规范

Swift开发规范

作者: 与伟大LEE同行 | 来源:发表于2019-04-29 15:37 被阅读0次

    一. 格式规范

    1.1 使用4个空格进行缩进

    推荐

    if value == 1 {
        print("")
    }
    

    1.2 二元运算符(+, ==, 或->)的前后都需要添加空格

    推荐

    let value = 1 + 2
                        
    if value == 1 {
        /* ... */
    }
                        
    func test(with value: TestClass) -> returnValue {
        /* ... */
    }
    

    1.3 一般情况下,在逗号和冒号后面加一个空格

    推荐

    let array = [1, 2, 3, 4, 5]
    
    let dic: [String: Any] = [:]
    

    不推荐

    let array = [1,2,3,4,5]
    
    let dic : [String :Any] = [:]
    

    1.4 switch 每个case结尾留一行空行, 最后一行不留. if同理.

    推荐

    switch i {
    case .a:
        print("a")
    
    case .b:
        print("b")
        
    default:
        default
    }
    
    if isTrue {
        /* ... 结尾空行 */
    
    } else if isFlase {
        /* ... */
    
    } else {
        /* ... 最后一行不需要空行 */
    }
    

    不推荐

    switch i {
    case .a:
        print("a")
    case .b:
        print("b")
    default:
        default
    
    }
    
    if isTrue {
        /* ... */
    } else if isFlase {
        /* ... */
    } else {
        /* ... */
    
    }
    

    1.5 代码结尾不要使用分号;

    推荐

    print("Hello World")
    

    不推荐

    print("Hello World");
    

    1.6 左大括号不要另起一行

    推荐

    // 1. Class Define
    class TestClass {
        /* ... */
    }
    
    // 2. if
    if value == 1 {
        /* ... */
    }
    
    // 3. if else
    if value == 1 {
        /* ... */
    } else {
        /* ... */
    }
    
    // 4. while
    while isTrue {
        /* ... */
    }
    
    // 5. guard
    guard let value = value else  {
        /* ... */
    }
    

    不推荐

    // 1. Class Define
    class TestClass 
    {
        /* ... */
    }
    
    // 2. if
    if value == 1
    {
        /* ... */
    }
    
    // 3. if else
    if value == 1
    {
        /* ... */
    }
    else
    {
        /* ... */
    }
    
    // 4. while
    while isTrue 
    {
        /* ... */
    }
    
    // 5. guard
    guard let value = value else 
    {
        /* ... */
    }
    

    1.7 判断语句不用括号

    推荐

    if value == 1 {
        /* ... */
    }
    
    if value == 1 && string == "test" {
        /* ... */
    }
    

    不推荐

    if (value == 1) {
        /* ... */
    }
    
    if ((value == 1) && (string == "test")) {
        /* ... */
    }
    

    1.8 不建议使用self. , 除非方法参数与属性同名或其他必要情况

    推荐

    func setup() {
        label = UILabel()
    }
    
    

    不推荐

    func setup() {
        self.label = UILabel()
    }
    

    1.9 善用类型推导, 不写多余代码

    推荐

    func set(color: UIColor) {
        /* ... */
    }
    
    set(color: .black)
    

    不推荐

    func set(color: UIColor) {
        /* ... */
    }
    
    set(color: UIColor.black)
    

    1.10 添加有必要的注释,尽可能使用Xcode注释快捷键(⌘⌥/)

    推荐

    /// <#Description#>
    ///
    /// - Parameter test: <#test description#>
    /// - Returns: <#return value description#>
    func test(string: String?) -> String? {
        /* ... */
    }
    

    不推荐

    /// <#Description#>
    func test(string: String?) -> String? {
        /* ... */
    }
    

    1.11 注释符//后加空格,如果//跟在代码后面,前面也加一个空格

    推荐

    // 注释
    let aString = "xxx" // 注释
    

    1.12 使用// MARK: -,按功能和协议 / 代理分组

    /// MARK顺序没有强制要求,但System API & Public API一般分别放在第一块和第二块。
    
    // MARK: - Public
    
    // MARK: - Request
    
    // MARK: - Action
    
    // MARK: - Private
    
    // MARK: - xxxDelegate
    

    1.13 对外接口不兼容时,使用@available(iOS x.0, *)标明接口适配起始系统版本号

    @available(iOS x.0, *)
    class MyClass {
        /* ... */
    }
    
    @available(iOS x.0, *)
    func myFunction() {
        /* ... */
    }
    

    1.14 方法间合理换行

    推荐

    extension XXX { 
        // 扩展开始 增加一个换行
        func xxxx1() {
            /* ... */
        }
        // 方法之间 增加一个换行
        func xxxx2() {
            /* ... */
        }
    }
    

    1.15 代码块合理换行

    推荐

    func xxxx() {
        /* ... */
        
        /* ... */
        
        /* ... */
    }
    

    二. 命名规范

    2.1 建议不要使用前缀, 善用命名空间.

    推荐

    HomeViewController
    Bundle
    

    不推荐

    NEHomeViewController
    NSBundle
    

    2.2 不要缩写、简写、单个字母来命名

    推荐

    let frame = view.frame
    let image = imageView.image
    let backgroundColor = view.backgroundColor 
    

    不推荐

    let f = view.frame
    let img = imageView.image
    let bgColor = view.backgroundColor 
    

    2.3 如非必要, 不要声明全局常量/变量/函数, 应该根据类型适当归类包装, 合理利用命名空间.

    推荐

    enum Main {
        static let color = UIColor(red: 0.1, green: 0.2, blue: 0.3, alpha: 1.0)
        static let count = 13
        static func font(_ size: CGFloat) -> UIFont {
            return UIFont(name: "xxx", size: size) ?? .systemFont(ofSize: size)
        }
    }
    

    不推荐

    let mainColor = UIColor(red: 0.1, green: 0.2, blue: 0.3, alpha: 1.0)
    let mainCount = 13
    func mainFont(_ size: CGFloat) -> UIFont {
        return UIFont(name: "xxx", size: size) ?? UIFont.systemFont(ofSize: size)
    }
    
    

    2.4 变量命名

    • 使用小驼峰,首字母小写

    • 变量命名应该能推断出该变量类型,如果不能推断,则需要以变量类型结尾

    推荐

    class TestClass: class {
        // UIKit的子类,后缀最好加上类型信息
        let coverImageView: UIImageView
        @IBOutlet weak var usernameTextField: UITextField!
    
        // 作为属性名的firstName,明显是字符串类型,所以不用在命名里不用包含String
        let firstName: String
    
        // UIViewContrller以ViewController结尾
        let fromViewController: UIViewController
    
        // 集合类型以复数形式命名
        var datas: [Data] = []
        var items: [Item] = []
    }
    

    不推荐

    class TestClass: class {
        // image不是UIImageView类型
        let coverImage: UIImageView
        // or cover不能表明其是UIImageView类型
        var cover: UIImageView
    
        // String后缀多余
        let firstNameString: String
    
        // UIViewContrller不要缩写
        let fromVC: UIViewController
        
        // 集合类型多余后缀和描述
        var dataList: [Data] = []
        var itemArray: [Item] = []
    }
    

    2.5 类型命名:使用大驼峰表示法,首字母大写

    2.6 方法命名:使用参数标签让方法语义更清楚, 参数标签和参数需要表达正确的语义(Public接口及基础组件必须遵循) (from Swift API Design Guidelines)

    2.6.1 省略所有的冗余的外部参数标签

    推荐

    func min(_ number1: Int, _ number2: Int) {
        /* ... */
    }
    
    min(1, 2)
    

    不推荐

    func min(number1: Int, number2: Int) {
        /* ... */
    }
    
    min(number1: 1, number2: 2)
    

    2.6.2 进行安全值类型转换的构造方法可以省略参数标签,非安全类型转换则需要添加参数标签以表示类型转换方法

    推荐

    extension UInt32 {
      /// 安全值类型转换,16位转32位,可省略参数标签
      init(_ value: Int16)
      
      /// 非安全类型转换,64位转32位,不可省略参数标签
      /// 截断显示
      init(truncating source: UInt64)
      
      /// 非安全类型转换,64位转32位,不可省略参数标签
      /// 显示最接近的近似值
      init(saturating valueToApproximate: UInt64)
    }
    

    2.6.3 当第一个参数构成整个语句的介词时(如,at, by, for, in, to, with 等),为第一个参数添加介词参数标签

    推荐

    // 添加介词标签having
    func removeBoxes(having length: Int) {
        /* ... */
    }
    
    let length = 23
    x.removeBoxes(having: length)
    

    例外情况是,当后面所有参数构成独立短语时,则介词提前。

    推荐

    // 介词To提前
    a.moveTo(x: b, y: c)
    
    // 介词From提前
    a.fadeFrom(red: b, green: c, blue: d)
    

    不推荐

    a.move(toX: b, y: c)
    
    a.fade(fromRed: b, green: c, blue: d)
    

    2.6.4 当第一个参数构成整个语句一部分时,省略第一个参数标签,否则需要添加第一个参数标签.

    推荐

    // 参数构成语句一部分,省略第一个参数标签
    view.addSubview(tempView)
    
    // 参数不构成语句一部分,不省略第一个参数标签
    dismiss(animated: false)
    

    2.6.5 其余情况下,给除第一个参数外的参数都添加标签

    2.7 方法命名:不要使用冗余的单词,特别是与参数及参数标签重复

    推荐

    func remove(_ member: Element) -> Element?
    

    不推荐

    func removeElement(_ member: Element) -> Element?
    

    2.8 命名出现缩写词,缩写词要么全部大写,要么全部小写,以首字母大小写为准

    推荐

    let urlRouterString = "https://xxxxx"
    
    let htmlString = "xxxx"
    
    class HTMLModel {
        /* ... */
    }
    
    struct URLRouter {
        /* ... */
    }
    

    不推荐

    let uRLRouterString = "https://xxxxx"
    
    let hTMLString = "xxxx"
    
    class HtmlModel {
        /* ... */
    }
    
    struct UrlRouter {
        /* ... */
    }
    

    2.9 Bool类型命名:用is为前缀

    推荐

    var isString: Bool = true
    

    2.10 枚举定义尽量简写,不要包括类型前缀

    推荐

    public enum UITableViewRowAnimation: Int {
        case fade
    
        case right // slide in from right (or out to right)
    
        case left
    
        case top
    
        case bottom
    
        case none // available in iOS 3.0
    
        case middle // available in iOS 3.2.  attempts to keep cell centered in the space it will/did occupy
    
        case automatic // available in iOS 5.0.  chooses an appropriate animation style for you
    }
    

    2.11 协议命名 (from Swift API Design Guidelines)

    2.11.1 如果协议描述的是协议做的事应该命名为名词(eg. Collection)

    推荐

    protocol TableViewSectionProvider {
        func rowHeight(at row: Int) -> CGFloat
        var numberOfRows: Int { get }
        /* ... */
    }
    

    2.11.2 如果协议描述的是能力,需添加后缀able或 ing (eg. Equatable、 ProgressReporting)

    推荐

    protocol Loggable {
        func logCurrentState()
        /* ... */
    }
    
    protocol Equatable {
        func ==(lhs: Self, rhs: Self) -> Bool {
            /* ... */
        }
    }
    

    2.11.3 如果已经定义类,需要给类定义相关协议,则添加Protocol后缀

    推荐

    protocol InputTextViewProtocol {
        func sendTrackingEvent()
        func inputText() -> String
        /* ... */
    }
    

    2.11.4 如果已经定义类,需要给类定义相关委托协议,则添加Delegate后缀

    推荐

    public protocol UITabBarControllerDelegate: NSObjectProtocol {
        @available(iOS 3.0, *)
        func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool
    }
    

    三. 语法规范

    3.1 多使用let,少使用var

    3.2 少用!去强制解包

    3.3 可选类型拆包取值时,使用if let判断

    推荐

    if let optionalValue = optionalValue {
        /* ... */
    }
    

    杜绝

    if  optionalValue != nil {
        let value = optionalValue!
        /* ... */
    }
    

    3.4 多个可选类型拆包取值时,将多个if let合并, 除非特殊逻辑需要.

    推荐

    var subview: UIView?
    var volume: Double?
    
    if let subview = subview, let volume = volume {
        /* ... */
    }
    

    不推荐

    var optionalSubview: UIView?
    var volume: Double?
    
    if let unwrappedSubview = optionalSubview {
        if let realVolume = volume {
            /* ... */
        }
    }
    

    3.5 不要使用 as! 或 try!

    推荐

    // 使用if let as?判断
    if let text = text as? String {
        /* ... */
    }
    
    // 使用if let try 或者 try?
    if let test = try aTryFuncton() {
        /* ... */
    }
    

    3.6 数组和字典变量定义,定义时需要标明泛型类型,并使用更简洁的语法.

    推荐

    var names: [String] = []
    var lookup: [String: Int] = [:]
    

    不推荐

    var names = [String]()
    var names: Array<String> = [String]() / 不够简洁
    
    var lookup = [String: Int]()
    var lookup: Dictionary<String, Int> =Dictionary<String, Int>() // 不够简洁
    

    3.7 数组访问尽可能使用 .first 或 .last, 推荐使用 for item in itemsitems.forEach { } 而不是 for i in 0...X

    推荐

    items.first
    items.last
    
    for item in items {
        /* ... */
    }
    
    items.forEach { 
        /* ... */
    }
    

    不推荐

    items[0]
    items[items.count - 1]
    
    for i in 0 ..< items.count {
        let item = items[i]
        /* ... */
    }
    

    3.8 如果变量能够推断出类型,则不建议声明变量时指明类型

    推荐

    let value = 1 
    
    let text = "xxxx"
    

    不推荐

    let value: Int = 1 
    
    let text: String = "xxxx"
    

    3.9 判断相等

    3.9.1 使用==!=判断内容上是否一致

    推荐

    // String类型没有-isEqualToString方法,用==判断是否相等
    let str1 = "netease"
    let str2 = "netease"
    if str1 == str2 {
        // is true
        /* ... */
    }
    
    // 对于自定义类型,判断内容是否一致,需要实现Equatable接口
    class BookItem {
        let bookId: String
        let title: String
        
        init (bookId: String, title: String) {
            self.bookId = bookId
            self.title = title
        }
    }
    
    extension BookItem: Equatable {
    
        static func ==(lhs: BookItem, rhs: BookItem) -> Bool {
            // 具体判断规则根据实际需要进行
            return lhs.bookId == rhs.bookId
        }
    }
    

    3.9.2 使用===!==判断class类型对象是否同一个引用,而不是用==!=

    推荐

    if tenEighty === alsoTenEighty {
        /* ... */
    }
    
    if tenEighty !== notTenEighty {
        /* ... */
    }
    

    3.10 协议

    3.10.1 当实现protocol时,如果确定protocol的实现不会被重写,建议用extension将protocol实现分离

    推荐

    class MyViewController: UIViewController {
      // class stuff here
    }
    
    // MARK: - UITableViewDataSource
    extension MyViewController: UITableViewDataSource {
      // table view data source methods
    }
    
    // MARK: - UIScrollViewDelegate
    extension MyViewController: UIScrollViewDelegate {
      // scroll view delegate methods
    }
    

    不推荐

    class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
      // all methods
    }
    

    3.12 当方法最后一个参数是Closure类型,调用时建议使用尾随闭包语法, 但只在只存在一个闭包参数时才使用尾闭包。

    推荐

    UIView.animateWithDuration(1.0) {
        self.myView.alpha = 0
    }
    
    UIView.animateWithDuration(1.0,
        animations: {
            self.myView.alpha = 0
        },
        completion: { finished in
            self.myView.removeFromSuperview()
        }
    )
    

    不推荐

    UIView.animateWithDuration(1.0, animations: {
        self.myView.alpha = 0
    })
    
    UIView.animateWithDuration(1.0,
        animations: {
            self.myView.alpha = 0
        }) { finished in
            self.myView.removeFromSuperview()
    }
    

    3.13 高阶函数推荐最简化语法

    推荐

    array.sort(by: <)
    
    array.sort { $0.age < $1.age }
    

    不推荐

    array.sort { (l, r) -> Bool in
        l < r
    }
    
    array.sort { (l, r) -> Bool in
        return l < r
    }
    

    3.14 访问控制 (优先考虑最低级)

    • private

    • fileprivate

    • internal (默认忽略不写)

    • public

    • open

    访问控制权限关键字应该写在最前面,除了@IBOutlet@IBAction@discardableResult@objc等关键字.

    推荐

    // 类似注解修饰词单独占一行
    @objc
    func print(message: String) -> String {
        /* ... */
        return xxx
    }
    

    3.15 如调用者可以不使用方法的返回值,则需要使用@discardableResult标明

    推荐

    @discardableResult
    func print(message: String) -> String {
        let output = "Output : \(message)"
        print(output)
        return output
    }
    

    3.16 Golden Path,最短路径原则

    推荐

    func test(_ number1: Int?, _ number2: Int?, _ number3: Int?) {
        guard
            let number1 = number1, 
            number2 = number2, 
            number3 = number3 else { 
            fatalError("impossible") 
        }
        /* ... */
    }
    
    func login(with username: String?, password: String?) throws -> LoginError {
        guard let username = username else { 
            throw .noUsername 
        }
        guard let password = password else { 
            throw .noPassword
        }
    
        /* login code */
    }
    

    不推荐

    func test(_ number1: Int?, _ number2: Int?, _ number3: Int?) {
        if let number1 = number1 {
            if let number2 = number2 {
                if let number3 = number3 {
                    /* ... */
                } else {
                    fatalError("impossible")
                }
            } else {
                fatalError("impossible")
            }
        } else {
            fatalError("impossible")
        }
    }
    
    func login(with username: String?, password: String?) throws -> LoginError {
        if let username = username {
          if let password = password {
              /* login code */
          } else {
              throw .noPassword
          }
        } else {
            throw .noUsername
        }
    }
    

    3.17 循环引用

    3.17.1 使用委托和协议时,避免循环引用,定义属性的时候使用weak修饰

    推荐

    public weak var dataSource: UITableViewDataSource?
    
    public weak var delegate: UITableViewDelegate?
    

    3.17.2 在逃逸Closures中使用self时避免循环引用

    推荐

    request(.list) { [weak self] (result: Result<[Model]>) in
        guard let self = self else { return }
    
        self.items = self.result.value
        self.tableView.reloadData()
    }
    

    不推荐

    request(.list) { [unowned self] (result: Result<[Model]>) in
        self.items = self.result.value
        self.tableView.reloadData()
    }
    

    不推荐

    request(.list) { [weak self] (result: Result<[Model]>) in
        self?.items = self?.result.value
        self?.tableView.reloadData()
    }
    

    3.17.3 使用方法作为闭包参数时, 应注意循环引用问题

    不推荐

    func abc() {
        // 内嵌方法
        func close() {
            // 访问了外部self 存在循环引用
            self.controller.dismiss()
        }
        // 作为闭包参数
        view.set(close)
        xxxx.closeHandle = close
    }
    

    推荐

    func abc() {
        weak var `self` = self
        // 内嵌方法
        func close() {
            // 访问weak修饰的self 不存在循环引用
            self?.controller.dismiss()
        }
        // 作为闭包参数
        view.set(close)
        xxxx.closeHandle = close
    }
    

    ⚠️同样上面的代码如果func close() 不是内嵌方法, 而是与func abc()同级的方法, 那么必然存在循环引用, 需要重点注意.

    不推荐

    func close() {
        self.controller.dismiss()
    }
    
    func abc() {
        // 作为闭包参数
        view.set(close)
        xxxx.closeHandle = close
    }
    

    如果一定要使用同级的方法可用以下方法暂时解决:

    推荐

    func close() {
        self.controller.dismiss()
    }
    
    func abc() {
        weak var `self` = self
        // 内嵌方法
        func close() {
            self?.close()
        }
        // 作为闭包参数
        view.set(close)
        xxxx.closeHandle = close
    }
    

    3.18 空判断

    推荐

    if array.isEmpty {
        /* ... */
    }
    
    if string.isEmpty {
        /* ... */
    }
    

    不推荐

    if array.count == 0 {
        /* ... */
    }
    
    if string.count == 0 {
        /* ... */
    }
    

    3.19 单例

    推荐

    class TestManager {
        static let shared = TestManager()
    
        /* ... */
    }
    

    相关文章

      网友评论

        本文标题:Swift开发规范

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