Swift開(kāi)發(fā)規(guī)范

一. 格式規(guī)范

1.1 使用4個(gè)空格進(jìn)行縮進(jìn)

推薦

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

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

推薦

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

1.3 一般情況下,在逗號(hào)和冒號(hào)后面加一個(gè)空格

推薦

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 每個(gè)case結(jié)尾留一行空行, 最后一行不留. if同理.

推薦

switch i {
case .a:
    print("a")

case .b:
    print("b")
    
default:
    default
}

if isTrue {
    /* ... 結(jié)尾空行 */

} else if isFlase {
    /* ... */

} else {
    /* ... 最后一行不需要空行 */
}

不推薦

switch i {
case .a:
    print("a")
case .b:
    print("b")
default:
    default

}

if isTrue {
    /* ... */
} else if isFlase {
    /* ... */
} else {
    /* ... */

}

1.5 代碼結(jié)尾不要使用分號(hào);

推薦

print("Hello World")

不推薦

print("Hello World");

1.6 左大括號(hào)不要另起一行

推薦

// 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 判斷語(yǔ)句不用括號(hào)

推薦

if value == 1 {
    /* ... */
}

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

不推薦

if (value == 1) {
    /* ... */
}

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

1.8 不建議使用self. , 除非方法參數(shù)與屬性同名或其他必要情況

推薦

func setup() {
    label = UILabel()
}

不推薦

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

1.9 善用類型推導(dǎo), 不寫(xiě)多余代碼

推薦

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 注釋符//后加空格,如果//跟在代碼后面,前面也加一個(gè)空格

推薦

// 注釋
let aString = "xxx" // 注釋

1.12 使用// MARK: -,按功能和協(xié)議 / 代理分組

/// MARK順序沒(méi)有強(qiáng)制要求,但System API & Public API一般分別放在第一塊和第二塊。

// MARK: - Public

// MARK: - Request

// MARK: - Action

// MARK: - Private

// MARK: - xxxDelegate

1.13 對(duì)外接口不兼容時(shí),使用@available(iOS x.0, *)標(biāo)明接口適配起始系統(tǒng)版本號(hào)

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

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

1.14 方法間合理?yè)Q行

推薦

extension XXX { 
    // 擴(kuò)展開(kāi)始 增加一個(gè)換行
    func xxxx1() {
        /* ... */
    }
    // 方法之間 增加一個(gè)換行
    func xxxx2() {
        /* ... */
    }
}

1.15 代碼塊合理?yè)Q行

推薦

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

二. 命名規(guī)范

2.1 建議不要使用前綴, 善用命名空間.

推薦

HomeViewController
Bundle

不推薦

NEHomeViewController
NSBundle

2.2 不要縮寫(xiě)、簡(jiǎn)寫(xiě)、單個(gè)字母來(lái)命名

推薦

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 如非必要, 不要聲明全局常量/變量/函數(shù), 應(yīng)該根據(jù)類型適當(dāng)歸類包裝, 合理利用命名空間.

推薦

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 變量命名

  • 使用小駝峰,首字母小寫(xiě)

  • 變量命名應(yīng)該能推斷出該變量類型,如果不能推斷,則需要以變量類型結(jié)尾

推薦

class TestClass: class {
    // UIKit的子類,后綴最好加上類型信息
    let coverImageView: UIImageView
    @IBOutlet weak var usernameTextField: UITextField!

    // 作為屬性名的firstName,明顯是字符串類型,所以不用在命名里不用包含String
    let firstName: String

    // UIViewContrller以ViewController結(jié)尾
    let fromViewController: UIViewController

    // 集合類型以復(fù)數(shù)形式命名
    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不要縮寫(xiě)
    let fromVC: UIViewController
    
    // 集合類型多余后綴和描述
    var dataList: [Data] = []
    var itemArray: [Item] = []
}

2.5 類型命名:使用大駝峰表示法,首字母大寫(xiě)

2.6 方法命名:使用參數(shù)標(biāo)簽讓方法語(yǔ)義更清楚, 參數(shù)標(biāo)簽和參數(shù)需要表達(dá)正確的語(yǔ)義(Public接口及基礎(chǔ)組件必須遵循) (from Swift API Design Guidelines)

2.6.1 省略所有的冗余的外部參數(shù)標(biāo)簽

推薦

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

min(1, 2)

不推薦

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

min(number1: 1, number2: 2)

2.6.2 進(jìn)行安全值類型轉(zhuǎn)換的構(gòu)造方法可以省略參數(shù)標(biāo)簽,非安全類型轉(zhuǎn)換則需要添加參數(shù)標(biāo)簽以表示類型轉(zhuǎn)換方法

推薦

extension UInt32 {
  /// 安全值類型轉(zhuǎn)換,16位轉(zhuǎn)32位,可省略參數(shù)標(biāo)簽
  init(_ value: Int16)
  
  /// 非安全類型轉(zhuǎn)換,64位轉(zhuǎn)32位,不可省略參數(shù)標(biāo)簽
  /// 截?cái)囡@示
  init(truncating source: UInt64)
  
  /// 非安全類型轉(zhuǎn)換,64位轉(zhuǎn)32位,不可省略參數(shù)標(biāo)簽
  /// 顯示最接近的近似值
  init(saturating valueToApproximate: UInt64)
}

2.6.3 當(dāng)?shù)谝粋€(gè)參數(shù)構(gòu)成整個(gè)語(yǔ)句的介詞時(shí)(如,at, by, for, in, to, with 等),為第一個(gè)參數(shù)添加介詞參數(shù)標(biāo)簽

推薦

// 添加介詞標(biāo)簽having
func removeBoxes(having length: Int) {
    /* ... */
}

let length = 23
x.removeBoxes(having: length)

例外情況是,當(dāng)后面所有參數(shù)構(gòu)成獨(dú)立短語(yǔ)時(shí),則介詞提前。

推薦

// 介詞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 當(dāng)?shù)谝粋€(gè)參數(shù)構(gòu)成整個(gè)語(yǔ)句一部分時(shí),省略第一個(gè)參數(shù)標(biāo)簽,否則需要添加第一個(gè)參數(shù)標(biāo)簽.

推薦

// 參數(shù)構(gòu)成語(yǔ)句一部分,省略第一個(gè)參數(shù)標(biāo)簽
view.addSubview(tempView)

// 參數(shù)不構(gòu)成語(yǔ)句一部分,不省略第一個(gè)參數(shù)標(biāo)簽
dismiss(animated: false)

2.6.5 其余情況下,給除第一個(gè)參數(shù)外的參數(shù)都添加標(biāo)簽

2.7 方法命名:不要使用冗余的單詞,特別是與參數(shù)及參數(shù)標(biāo)簽重復(fù)

推薦

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

不推薦

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

2.8 命名出現(xiàn)縮寫(xiě)詞,縮寫(xiě)詞要么全部大寫(xiě),要么全部小寫(xiě),以首字母大小寫(xiě)為準(zhǔn)

推薦

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 枚舉定義盡量簡(jiǎn)寫(xiě),不要包括類型前綴

推薦

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 協(xié)議命名 (from Swift API Design Guidelines)

2.11.1 如果協(xié)議描述的是協(xié)議做的事應(yīng)該命名為名詞(eg. Collection)

推薦

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

2.11.2 如果協(xié)議描述的是能力,需添加后綴able或 ing (eg. Equatable、 ProgressReporting)

推薦

protocol Loggable {
    func logCurrentState()
    /* ... */
}

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

2.11.3 如果已經(jīng)定義類,需要給類定義相關(guān)協(xié)議,則添加Protocol后綴

推薦

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

2.11.4 如果已經(jīng)定義類,需要給類定義相關(guān)委托協(xié)議,則添加Delegate后綴

推薦

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

三. 語(yǔ)法規(guī)范

3.1 多使用let,少使用var

3.2 少用!去強(qiáng)制解包

3.3 可選類型拆包取值時(shí),使用if let判斷

推薦

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

杜絕

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

3.4 多個(gè)可選類型拆包取值時(shí),將多個(gè)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 數(shù)組和字典變量定義,定義時(shí)需要標(biāo)明泛型類型,并使用更簡(jiǎn)潔的語(yǔ)法.

推薦

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

不推薦

var names = [String]()
var names: Array<String> = [String]() / 不夠簡(jiǎn)潔

var lookup = [String: Int]()
var lookup: Dictionary<String, Int> =Dictionary<String, Int>() // 不夠簡(jiǎn)潔

3.7 數(shù)組訪問(wèn)盡可能使用 .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 如果變量能夠推斷出類型,則不建議聲明變量時(shí)指明類型

推薦

let value = 1 

let text = "xxxx"

不推薦

let value: Int = 1 

let text: String = "xxxx"

3.9 判斷相等

3.9.1 使用==!=判斷內(nèi)容上是否一致

推薦

// String類型沒(méi)有-isEqualToString方法,用==判斷是否相等
let str1 = "netease"
let str2 = "netease"
if str1 == str2 {
    // is true
    /* ... */
}

// 對(duì)于自定義類型,判斷內(nèi)容是否一致,需要實(shí)現(xiàn)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 {
        // 具體判斷規(guī)則根據(jù)實(shí)際需要進(jìn)行
        return lhs.bookId == rhs.bookId
    }
}

3.9.2 使用===!==判斷class類型對(duì)象是否同一個(gè)引用,而不是用==!=

推薦

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

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

3.10 協(xié)議

3.10.1 當(dāng)實(shí)現(xiàn)protocol時(shí),如果確定protocol的實(shí)現(xiàn)不會(huì)被重寫(xiě),建議用extension將protocol實(shí)現(xiàn)分離

推薦

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 當(dāng)方法最后一個(gè)參數(shù)是Closure類型,調(diào)用時(shí)建議使用尾隨閉包語(yǔ)法, 但只在只存在一個(gè)閉包參數(shù)時(shí)才使用尾閉包。

推薦

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 高階函數(shù)推薦最簡(jiǎn)化語(yǔ)法

推薦

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 訪問(wèn)控制 (優(yōu)先考慮最低級(jí))

  • private

  • fileprivate

  • internal (默認(rèn)忽略不寫(xiě))

  • public

  • open

訪問(wèn)控制權(quán)限關(guān)鍵字應(yīng)該寫(xiě)在最前面,除了@IBOutlet、@IBAction、@discardableResult、@objc等關(guān)鍵字.

推薦

// 類似注解修飾詞單獨(dú)占一行
@objc
func print(message: String) -> String {
    /* ... */
    return xxx
}

3.15 如調(diào)用者可以不使用方法的返回值,則需要使用@discardableResult標(biāo)明

推薦

@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 循環(huán)引用

3.17.1 使用委托和協(xié)議時(shí),避免循環(huán)引用,定義屬性的時(shí)候使用weak修飾

推薦

public weak var dataSource: UITableViewDataSource?

public weak var delegate: UITableViewDelegate?

3.17.2 在逃逸Closures中使用self時(shí)避免循環(huán)引用

推薦

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 使用方法作為閉包參數(shù)時(shí), 應(yīng)注意循環(huán)引用問(wèn)題

不推薦

func abc() {
    // 內(nèi)嵌方法
    func close() {
        // 訪問(wèn)了外部self 存在循環(huán)引用
        self.controller.dismiss()
    }
    // 作為閉包參數(shù)
    view.set(close)
    xxxx.closeHandle = close
}

推薦

func abc() {
    weak var `self` = self
    // 內(nèi)嵌方法
    func close() {
        // 訪問(wèn)weak修飾的self 不存在循環(huán)引用
        self?.controller.dismiss()
    }
    // 作為閉包參數(shù)
    view.set(close)
    xxxx.closeHandle = close
}

??同樣上面的代碼如果func close() 不是內(nèi)嵌方法, 而是與func abc()同級(jí)的方法, 那么必然存在循環(huán)引用, 需要重點(diǎn)注意.

不推薦

func close() {
    self.controller.dismiss()
}

func abc() {
    // 作為閉包參數(shù)
    view.set(close)
    xxxx.closeHandle = close
}

如果一定要使用同級(jí)的方法可用以下方法暫時(shí)解決:

推薦

func close() {
    self.controller.dismiss()
}

func abc() {
    weak var `self` = self
    // 內(nèi)嵌方法
    func close() {
        self?.close()
    }
    // 作為閉包參數(shù)
    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()

    /* ... */
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容