Swift开发规范

一. 格式规范

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()

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