前言
按照自己的理解整理的 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(資源文件,包括靜態文件,本地化文件等)
原則
- 如果對應目錄下有多個相關的類,則controller,view,model的名字相應變爲controllers,views,models
- Images.xcassets中的目錄結構要與業務保持一致,從而方便查找和替換圖片
註釋
- 註釋可以採用“/* */“和“//“兩種註釋符號,涉及到多行註釋時,儘量使用“ /* */“
- 對於一行代碼的註釋可放在前一行及本行上,不允許放在下一行,更不允許在一行語句的中間加入註釋
不必每行都加註釋,在3~10行左右的段落做註釋要好於每行都做註釋,顯而易見的代碼不加註釋。例如:
if (!returnValue){ //調用登錄過程失敗 ←無用的註釋 NSLog(@”登錄失敗”); }
排版格式
- 代碼的縮進應使用空格(SPACE),不能使用製表符(TAB),並且縮進以2個字符爲單位
括弧遵循緊湊編碼方式,如下:
for (int i = 0; i < 10 ; i++) { }
空格的使用
關鍵字與其後的表達式之間要有空格,如:
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個空格
- 每行只能有一個語句
- 關於空行
- .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即可
- .h中的空行
命名規範
文件命名規範
目錄
- 目錄的名字採用第一個單詞首字母小寫,其他單詞首字母大寫的格式,如:contactList、controller
- 目錄主要用來對業務做區分,所以目錄的名字要有意義。如:
contactList:代表這是聯繫人列表的業務
controller:代表這是MVC的控制部分
資源文件
資源文件的名字採用第一個單詞首字母小寫,其他單詞首字母大寫的格式,最後以文件類型結尾,如:
contactImage:代表這是聯繫人圖片
contactInfoDB:代表這是聯繫人數據庫文件
contactLocalizable:代表這是聯繫人業務的本地化文件
代碼命名規範
方法
方法的名稱應全部使用有意義的單詞組成,且以小寫字母開頭,多單詞組合時,後面的單詞首字母大寫。如:
-(void)getContactInformation
設置類變量的內容的方法應使用set作爲前綴,讀取變量的內容的方法應使用get作爲前綴。如:
-(void)getContactName; -(void)setContactName:(NSString *)contactName;
方法中的參數:第一個參數名稱要從函數名稱上攜帶出來,第二個參數的首字母小寫,多個單詞組合時,後面單詞首字母大寫。參數有別名時,參數別名與參數名一致,但參數名前綴以_。參數別名與前一參數保留1個空格。參數無別名時,以有意義的字母命名。如:
-(void)myFunctionWithSizeA:(CGSize)sizeA sizeB:(CGSize)_sizeB;
變量
變量必須起有意義的名字,使其他組員可以很容易讀懂變量所代表的意義,變量命名可以採用同義的英文命名,可使用幾個英文單詞,第一個單詞首字母小寫,其他單詞首字母大寫。如:
NSString *contactName;
對於一些特殊類型的變量,命名時要帶上類型,如NSArray 的變量命名爲xxxArray,其他的如xxxDictionary,xxxSize等。這樣就可以從名稱上知道是什麼類型的變量。千萬不能將NSArray的變量命名爲xxxDictionary。
對於要和interface builder關聯的的輸出口變量,命名時要後綴以特定的控件名。如:
IBOutlet UILabel *contactNameLabel;
對於使用c語言形式聲明的變量,一些特定類型可採用一定的簡寫。如:
指針類型:P
結構體類型:Rec
數組類型:Arr
Core Graphic:CG
等。
循環控制變量通常使用單一的字符如:i、j、k等。使用有意義的名字,如objectIndex也是可以的- 儘量避免使用全局變量,如果必須使用全局變量則必須加前綴“Pub_“,同時應在變量名稱中體現變量的類型
- 私有實例變量前加一個下劃線,如_myPrivateVarible
枚舉變量也要有相應的前綴來區分不同的enum變量。比如蘋果公司的一個enum。如:
typedef enum CGPathDrawingMode CGPathDrawingMode; /* Drawing modes for text. */ enum CGTextDrawingMode { kCGTextFill, kCGTextStroke, kCGTextFillStroke, kCGTextInvisible, kCGTextFillClip, kCGTextStrokeClip, kCGTextFillStrokeClip, kCGTextClip };
常量
- 避免在程序中直接出現常數,使用超過一次的應以宏定義的形式來替代
- 常數的宏定義應與它實際使用時的類型相一致。如以3.0來定義浮點類型,用3表示整型
常量的命名應當能夠表達出它的用途,並且用大寫字母表示。如:
#define PI 3.1415926
一些常量前加特殊前綴,可以作爲不同常量的區分,如:
UserDefaultsKey的變量前加UDKEY_, NotificationNameKey前面加NNKEY_, DictionaryKey前面加DICTKEY_
類
- 所有的類名前加MB,以區分業務類和引入的第三方類
- 所有的類名,接口名(Protocol)均以大寫字母開頭,多單詞組合時,後面的單詞首字母大寫。類,接口名必須是有意義的
- 繼承自UIView的類以View結尾,如:
MBContactInfoView
,MBLabelView
等 - 繼承自UIViewController的類以ViewController結尾,如:
MBContactInfoViewController
,MBContactDetailViewController
等。
架構規範
整體設計
- 遵循嚴格的MVC結構設計。
- 強化View層和Model層的功能,Control層只做調度:
- 界面展示邏輯完全在View層控制,Control層通過View層提供的一個接口來將數據傳遞給View層,然後由View層來做具體的界面展示控制。
- 數據的加工在Model層實現,Model層通過重寫setter或getter來對數據進行加工。
- Control層負責響應事件和回調,然後發送網絡請求等。
- 禁止出現功能不明確的類。類必須和具體業務對應
類設計
UIViewController
原則
UIViewController中儘量不要出現直接設置控件(UIButton,UITextField等)屬性的代碼,而是將控件封裝在單獨的自定義UIView類中,然後在自定義UIView類中設置控件的屬性。如下的代碼最好不要在UIViewController中出現:
self.textLabel = [[UILabel alloc] init]; self.textLabel.textColor = [UIColor blackColor]; self.textLabel ... ... self.textLabel ... ... self.textLabel ... ...
- 封裝私有
initContent
和updateContent
方法,前者在viewDidLoad中調用,用來初始化所有的自定義UIView,後者在viewWillAppear中調用,用來更新所有的自定義UIView。 - 所有的生命週期方法都必須調用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
原則
- 爲每個自定義UIView建立xib(xib和UIView類關聯的方式見DEMO),靜態的約束在xib中直接添加,動態的約束使用Masonry在代碼中添加。
- 儘量使用Autolayout做界面約束,只有在Autolayout不支持的情況下才使用設置frame的方法來做界面約束。
- 展示的邏輯在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
原則
數據的加工處理一般放在Form/Model中,如下:
-(void)setPoint:(NSString *)point{ if ([point isEqualToString:@"null"]){ _point = @"0"; }else{ _point = point; } }
如果需要用到多個Model中的部分屬性,則需要建立新的Model。
Category
原則
- Category的命名要明確且跟具體的業務相關
- 能不用Category實現的功能就不用Category實現
Protocol
原則
- Portocol的命名要明確且跟具體的業務相關
如果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
原則
如果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! }
命名要和具體業務相關,禁止出現業務不明確的Manager
Manager不對數據進行加工處理,而是交由Form/Model來做,類似下面的代碼不能寫在Manager內部
if "zzz" == c.zzz { c.zzz = a.xxx+b.yyy } else { c.zzz = a.xxx }