iOS代碼規範(Swift 與 OC 混編版)

前言

按照自己的理解整理的 iOS 代碼規範,部分規範參考了網上現有的一些資料,希望對大家有所幫助

編碼規範

項目結構規範

項目結構圖

|-業務1
|   |-業務1的Storyboard
|   |-子業務1
|   |   |-controller
|   |   |-view
|   |   `-model
|   |-子業務2
|   |   |-controller
|   |   |-views
|   |   `-models
|-業務2
|   |-業務2的Storyboard
|   |-子業務1
|   |   |-controller
|   |   |-view
|   |   `-model
|   |-子業務2
|   |   |-controller
|   |   |-view
|   |   `-model
|-util(工具)
|-lib(第三方庫)
|-ui(自定義的一些基礎view)
|-Images.xcassets(xcode自帶圖片管理文件)
`-Resources(資源文件,包括靜態文件,本地化文件等)

原則

  1. 如果對應目錄下有多個相關的類,則controller,view,model的名字相應變爲controllers,views,models
  2. Images.xcassets中的目錄結構要與業務保持一致,從而方便查找和替換圖片

註釋

  1. 註釋可以採用“/* */“和“//“兩種註釋符號,涉及到多行註釋時,儘量使用“ /* */“
  2. 對於一行代碼的註釋可放在前一行及本行上,不允許放在下一行,更不允許在一行語句的中間加入註釋
  3. 不必每行都加註釋,在3~10行左右的段落做註釋要好於每行都做註釋,顯而易見的代碼不加註釋。例如:

    if (!returnValue){      //調用登錄過程失敗          ←無用的註釋
        NSLog(@”登錄失敗”);
    }

排版格式

  1. 代碼的縮進應使用空格(SPACE),不能使用製表符(TAB),並且縮進以2個字符爲單位
  2. 括弧遵循緊湊編碼方式,如下:

    for (int i = 0; i < 10 ; i++) {
    }
  3. 空格的使用

    • 關鍵字與其後的表達式之間要有空格,如:

      if (expr)
      或
      for (expr)
    • 單目操作符不應與它們的操作數分開(如“!“和“^“等)
    • 除“,“外,其它雙目操作符應與它們的操作數用空格隔開,如:

      i=i+1;              //錯誤的寫法,操作符兩端沒有空格
      i = i + 1;          //正確的寫法,
      if(a>b)             //錯誤的寫法,邏輯判斷符號兩端沒有空格
      if(a > b)           //正確的寫法
    • .h中協議<>前面有一個空格
    • .h中成員聲明時,類型與變量之間有至少1個空格。*號靠近變量,不靠近類型
    • @property後留1個空格,()裏面,逗號緊跟前一變量,與後一變量之間留1個空格。()外面,先留1個空格,再聲明屬性
    • 方法的+,-後面與()之間留1個空格
    • 返回類型與之間留1個空格,方法參數中返回類型與之間留1個空格
    • 在多參數方法中,每個參數後面都有1個空格
  4. 每行只能有一個語句
  5. 關於空行
    • .h中的空行
      • 文件說明與頭文件包含(#import)之間空1行
      • 頭文件包含(#import)之間,如果需要分類區別,各類別之間空1行
      • 頭文件包含(#import)與@class之間空2行
      • @interface與@class之間空1行
      • 頭文件{}裏面,空1行開始聲明對象成員,如果需要分類區別,各類別之間空1行
      • 頭文件{}外,空1行書寫屬性,如果需要分類區別,各類別之間空1行
      • 屬性下面空1行開始寫方法,如果需要分類區別,各類別之間空1行
      • 方法完成後,空1行@end
      • 如果需要聲明protocol,空2行接着寫。通常protocol寫在@end後面,但是聲明在@interface之前
    • .m中的空行
      • 文件說明與頭文件包含(#import)之間空1行
      • 頭文件包含(#import)之間,如果需要分類區別,各類別之間空1行
      • @implementation和@synthesize之間空1行, 如果需要分類區別,各類別之間空1行
      • @synthesize與方法之間空1行
      • 方法與方法之間空1行
    • 方法裏面的空行
      • 變量聲明後需要空1行,如果需要分類區別,各類別之間空1行
      • 條件、循環,選擇語句,整個語句結束,需要空1行
      • 各功能塊之間空1行
      • 最後一個括弧之前不空行
      • 註釋與代碼之間不空行
      • #pragma mark 與方法之間空1行
    • 每行代碼最多不得操作100個字。設置如下:Xcode -> Preferences -> TextEditing
      -> Page Guide at column,輸入 100即可

命名規範

文件命名規範

目錄

  1. 目錄的名字採用第一個單詞首字母小寫,其他單詞首字母大寫的格式,如:contactList、controller
  2. 目錄主要用來對業務做區分,所以目錄的名字要有意義。如:
    contactList:代表這是聯繫人列表的業務
    controller:代表這是MVC的控制部分

資源文件

資源文件的名字採用第一個單詞首字母小寫,其他單詞首字母大寫的格式,最後以文件類型結尾,如:
contactImage:代表這是聯繫人圖片
contactInfoDB:代表這是聯繫人數據庫文件
contactLocalizable:代表這是聯繫人業務的本地化文件

代碼命名規範

方法

  1. 方法的名稱應全部使用有意義的單詞組成,且以小寫字母開頭,多單詞組合時,後面的單詞首字母大寫。如:

    -(void)getContactInformation
  2. 設置類變量的內容的方法應使用set作爲前綴,讀取變量的內容的方法應使用get作爲前綴。如:

    -(void)getContactName;
    -(void)setContactName:(NSString *)contactName;
  3. 方法中的參數:第一個參數名稱要從函數名稱上攜帶出來,第二個參數的首字母小寫,多個單詞組合時,後面單詞首字母大寫。參數有別名時,參數別名與參數名一致,但參數名前綴以_。參數別名與前一參數保留1個空格。參數無別名時,以有意義的字母命名。如:

    -(void)myFunctionWithSizeA:(CGSize)sizeA sizeB:(CGSize)_sizeB;

變量

  1. 變量必須起有意義的名字,使其他組員可以很容易讀懂變量所代表的意義,變量命名可以採用同義的英文命名,可使用幾個英文單詞,第一個單詞首字母小寫,其他單詞首字母大寫。如:

    NSString  *contactName;
  2. 對於一些特殊類型的變量,命名時要帶上類型,如NSArray 的變量命名爲xxxArray,其他的如xxxDictionary,xxxSize等。這樣就可以從名稱上知道是什麼類型的變量。千萬不能將NSArray的變量命名爲xxxDictionary。

  3. 對於要和interface builder關聯的的輸出口變量,命名時要後綴以特定的控件名。如:

    IBOutlet UILabel *contactNameLabel;
  4. 對於使用c語言形式聲明的變量,一些特定類型可採用一定的簡寫。如:
    指針類型:P
    結構體類型:Rec
    數組類型:Arr
    Core Graphic:CG
    等。
    循環控制變量通常使用單一的字符如:i、j、k等。使用有意義的名字,如objectIndex也是可以的

  5. 儘量避免使用全局變量,如果必須使用全局變量則必須加前綴“Pub_“,同時應在變量名稱中體現變量的類型
  6. 私有實例變量前加一個下劃線,如_myPrivateVarible
  7. 枚舉變量也要有相應的前綴來區分不同的enum變量。比如蘋果公司的一個enum。如:

    typedef enum CGPathDrawingMode CGPathDrawingMode;
    /* Drawing modes for text. */
    
    enum CGTextDrawingMode 
    {
         kCGTextFill,
         kCGTextStroke,
         kCGTextFillStroke,
         kCGTextInvisible,
         kCGTextFillClip,
         kCGTextStrokeClip,
         kCGTextFillStrokeClip,
         kCGTextClip
    };

常量

  1. 避免在程序中直接出現常數,使用超過一次的應以宏定義的形式來替代
  2. 常數的宏定義應與它實際使用時的類型相一致。如以3.0來定義浮點類型,用3表示整型
  3. 常量的命名應當能夠表達出它的用途,並且用大寫字母表示。如:

    
    #define PI 3.1415926
    
  4. 一些常量前加特殊前綴,可以作爲不同常量的區分,如:

    UserDefaultsKey的變量前加UDKEY_,
    NotificationNameKey前面加NNKEY_,
    DictionaryKey前面加DICTKEY_

  1. 所有的類名前加MB,以區分業務類和引入的第三方類
  2. 所有的類名,接口名(Protocol)均以大寫字母開頭,多單詞組合時,後面的單詞首字母大寫。類,接口名必須是有意義的
  3. 繼承自UIView的類以View結尾,如:MBContactInfoViewMBLabelView
  4. 繼承自UIViewController的類以ViewController結尾,如:MBContactInfoViewControllerMBContactDetailViewController等。

架構規範

整體設計

  1. 遵循嚴格的MVC結構設計。
  2. 強化View層和Model層的功能,Control層只做調度:
    • 界面展示邏輯完全在View層控制,Control層通過View層提供的一個接口來將數據傳遞給View層,然後由View層來做具體的界面展示控制。
    • 數據的加工在Model層實現,Model層通過重寫setter或getter來對數據進行加工。
    • Control層負責響應事件和回調,然後發送網絡請求等。
  3. 禁止出現功能不明確的類。類必須和具體業務對應

類設計

UIViewController

原則

  1. UIViewController中儘量不要出現直接設置控件(UIButton,UITextField等)屬性的代碼,而是將控件封裝在單獨的自定義UIView類中,然後在自定義UIView類中設置控件的屬性。如下的代碼最好不要在UIViewController中出現:

    self.textLabel = [[UILabel alloc] init];
    self.textLabel.textColor = [UIColor blackColor];
    self.textLabel ... ...
    self.textLabel ... ...
    self.textLabel ... ...
  2. 封裝私有initContentupdateContent方法,前者在viewDidLoad中調用,用來初始化所有的自定義UIView,後者在viewWillAppear中調用,用來更新所有的自定義UIView。
  3. 所有的生命週期方法都必須調用super方法。

結構

import UIKit

class SwiftController: MBViewController {

    // 屬性

    @IBOutlet weak var queryButton: UIButton!

    @IBOutlet weak var contentView: MBContentView!

    @IBOutlet weak var tableView: UITableView!

    var contactTitle:NSString?

    // 生命週期
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        // 與界面無關屬性的初始化
        self.contactTitle = "This is Test"

        // 界面的初始化在統一的方法initContent中執行
        self.initContent()
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        // 界面的更新在統一的方法updateCotent中執行
        self.updateCotent()
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)

        // 添加對通知事件的觀察
        NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("queryFinished:"), name: "queryFinished", object: nil)
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
    }

    override func viewDidDisappear(animated: Bool) {
        super.viewDidDisappear(animated)

        // 移除對通知事件的觀察
        NSNotificationCenter.defaultCenter().removeObserver(self, name: "queryFinished", object: nil)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // tableView協議
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        // #warning Potentially incomplete method implementation.
        // Return the number of sections.
        return 5
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete method implementation.
        // Return the number of rows in the section.
        return 10
    }

    func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return String(format: "Section %d", section)
    }

    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cellIdentifier : String = "cellIdentifier"
        var cell:UITableViewCell? = self.tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell
        if cell == nil {
            cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: cellIdentifier)
        }
        cell?.textLabel?.text = String(format: "Cell %d", indexPath.row)
        cell?.detailTextLabel?.text = "Watch out!!! Something AWESOME is coming~~~"
        return cell!
    }

    // 事件響應
    @IBAction func queryPressed(sender: AnyObject) {

    }

    func queryFinished(sender: AnyObject) {

    }

    // 私有方法
    private func initContent() {

        // 如果需要設置多個屬性則爲每個View封裝單獨的方法來進行View的初始化
        self.initQueryButton()
    }

    private func updateCotent() {
    }

    private func initQueryButton () {
        self.queryButton.setTitle("Query", forState: UIControlState.Normal)
        self.queryButton.setTitle("Query...", forState: UIControlState.Disabled)
    }

    /*
    // MARK: - Navigation
    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */

}

UIView

原則

  1. 爲每個自定義UIView建立xib(xib和UIView類關聯的方式見DEMO),靜態的約束在xib中直接添加,動態的約束使用Masonry在代碼中添加。
  2. 儘量使用Autolayout做界面約束,只有在Autolayout不支持的情況下才使用設置frame的方法來做界面約束。
  3. 展示的邏輯在UIView內部控制,如果UIView中某幾個控件的控制邏輯比較複雜,則需要封裝單獨的處理方法。

結構

import UIKit

class MBContentView: UIView {

    @IBOutlet var contentView: UIView!
    @IBOutlet var imageView: UIImageView!
    @IBOutlet var label: UILabel!

    /*
    // Only override drawRect: if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func drawRect(rect: CGRect) {
        // Drawing code
    }
    */

    // 生命週期
    // 在該方法中初始化View
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        // 加載xib內容
        NSBundle.mainBundle().loadNibNamed("ContentView", owner: self, options: nil)
        self.addSubview(self.contentView)

        // 在統一的initConstraint方法中添加約束
        self.initConstraint()
    }

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

    // 私有方法
    private func initConstraint(){
        // OC 用 Masonry, Swift 用 SnapKit
        self.contentView.mas_makeConstraints { (MASConstraintMaker) -> Void in
            var make = MASConstraintMaker
            make.edges.equalTo().self
//            make.top.equalTo().self
//            make.bottom.equalTo().self
//            make.leading.equalTo().self
//            make.trailing.equalTo().self
        }
    }

    // 提供接口供ViewController調用
    func setQueryResult(queryResult:MBQueryResult?) {
        self.label.text = queryResult?.contactName
    }

}

Form/Model

原則

  1. 數據的加工處理一般放在Form/Model中,如下:

    -(void)setPoint:(NSString *)point{
        if ([point isEqualToString:@"null"]){
            _point = @"0";
        }else{
            _point = point;
        }
    }
  2. 如果需要用到多個Model中的部分屬性,則需要建立新的Model。

Category

原則

  1. Category的命名要明確且跟具體的業務相關
  2. 能不用Category實現的功能就不用Category實現

Protocol

原則

  1. Portocol的命名要明確且跟具體的業務相關
  2. 如果Portocol實現的方法不超過3個,可以直接定義在協議相關的類中,如下:

    @protocol XXXDelegate <NSObject>
    -(void)method1:(id)obj;
    -(void)method2:(id)obj;
    -(void)method3:(id)obj;
    @end
    
    @interface XXX : UIView<XXXDelegate>
    @property (weak, nonatomic) IBOutlet id<XXXDelegate>delegate;
    @end

    超過3個則需要單獨建立協議類

Manager

原則

  1. 如果Manager需要在應用的生命週期內一直存在,則實現成單例,單例實現方式如下:

    class func shareInstance()->XXManager{
        struct MBSingleton{
            static var predicate:dispatch_once_t = 0
            static var instance:XXManager? = nil
        }
        dispatch_once(&MBSingleton.predicate,{
            MBSingleton.instance=XXManager()
            }
        )
        return MBSingleton.instance!
    }
  2. 命名要和具體業務相關,禁止出現業務不明確的Manager

  3. Manager不對數據進行加工處理,而是交由Form/Model來做,類似下面的代碼不能寫在Manager內部

    if "zzz" == c.zzz {
        c.zzz = a.xxx+b.yyy
    } else {
        c.zzz = a.xxx
    }
發佈了61 篇原創文章 · 獲贊 167 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章