1. 程式人生 > 其它 >swift程式碼統一編碼規範

swift程式碼統一編碼規範

編碼規範

背景

隨著團隊擴大,人員增多。需要統一編碼規範

規範

命名-明確的使用含義

  • 請使用駝峰命名規則
//推薦 class UserInfo{ var userInfo: UserInfo? } //不推薦 class Userinfo{ var user_info: Userinfo? }
  • 首字母大寫
//推薦 class UserInfo{} //不推薦 class userinfo{}
  • 用統一的標識開頭:YP
//推薦 class YPUserInfo{} //不推薦 class userinfo{}
  • 控制器
    • VC結尾
//推薦 class YPUserInfoVC{} class YPUserViewVC{} //不推薦 class YPUserInfo{} class YPUserInfoViewContrller{}
  • 命名應該具有標識性
    • 不能使用拼音
    • 不能使用過於簡單的縮寫
//推薦 class YPRecruitVC{} //招工控制器 class YPRecruitListVC{} //招工列表控制器 class YPRecruitDetailVC{} //招工詳情控制器 //不推薦 class YPAViewVC{} class YPZhaoGonVC{} class YPZGVC{}
  • 檢視命名
    • 常規以View結尾:UIContentView
    • tableView的cell以TCell為字尾:YPBaseTCell
    • UICollectionView的cell以Cell為字尾:YPBaseCCell
    • vm
    • 所有命名應該具有描述性
  • 屬性應該是一個名詞
//推薦 let isShow: Bool let count: Int //不推薦 let s: Bool
  • 區域性變數
    • 需要遵守命名規範
    • 使用具有代表意義的名詞
例如:modelitemtempdataSource //推薦 for i in dataSource {} 為了減少不必要的屬性申明 for model in models{} models.foreach{$0.name = ""} models.foreach{ model in model.name = "1" model.id = "2" } //不推薦 for a in dataSource {} for m in models{} models.foreach{ model in model.name = "" } models.foreach{ f in f.name = "" f.id = "" }
  • 常量
    • 常量的名字需要大寫首字母並保持駝峰:KLastChoosedOccsInRecruitList
    • 避免使用全域性常量,轉而使用結構體和類
/// 推薦 struct YPConfig{ static let KLastChoosedOccsInRecruitList = "KLastChoosedOccsInRecruitList" } //不推薦 let KLastChoosedOccsInRecruitList = "KLastChoosedOccsInRecruitList"
  • 列舉
    • 以enum結尾
    • Case的命名
      • 小寫字母開頭
      • 名詞或者動詞
      • 駝峰規則
//推薦 enum YPOperationEnum{ case add case remove } //不推薦 enum YPOperation{ case Add case add_user case auser }
  • 型別
    • 根據變數、引數、關聯型別的作用來命名,而不是基於它們的型別
//推薦 var greeting = "Hello" protocol ViewController { associatedtype ContentView : View } class ProductionLine { func restock(from supplier: WidgetFactory) } //不推薦 var string = "Hello" protocol ViewController { associatedtype ViewType : View }class ProductionLine { func restock(from widgetFactory: WidgetFactory) }
  • 協議
    • 描述事物的協議,讀起來應該像名詞(例如,Collection
    • 描述能力的協議,應該使用字尾ableibleing
例如,Equatable,ProgressReporting
  • 協議方法,第一個未命名引數應該是委託資料來源
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int

方法

  • 方法或者函式名最好能在呼叫處形成符合語法規範的英語短語
//推薦 x.insert(y, at: z) // “x, insert y at z” x.subViews(havingColor: y) //“x's subviews having color y” x.capitalizingNouns() //“x, capitalizing nouns” //不推薦 x.insert(y, position: z) x.subViews(color: y) x.nounCapitalize()
  • 省略無用的單詞。每個單詞都需要傳達出相應的關鍵資訊
//推薦 public mutating func remove(_ member: Element) -> Element? //不推薦 public mutating func removeElement(_ member: Element) -> Element?
  • 為了使用起來更流暢,可以從第二個或者第三個引數開始降低命名要求,前提是這些引數不影響整個 API 的語義
AudioUnit.instantiate(with: description, options: [.inProcess], completionHandler: stopProgressBar)
  • 工廠方法用make開頭:x.makeWidget(cogCount: 47)
  • 構造器和工廠方法的第一個引數命名不應該考慮方法名,應該獨立命名,如:x.makeWidget(cogCount: 47)
//推薦 let foreground = Color(red: 32, green: 64, blue: 128) let newPart = factory.makeWidget(gears: 42, spindles: 14) let ref = Link(target: destination) //不推薦,下面的例子中,試圖將第一個引數名和方法名拼成連續的短語 let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128) let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14) let ref = Link(to: destination)
  • 沒有副作用的方法和函式讀起來應該像名詞短語
例如,x.distance(to: y)i.successor()
  • 有副作用的方法和函式讀起來應該像祈使動詞
例如,print(x)x.sort()x.append(y)
  • 可變/不可變方法的命名要成對出現。一個可變方法通常都有一個不可變方法與之對應,二者的語義相近,區別在於前者更新例項,而後者返回一個新值
    • 當一項操作恰好能夠被一個動詞描述時,使用動詞原形為可變方法命名;使用動詞的過去分詞 (ed) 或現在分詞 (ing) 為不可變方法命名
  • 命名不可變方法,最好使用過去分詞(通常是增加字尾 “ed”)
/// Reverses `self` in-place. mutating func reverse() /// Returns a reversed copy of `self`. func reversed() -> Self ... x.reverse()let y = x.reversed()
  • 如果由於動詞後面直接跟隨一個物件,無法新增 “ed” 時,使用現在分詞命名不可變方法,即字尾 “ing”
/// Strips all the newlines from `self` mutating func stripNewlines() /// Returns a copy of `self` with all the newlines stripped. func strippingNewlines() -> String ... s.stripNewlines()let oneLine = t.strippingNewlines()
  • 當一項操作恰好能夠被一個名詞描述時,使用名詞本身為不可變方法命名;使用名詞前加 “form” 的方式為可變方法命名。
  • 對於返回值是布林型別的方法和屬性,讀起來應該像是對被呼叫物件的斷言,其使用場景是不可變方法。例如,x.isEmptyline1.intersects(line2)
  • 避免使用全域性函式,轉而使用方法和屬性。以下情況例外
    • 沒有明顯的self:min(x, y, z)
  • 函式是不受限的範型函式:print(x)
  • 在特定的領域中已經有約定俗成函式語法在:sin(x)
  • 對於沒有引數的方法
//推薦 var method: Elenum{ //coding } //不推薦 func method(){ //coding }

註釋

/// 類註釋[會在程式碼提示中顯示]:使用者資訊模型 class YPUserInfoModel{ /// 屬性註釋[會在程式碼提示中顯示]:使用者id var id: String? /// 屬性註釋[會在程式碼提示中顯示]:使用者暱稱 var name: String? } //MARK: - 程式碼模組註釋[在檔案目錄中顯示] extension YPUserInfoModel{ /// 方法註釋[會在程式碼提示中顯示]:更新使用者暱稱 /// - Parameters: /// - userName: 使用者暱稱 /// - Returns: model func update(userName: String) -> Self{ //內部說明: 邏輯說明 name = userName } } class YPHomeListVC: UIViewController{ //MARK: 業務屬性 /// 操作型別 let operation: Operation = .normal /// 頁碼 var page: Int = 0 ... //MARK: UI屬性[懶載入] /// 頭檢視 lazy var headView: UIView = { let view = UIView() //coding return view }() /// 表格 lazy var tableView: UITableView = { let view = UITableView() //coding return view }() }
  • 所有的類必須新增類註釋
  • 所有屬性必須加註釋
  • 方法必須加註釋,方法中的引數和返回需要有註釋
  • 方法內部,複雜邏輯需要新增邏輯說明
  • 方法中的引數需要新增明確的作用說明,如果有返回值也需要說明
  • 複雜邏輯 註釋在程式碼處

程式碼組織結構

目錄結構

  • Main
    • 標籤模組1
      • Home
        • View
          • Cell
        • Controller
        • ViewModel
      • 功能模組1
        • 。。。
      • 功能模組2
        • 。。。
    • 標籤模組2
    • 標籤模組3
    • 。。。
  • Model
  • Resource
    • 標籤模組
      • name.svg

控制器中列舉和結構體的宣告

訪問域

  • 明確屬性、方法、類的訪問域:privatefileprivateinternalpublicopen
  • 同訪問域的方法應該通過extension的程式碼塊進行整合【不合理】
class YPContentView{ private var isShowAlert: Bool = false } private extension YPContentView{ private func method1(){} private func method2(){} } extension YPContentView{ func method3(){} func method4(){} } public extension YPContentView{ func method5(){} func method6(){} }
  • 只開放get的許可權
// 推薦 private(set) var name: String? //不推薦 private var _name: String? var name: String?{return name}

屬性申明

  • 對於不需要修改的內容使用let
class YPBaseTCell: UITableViewCell{ private let operation: YPOperationEnum init(operation: YPOperationEnum){ self. operation = operation super.ini() } }

程式碼

空格

  • 等號前後需有空格
//推薦 isHidden = false //不推薦 isHidden=false
  • if的判斷條件前後需有空格
//推薦 if let temp = [].first { } if true { } guard true else {return} //不推薦 if let temp = [].first{ } if true{ } guard true else{return}

換行

  • 程式碼塊
//推薦 func method { //coding } for i in [1,2] { //coding } if true { //coding } guard true else { //coding } //不推薦 func method { //coding } for i in [1,2] { //coding } if true { //coding } guard true else{ //coding }

寫法

  • 應該使用 +=, -=, *=, /=
var lookedNum: Int = 0 //推薦 lookedNum += 1 //不推薦 lookedNum = lookedNum + 1

懶載入

  • controller中的UI必須使用懶載入
  • 懶載入的內部檢視 統一使用view,不要與其本身相同
  • 請新增合適且明確的訪問域
//推薦 private lazy var headView: YPRecruitSendResultTopHeaderView = { let view = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, width: YPScreen.screen_w, height: 190.scale)) return view }() //不推薦 lazy var headView: YPRecruitSendResultTopHeaderView = { let headView = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, width: YPScreen.screen_w, height: 190.scale)) return headView }() lazy var headView: YPRecruitSendResultTopHeaderView = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, width: YPScreen.screen_w, height: 190.scale))
  • 在懶載入中不要直接addSubView
//推薦 class YPHomeListVC{ private lazy var headView: YPRecruitSendResultTopHeaderView = { let view = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, width: YPScreen.screen_w, height: 190.scale)) return view }() override func makeUI() { super.makeUI() view.addSubview(headView) headView { make in make.edges.equalToSuperview() } } } //不推薦 class YPHomeListVC{ private lazy var headView: YPRecruitSendResultTopHeaderView = { let view = YPRecruitSendResultTopHeaderView.init(frame: .init(x: 0, y: 0, width: YPScreen.screen_w, height: 190.scale)) self.view.addSubView(view) return view }() }

記憶體

Block

  • 型別申明
//推薦 class YPHomeListVC{ // typealias Call = () -> Void // var call: Call? } //不推薦 class YPHomeListVC{ var call: (() -> Void)? }

weak 和 unowned

//推薦 let call: Call = {[weak self] in guard let weakSelf = self else {return} //coding } //不推薦 let call: Call = {[weak self] in guard let self = self else {return} //coding }

podfile

  • 接入的第三方庫,必須直接指定版本

彈窗

  • fYPProgressHUD
  • YPLableAlertView
  • YPStateAlertView

MVVM

input、output、transform

為了減少不必要的屬性申明,在VC和VM的互動中。部分邏輯和 事件應該通過下面的方式進行互動 不支援在 Docs 外貼上 block f
  • VC不需要引用目標,例如提交方法、登入方法
class YPHomeListVM: YPBaseViewModel{ } extension YPHomeListVM: ViewModelType{ struct Input { let header: ControlEvent<Void> let footer: ControlEvent<Void> } struct Output { let state: Observable<YPRefreshState> } func transform(input: Input) -> Output { let state = input.header.flatMap{ //coding } return .init(state: state) } } class YPHomeListVC: YPBaseVMController<YPHomeListVM>{ override func bindViewModel() { let out = viewModel.transform(input: .init(header: tableView.rx.header, footer: tableView.rx.footer)) out.state.bind(to: tableView.rx.endRefresh).disposed(by: viewModel.rx.disposeBag) } } 如上所示,YPHomeListVC不需要在其它地方訪問重新整理的狀態。VM不需要在其它地方監聽下拉和上拉時間,這種情況就通過input和output進行互動

VM中的Rx

  • 使用let
//推薦 /// 排序型別 let sort = BehaviorRelay<SortEnum>(value: .newest) //不推薦 /// 排序型別 var sort = BehaviorRelay<SortEnum>(value: .newest) private(set) var sort = BehaviorRelay<SortEnum>(value: .newest)
  • 避免對controller的引用,需要使用controller的時候,請用回撥和Rx的方式放在controller中
class YPHomeListVM: YPBaseViewModel{ //不推薦 weak var hostVC: UIViewController? }

業務

網路庫

errCode

在業務場景中,建議不要直接使用字串,改用列舉型別 /// 推薦 if YPWhiteListEnum.paidIssue.rawValue == response.errCode {// "paid_issue" 付費釋出提示 paidSendAlert(response: response) } /// 不推薦 if "integral_lack" == response.errCode {// 付費釋出積分不足 integralLackAlert(response: response) }

推薦

推薦使用isEmpty

//推薦 "sadasdsa".isEmpty [1,2].isEmpty //不推薦 "sadasdsa".count == 0 [1,2].count == 0

禁止強制解包

//推薦 guard let value = values2 as? String else {return} //不推薦 let value = value2 as! String

陣列取值,需要判斷陣列是否下標越界

//推薦 let source: [String] = ["1"] if source.count > 1{ let value: String = source[1] } let value: String? = source.safe(idx: 1) //不推薦 let value: String = source[1]

獲取系統版本號,禁止強制直接轉數值型別

let versionString = "14.2.1" //推薦 let versions:[Int] = versionString.components(separatedBy: '.').map{Int($0) ?? 0} //不推薦 let vaersion: CGFloat = CGFloat(versionString)

不推薦使用public、fileprivate等修飾符 修飾cextension擴充套件

//推薦 extension YPHomeViewModel{ fileprivate func medthod(){} fileprivate func medthod(){} } //不推薦 fileprivate extension YPHomeViewModel{ func medthod(){} func medthod(){} }

SnpKit

  • 約束的程式碼儘量精簡
//推薦 openNoticeContentView.snp.makeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalTo(advertisingContentView.snp.bottom) make.height.equalTo(0) } //不推薦 openNoticeContentView.snp.makeConstraints { (make) in make.left.equalTo(0) make.right.equalTo(0) make.top.equalTo(advertisingContentView.snp.bottom).offset(0) make.height.equalTo(0) }
  • 適配劉海屏
  • 路由
//不推薦 aaa_aaaa_aaaa //推薦 aaa/aaa/aaa