iOS代碼開發詳細規范

一、命名

1.屬性對象和局部變量(采用小駝峰命名法)

UI視圖對象

變量名稱 + (Label / Button / Cell / TableView / WebView / ScrollView / CollectionView / ImageView / TabBar / TextField ...)
如:UILabel *titleLabel;
特殊類型:UIBarButtonItem -> ButtonItem.

非UI視圖對象

VC: name + (VC / TableVC / CollectionVC / PageVC)
如: UIPageViewController *firstPageVC;
特殊類型:

  • UINavigationController -> NavCtroller
  • UITapGestureRecognizer -> TapGesture
  • UILongPressGestureRecognizer -> LongPressGesture

數據類型: name + 類型(Integer / Int / Long / Float / Double / Number / String / Array / MutableArray / IndexPath / Date / Error...)
如:NSArray *dataArray;
特殊類型:

  • NSDictionnary -> Dict
  • NSMutableDictionnary等可變類型變量,使用不可變類型的命名方式如:name + Dict
  • NSTimeInterval -> Double
  • CGRect -> Rect, CGSize -> Size

注意:1.局部變量可不遵守變量名字后面加數據類型這一規則。 2.Bool類型以is、has、can等作為前綴,或以ing、ed等作后綴。

其他:

  • name + Model
  • name + ViewModel
  • name + Request(網絡請求)
  • name + Block

注意:盡量為每個變量取有意義的名字,即“name”,但若確實沒有name的情況下,直接采用如下形式,如:UITableView *tableView;

2.實例變量

實例變量采用小駝峰命名法的基礎上,以下劃線“_”作為前綴,如:
UIButton *_loginButton;

3.常量 和 宏常量

普通常量:以小寫字母“k”開頭的駝峰命名法,如:static NSString *const kMovieCellHeight;
通知常量:一般形式為[ 觸發通知的類名] + [Did 或 Will] + [ 動作 ] + Notification ;
宏常量:全部大寫,中間用下劃線“_”作間隔,如:#define TARGET_OS_IOS
注意:

  • 如果宏常量和APP業務相關則添加相應業務類名作前綴,如果適用于整個APP則直接使用APP前綴,如:#define MOHomeCellHeaderHeight (18.0)
  • 盡量不用宏來定義常量,而是采用枚舉和const定義的常量

4.枚舉類型名稱 和 枚舉變量名稱

定義枚舉類型:采用OC風格定義枚舉類型,舉個栗子:

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {  
    UIViewAnimationTransitionNone,         //默認從0開始  
    UIViewAnimationTransitionFlipFromLeft,  
    UIViewAnimationTransitionFlipFromRight,  
    UIViewAnimationTransitionCurlUp,  
    UIViewAnimationTransitionCurlDown,  
};

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {  
    UIViewAutoresizingNone                 = 0,  
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,  
    UIViewAutoresizingFlexibleWidth        = 1 << 1,  
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,  
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,  
    UIViewAutoresizingFlexibleHeight       = 1 << 4,  
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5  
};

其中:    
1.枚舉類型名稱采用首字母大寫的駝峰命名法,并且添加相關類作為前綴    
(本例中相關的類為UIView)
2.枚舉值命名要添加枚舉類型名作前綴    
(本例中枚舉值前綴分別為UIViewAnimationTransition和UIViewAutoresizing)

枚舉變量名稱和類型名稱基本保持一致的情況下,盡量增強其可閱讀性,采用小駝峰命名法,如:

UIViewAnimationTransition animationTransitionType; 

5.方法名

采用小駝峰命名法,做到見其名知其含義,本身具有充分的解釋性,拒絕無意義命名。
例外情況:

  • 可以用一些通用的大寫字母縮寫打頭的方法,比如 PDF,TIFF
  • 可以用帶下劃線的前綴來命名私有方法或者類別中的方法

6.類名 和 協議名

類名:“統一的類名前綴” + “功能模塊簡稱”(可省略) + “描述該類的詞或詞組” + 控件類型名尾綴(如Cell,ViewController,View等,可省略)
協議名:類名 + “Delegate”
都采用大駝峰命名法。

7.分類的名稱 和 分類中方法名

分類的名稱和類名的命名方式基本一致,只是前綴由開發者指定。
但分類中的方法名,需使用分類的前綴小寫形式 + “_”的形式,如:

@interface NSDate (MOLocalDate)

/// 獲取根據時間間隔按照系統時間計算的日期,seconds:單位(秒)
+ (NSDate *)mo_dateWithTimeIntervalSince1970:(NSTimeInterval)seconds;    


8.Assets.xcassets文件命名

1.功能模塊使用的資源,使用功能模塊代碼資源同樣的文件夾結構(一級或多級)和名稱。
2.公共或通用資源文件夾取名為“Common”或“Normal”
3.圖片資源命名方式采用全部小寫和分割線分隔的形式,前綴為功能模塊名稱或縮寫,如:
common_navigation_bar_back

9.功能模塊內文件夾命名

一般情況下,各功能模塊下的文件夾命名包括但不限于以下命名:

  • View 或 CustomView
  • Model
  • ViewController
  • Cell
  • ViewModel (MVVM模式下使用)
  • Helper
  • ……

10.Xib或Storyboard中命名

  • Xib文件名和對應類名保持一致
  • Xib或者Storyboard中的控件,命名采用全部大寫且用一個空格分隔的形式。添加控件時立即添加有意義的命名,方便查看不同控件的約束關系以及后期維護。

總結:命名盡量不要用單詞簡寫,注重保持語義明確;名稱保持一致性,避免同樣語義出現多種單詞如“number”和“count”;命名風格統一,遵守統一的的命名規則。

二、風格

1.關于空格與空行

  • 數組或字典字面量用逗號隔開的形式書寫時,逗號后留有一個空格
  • ifswitch語句中,這兩個關鍵字和緊跟的括號之間留有一個空格
  • 二元符號如"="、"=="、">"、"<"、"||"、"&&"等兩側留有一個空格
  • 三元符號中"?"和":"兩側留有一個空格
  • 方法名“+”和“-”號和方法名中間留有一個空格;方法實現中“{”和方法名之間留有一個空格。
  • 定義屬性時,逗號后留有一個空格,如:
@property (nonatomic, copy, readonly) NSString *titleString;    
小括號左右兩邊各用一個空格與外部隔開;
星號“*”左邊留有一個空格。
  • 聲明類時
@interface MOCalendarService : NSObject
冒號左右各用一個空格隔開
  • 聲明分類或延展(類目)時
/// 分類
@interface NSDate (MOLocalDate)
/// 延展(類目)
@interface ClassName () <UICollectionViewDelegate,    
 UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>

- 小括號左右兩邊各用一個空格與外部隔開
- 協議列表中,逗號后留有一個空格
  • 方法與方法之間空一行,方法內的代碼塊與代碼塊之間都空一行
  • #prgma mark - Demo分隔語句上方留有一空行,下方不留空行或留有一空行。

2.注釋

  • 在需要注釋的方法或屬性上方使用Command + Alt + /”快捷鍵,或者“/// + 一個空格 + 說明”的形式進行注釋,方便別處使用“Alt + 鼠標點按”直接查詢該方法或屬性的定義。
    (注意:在屬性右側使用“/// + 一個空格 + 說明”的形式進行注釋,在別處無法用“Alt + 鼠標點按”快捷鍵獲取注釋詳情)
  • 針對方法內某一行代碼語句進行注釋,可以在此行代碼上方或右側進行“// + 一個空格 + 說明”的形式進行注釋,注意將該代碼段和其它代碼段用注釋行或空白行進行分隔。
  • 針對方法內某幾行代碼段進行注釋,也注意將該代碼段和其它代碼段用注釋行或空白行進行分隔。

總結:

  • 注釋方法和屬性盡量方便代碼使用時用快捷鍵獲取注釋內容
  • 注釋內容盡量讓不懂代碼的人能看懂在做的事情
  • 注釋代碼行或代碼段要明確區分注釋區域
  • 典型的需要注釋的地方包括:頭文件中類名、屬性和方法注釋,ifswitch語句注釋,魔術數字或字符串注釋
  • 注釋不要和Mark混淆使用。

3.Mark分區

建議開發時使用Xcode右下角的代碼快捷(Code Snippet Library)方式保存和提取常用文件Mark分區結構。
針對VC:

#pragma mark - View Lifecycle
- (void)viewDidLoad {
    [super viewDidLoad];
    [self buildingUI];
    [self makeViewConstraints];
    [self bindViewModel];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
}

#pragma mark - buildingUI
- (void)buildingUI {
}

#pragma mark - Make Constraints
- (void)makeViewConstraints {
}

#pragma mark - bindViewModel
- (void)bindViewModel {
}

#pragma mark - Delegate Methods

#pragma mark - Publich Methods

#pragma mark - Private Methods

#pragma mark - Notification Event

#pragma mark - Property Set

#pragma mark - Property Get

針對ViewModel:

#pragma mark - Life Cycle
- (instancetype)init {
    self = [super init];
    if (self) {
        [self binding];
    }
    
    return self;
}

#pragma mark - Bind
- (void)binding {
    
}

#pragma mark - Publich Methods

#pragma mark - Private Methods

#pragma mark - Property Set

#pragma mark - Property Get


針對TableViewController:

#import "<#ControllerName#>.h"

// static NSString *const <#cellNameId#> = @"<#cellId#>";

@interface <#ControllerName#> () <UITableViewDelegate, UITableViewDataSource>

// @property (nonatomic, strong) <#ViewModolClass#> *viewModel;
@property (nonatomic, strong) UITableView *tableView;

@end

@implementation <#ControllerName#>

#pragma mark - View Lifecycle
- (void)viewDidLoad {
    [super viewDidLoad];
    [self buildingUI];
    [self makeViewConstraints];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
}

#pragma mark - buildingUI
- (void)buildingUI {
    self.title = @"<#TitleName#>";
}

#pragma mark - Make Constraints
- (void)makeViewConstraints {
    [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
}

#pragma mark - UITableViewDataSource && UITableViewDelegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return <#countInteger#>;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 44.f;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    return 0.1f;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    return 0.1f;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:<#cellNameId#>];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:<#cellNameId#>];
    }
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:NO];
    // do something
    
}

#pragma mark - Publich Methods

#pragma mark - Private Methods

#pragma mark - Notification Event

#pragma mark - Property Set

#pragma mark - Property Get
// - (<#ViewModolClass#> *)viewModel {
//  if (!_viewModel) {
//      _viewModel = [[<#ViewModolClass#> alloc] init];
//  }
//  return _viewModel;
//}

- (UITableView *)tableView {
    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        // _tableView.tableFooterView = [[UIView alloc] init];
        // _tableView.showsVerticalScrollIndicator = NO;
        // _tableView.contentInset = UIEdgeInsetsMake(0, 0, 50, 0);
        [self.view addSubview:_tableView];
    }
    return _tableView;
}

@end


針對Cell:

#pragma mark - Init Method
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        [self makeViewConstraints];
    }
    return self;
}

#pragma mark - 約束布局
- (void) makeViewConstraints {
}

#pragma mark - Public Methods

#pragma mark - Private Methods

#pragma mark - Property Set

#pragma mark - Property Get

針對自定義View:

#pragma mark - Init Method
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self makeViewConstraints];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self makeViewConstraints];
    }
    return self;
}

#pragma mark - 約束布局
- (void) makeViewConstraints {
}

#pragma mark - Publich Methods

#pragma mark - Private Methods

#pragma mark - Property Set

#pragma mark - Property Get

4.對齊

  • 所有代碼段使用四個空格進行縮進
  • 名字太長的方法定義或調用,可采用冒號對齊的方式多行展示
  • 數組和字典字面量跨行書寫時“@”符號對齊:
/// 我是數組
NSArray *demoArray = @[@"Object-C",
                       @"Swift",
                       @"Python",
                       @(YES),
                       @(1)
                      ];
/// 我是字典
NSDictionary *demoDict = @{@"Object-C":@"Object-C",
                           @"Swift":@"Swift",
                           @"Python":@"Python",
                           @"BoolValue":@(YES),
                           @"Number":@(1)
                          };
其實,上面兩個例子中,中括號和大括號的始末位置也進行了對齊。
  • if語句中,左大括號“{”不跨行,并和右括號“)”之間留有一個空格; 右大括號“}”和對應的“if”保持左端對齊; else語句和左側“}”以及右側“{”在同一行,舉個例子:
/// 我是 if 語句
if (18 == self.age) {
    if (self.isGirl) {
        NSLog(@"Marry me!");
    } else {
        /// do something
    }
}

建議使用if語句時,直接選擇蘋果提供的快捷代碼段。

  • 所有枚舉值與最左側保持四個空格的縮進
  • 非線程安全的屬性以"@property (nonatomic, ...)"打頭的形式書寫,代碼更工整
  • block中嵌套block時,注意每個"}"和對應的代碼起點對齊

三、開發習慣

1.Switch語句每個case后添加“{}”的習慣

- (void)sampleForSwitch {
    SampleEnum testEnum = SampleEnumTwo;
    switch(testEnum) {
        caseSampleEnumUndefined: {
            // do something
            break;
        }
        caseSampleEnumOne: {
            // do something
            break;
        }
        caseSampleEnumTwo: {
            // do something
            break;
        }
        default: {
            NSLog(@"WARNING: there is an enum type not handled properly!");
            break;
        }
    }
}

2.先建立實體文件夾,后在工程中引入

3.條件語句

簡單條件判斷推薦使用三目運算符“? :”,如:

result = object ? : [self createObject];

注意這里“?”后寫成空格,會直接返回object的情況,不建議用下面這種形式:
result = object ? object : [self createObject];    


4.Bool賦值

簡單的條件判斷后賦布爾值的邏輯,可以省略if語句,如:

BOOL isAdult = age > 18;

而不是:
BOOL isAdult;
if (age > 18) {
    isAdult = YES;
}
else {
    isAdult = NO;
}

5.拒絕魔術數字和字符串

舉個例子:

/// 反例1 “Nissan”字符串突然橫空出現
if (carName == "Nissan")
/// 反例2 “18”是個什么意思~
if (age > 18) { ... }

推薦下面的方式:
/// 用枚舉類型代替字符串類型(由于是數字類型比較,編譯速度比字符串類型更為高效)
if (car == Car.Nissan)
/// 提前為魔術數字定義常量,同時方便添加注釋(即明確數字含義后再繼續寫代碼)
const int adultAge = 18; 
if (age > adultAge) { ... }

6.readonly屬性

不需要修改屬性的地方,心懷添加readonly的念想。

7.copy屬性

對于有可變類型子類的數據類型,使用copy屬性防止數據被更改,如NSString、NSArray、NSDictionary。

8.使用字面量方式初始化數據,增強可讀性

9.復雜判斷簡單化

if (job.JobState == JobState.New
    || job.JobState == JobState.Submitted
    || job.JobState == JobState.Expired
    || (job.JobTitle && job.JobTitle.length) {
    ....
}

可以被優化為:
/// 首先將上面整體提煉為一個方法
if ([self canDeleteJob:job]) { ... }        
/// 方法內對條件判斷進行拆分
- (BOOL)canDeleteJob:(Job *)job {
    BOOL invalidJobState = job.JobState == JobState.New
                          || job.JobState == JobState.Submitted
                          || job.JobState == JobState.Expired;
    BOOL invalidJob = job.JobTitle && job.JobTitle.length;

    return invalidJobState || invalidJob;
}

10.嵌套判斷平行化

BOOL isValid = NO;
if (user.UserName) {
    if (user.Password) {
        if (user.Email) {
            isValid = YES;
        }
    }
}
return isValid;

可以被優化為:

if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;

return YES;

11.回調方法加調用者的習慣

如經典的:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

添加調用者tableView作為參數,方便信息傳遞和區分調用者。

12.NSTimer、觀察者、通知、網絡請求等注意在Dealloc或viewWillDisappear:方法中移除。同一個文件中,移除的順序應和創建的順序保持一致,方便后期維護(注意:工程中使用RAC或已經封裝好的方法不需要作移除操作)。

13.訪問或操作NSArray、NSDictionary等對象時,注意判斷對象和對象內要訪問的元素是否為nil。

14.雖然很簡單,但盡量不用new方法而是統一采用Cocoa規范中的[[ClassName alloc] init]方法

15.Delegate使用weak屬性修飾

@property (nonatomic, weak) delegate;

16.提交代碼前保證無warning和error

17.隨時注釋別人或未來的自己有可能看不懂的代碼

18.邏輯捋清楚前不要寫代碼,因為一定會重寫。

四、設計思想

1.精簡

  • 方法不超過約一百行,否則就要考慮拆分
  • 清除:無用類、方法、資源、注釋、多余空行、Log語句、警告

2.分工明確

  • .h文件:核心屬性和方法聲明地帶 (.h文件由于不參與實現過程,用@class引用類,將#import "Demo.h"形式的引入統一寫在.m文件中)
  • .m文件:私有方法、私有變量聲明以及所有方法的實現基地
  • 分類:不常用、或與主業務無關的方法聚居地(不要濫用分類)
  • View層:UI搭建(MVC設計模式下可以進行簡單的數據展示)
  • Model層:展示最直白的數據結構
  • ViewController層:
    • 1.容器:容納子VC,構建View布局和展示
    • 2.控制Viewmodel
    • 3.響應UI事件(或信號)和代理方法
    • 4.不同生命周期邏輯處理
  • ViewModel層:用來且只用來處理和數據相關的一切
    • 1.數據初始化
    • 2.請求網絡數據
    • 3.數據業務處理:數據持久化、篩選、排序、驗證、增刪改查
    • 4.將處理后的數據在View上展示
    • 5.頭文件中返回最終有效readonly數據
  • 網絡層:分擔ViewModel層網絡請求職責

3.團隊一致性

上述所有,共同自律,提高整體效率。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,991評論 19 139
  • iOS編程規范0規范 0.1前言 為??高產品代碼質量,指導廣大軟件開發人員編寫出簡潔、可維護、可靠、可 測試、高效...
    iOS行者閱讀 4,502評論 21 35
  • 推薦文章:禪與 Objective-C 編程藝 前言 為??高產品代碼質量,指導廣大軟件開發人員編寫出簡潔、可維護、...
    WolfTin閱讀 2,822評論 0 1
  • 搜狐視頻iOS團隊 Objective-C 編碼規范 介紹 團隊中長期以來存在各人不同的編碼方式和習慣,導致代碼中...
    DiligentLeo閱讀 806評論 0 5
  • .Net 開發規范一、C# 編碼規范1. 代碼組織與風格1.1. Tab要使一個Tab為4個空格長。1.2. 縮進...
    PowerYangSoft閱讀 4,006評論 0 3