一、命名
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
數(shù)據(jù)類型: 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.局部變量可不遵守變量名字后面加數(shù)據(jù)類型這一規(guī)則。 2.Bool類型以is、has、can等作為前綴,或以ing、ed等作后綴。
其他:
- name + Model
- name + ViewModel
- name + Request(網(wǎng)絡(luò)請求)
- name + Block
注意:盡量為每個變量取有意義的名字,即“name”,但若確實(shí)沒有name的情況下,直接采用如下形式,如:
UITableView *tableView;
2.實(shí)例變量
實(shí)例變量采用小駝峰命名法的基礎(chǔ)上,以下劃線“_”作為前綴,如:
UIButton *_loginButton;
3.常量 和 宏常量
普通常量:以小寫字母“k”開頭的駝峰命名法,如:static NSString *const kMovieCellHeight;
通知常量:一般形式為[ 觸發(fā)通知的類名] + [Did 或 Will] + [ 動作 ] + Notification ;
宏常量:全部大寫,中間用下劃線“_”作間隔,如:#define TARGET_OS_IOS。
注意:
- 如果宏常量和APP業(yè)務(wù)相關(guān)則添加相應(yīng)業(yè)務(wù)類名作前綴,如果適用于整個APP則直接使用APP前綴,如:
#define MOHomeCellHeaderHeight (18.0) - 盡量不用宏來定義常量,而是采用枚舉和const定義的常量
4.枚舉類型名稱 和 枚舉變量名稱
定義枚舉類型:采用OC風(fēng)格定義枚舉類型,舉個栗子:
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone, //默認(rèn)從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.枚舉類型名稱采用首字母大寫的駝峰命名法,并且添加相關(guān)類作為前綴
(本例中相關(guān)的類為UIView)
2.枚舉值命名要添加枚舉類型名作前綴
(本例中枚舉值前綴分別為UIViewAnimationTransition和UIViewAutoresizing)
枚舉變量名稱和類型名稱基本保持一致的情況下,盡量增強(qiáng)其可閱讀性,采用小駝峰命名法,如:
UIViewAnimationTransition animationTransitionType;
5.方法名
采用小駝峰命名法,做到見其名知其含義,本身具有充分的解釋性,拒絕無意義命名。
例外情況:
- 可以用一些通用的大寫字母縮寫打頭的方法,比如 PDF,TIFF
- 可以用帶下劃線的前綴來命名私有方法或者類別中的方法
6.類名 和 協(xié)議名
類名:“統(tǒng)一的類名前綴” + “功能模塊簡稱”(可省略) + “描述該類的詞或詞組” + 控件類型名尾綴(如Cell,ViewController,View等,可省略)
協(xié)議名:類名 + “Delegate”
都采用大駝峰命名法。
7.分類的名稱 和 分類中方法名
分類的名稱和類名的命名方式基本一致,只是前綴由開發(fā)者指定。
但分類中的方法名,需使用分類的前綴小寫形式 + “_”的形式,如:
@interface NSDate (MOLocalDate)
/// 獲取根據(jù)時間間隔按照系統(tǒng)時間計(jì)算的日期,seconds:單位(秒)
+ (NSDate *)mo_dateWithTimeIntervalSince1970:(NSTimeInterval)seconds;
8.Assets.xcassets文件命名
1.功能模塊使用的資源,使用功能模塊代碼資源同樣的文件夾結(jié)構(gòu)(一級或多級)和名稱。
2.公共或通用資源文件夾取名為“Common”或“Normal”
3.圖片資源命名方式采用全部小寫和分割線分隔的形式,前綴為功能模塊名稱或縮寫,如:
common_navigation_bar_back
9.功能模塊內(nèi)文件夾命名
一般情況下,各功能模塊下的文件夾命名包括但不限于以下命名:
- View 或 CustomView
- Model
- ViewController
- Cell
- ViewModel (MVVM模式下使用)
- Helper
- ……
10.Xib或Storyboard中命名
- Xib文件名和對應(yīng)類名保持一致
- Xib或者Storyboard中的控件,命名采用全部大寫且用一個空格分隔的形式。添加控件時立即添加有意義的命名,方便查看不同控件的約束關(guān)系以及后期維護(hù)。
總結(jié):命名盡量不要用單詞簡寫,注重保持語義明確;名稱保持一致性,避免同樣語義出現(xiàn)多種單詞如“number”和“count”;命名風(fēng)格統(tǒng)一,遵守統(tǒng)一的的命名規(guī)則。
二、風(fēng)格
1.關(guān)于空格與空行
- 數(shù)組或字典字面量用逗號隔開的形式書寫時,逗號后留有一個空格
-
if或switch語句中,這兩個關(guān)鍵字和緊跟的括號之間留有一個空格 - 二元符號如"
="、"=="、">"、"<"、"||"、"&&"等兩側(cè)留有一個空格 - 三元符號中"
?"和":"兩側(cè)留有一個空格 - 方法名“+”和“-”號和方法名中間留有一個空格;方法實(shí)現(xiàn)中“{”和方法名之間留有一個空格。
- 定義屬性時,逗號后留有一個空格,如:
@property (nonatomic, copy, readonly) NSString *titleString;
小括號左右兩邊各用一個空格與外部隔開;
星號“*”左邊留有一個空格。
- 聲明類時
@interface MOCalendarService : NSObject
冒號左右各用一個空格隔開
- 聲明分類或延展(類目)時
/// 分類
@interface NSDate (MOLocalDate)
/// 延展(類目)
@interface ClassName () <UICollectionViewDelegate,
UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
- 小括號左右兩邊各用一個空格與外部隔開
- 協(xié)議列表中,逗號后留有一個空格
- 方法與方法之間空一行,方法內(nèi)的代碼塊與代碼塊之間都空一行
-
#prgma mark - Demo分隔語句上方留有一空行,下方不留空行或留有一空行。
2.注釋
- 在需要注釋的方法或?qū)傩陨戏绞褂?code>Command + Alt + /”快捷鍵,或者“
/// + 一個空格 + 說明”的形式進(jìn)行注釋,方便別處使用“Alt + 鼠標(biāo)點(diǎn)按”直接查詢該方法或?qū)傩缘亩x。
(注意:在屬性右側(cè)使用“/// + 一個空格 + 說明”的形式進(jìn)行注釋,在別處無法用“Alt + 鼠標(biāo)點(diǎn)按”快捷鍵獲取注釋詳情) - 針對方法內(nèi)某一行代碼語句進(jìn)行注釋,可以在此行代碼上方或右側(cè)進(jìn)行“
// + 一個空格 + 說明”的形式進(jìn)行注釋,注意將該代碼段和其它代碼段用注釋行或空白行進(jìn)行分隔。 - 針對方法內(nèi)某幾行代碼段進(jìn)行注釋,也注意將該代碼段和其它代碼段用注釋行或空白行進(jìn)行分隔。
總結(jié):
- 注釋方法和屬性盡量方便代碼使用時用快捷鍵獲取注釋內(nèi)容
- 注釋內(nèi)容盡量讓不懂代碼的人能看懂在做的事情
- 注釋代碼行或代碼段要明確區(qū)分注釋區(qū)域
- 典型的需要注釋的地方包括:頭文件中類名、屬性和方法注釋,
if或switch語句注釋,魔術(shù)數(shù)字或字符串注釋 - 注釋不要和Mark混淆使用。
3.Mark分區(qū)
建議開發(fā)時使用Xcode右下角的代碼快捷(Code Snippet Library)方式保存和提取常用文件Mark分區(qū)結(jié)構(gòu)。
針對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.對齊
- 所有代碼段使用四個空格進(jìn)行縮進(jìn)
- 名字太長的方法定義或調(diào)用,可采用冒號對齊的方式多行展示
- 數(shù)組和字典字面量跨行書寫時“@”符號對齊:
/// 我是數(shù)組
NSArray *demoArray = @[@"Object-C",
@"Swift",
@"Python",
@(YES),
@(1)
];
/// 我是字典
NSDictionary *demoDict = @{@"Object-C":@"Object-C",
@"Swift":@"Swift",
@"Python":@"Python",
@"BoolValue":@(YES),
@"Number":@(1)
};
其實(shí),上面兩個例子中,中括號和大括號的始末位置也進(jìn)行了對齊。
-
if語句中,左大括號“{”不跨行,并和右括號“)”之間留有一個空格; 右大括號“}”和對應(yīng)的“if”保持左端對齊; else語句和左側(cè)“}”以及右側(cè)“{”在同一行,舉個例子:
/// 我是 if 語句
if (18 == self.age) {
if (self.isGirl) {
NSLog(@"Marry me!");
} else {
/// do something
}
}
建議使用if語句時,直接選擇蘋果提供的快捷代碼段。
- 所有枚舉值與最左側(cè)保持四個空格的縮進(jìn)
- 非線程安全的屬性以"
@property (nonatomic, ...)"打頭的形式書寫,代碼更工整 - block中嵌套block時,注意每個"}"和對應(yīng)的代碼起點(diǎn)對齊
三、開發(fā)習(xí)慣
1.Switch語句每個case后添加“{}”的習(xí)慣
- (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.先建立實(shí)體文件夾,后在工程中引入
3.條件語句
簡單條件判斷推薦使用三目運(yùn)算符“? :”,如:
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.拒絕魔術(shù)數(shù)字和字符串
舉個例子:
/// 反例1 “Nissan”字符串突然橫空出現(xiàn)
if (carName == "Nissan")
/// 反例2 “18”是個什么意思~
if (age > 18) { ... }
推薦下面的方式:
/// 用枚舉類型代替字符串類型(由于是數(shù)字類型比較,編譯速度比字符串類型更為高效)
if (car == Car.Nissan)
/// 提前為魔術(shù)數(shù)字定義常量,同時方便添加注釋(即明確數(shù)字含義后再繼續(xù)寫代碼)
const int adultAge = 18;
if (age > adultAge) { ... }
6.readonly屬性
不需要修改屬性的地方,心懷添加readonly的念想。
7.copy屬性
對于有可變類型子類的數(shù)據(jù)類型,使用copy屬性防止數(shù)據(jù)被更改,如NSString、NSArray、NSDictionary。
8.使用字面量方式初始化數(shù)據(jù),增強(qiáng)可讀性
9.復(fù)雜判斷簡單化
if (job.JobState == JobState.New
|| job.JobState == JobState.Submitted
|| job.JobState == JobState.Expired
|| (job.JobTitle && job.JobTitle.length) {
....
}
可以被優(yōu)化為:
/// 首先將上面整體提煉為一個方法
if ([self canDeleteJob:job]) { ... }
/// 方法內(nèi)對條件判斷進(jìn)行拆分
- (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;
可以被優(yōu)化為:
if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;
return YES;
11.回調(diào)方法加調(diào)用者的習(xí)慣
如經(jīng)典的:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
添加調(diào)用者tableView作為參數(shù),方便信息傳遞和區(qū)分調(diào)用者。
12.NSTimer、觀察者、通知、網(wǎng)絡(luò)請求等注意在Dealloc或viewWillDisappear:方法中移除。同一個文件中,移除的順序應(yīng)和創(chuàng)建的順序保持一致,方便后期維護(hù)(注意:工程中使用RAC或已經(jīng)封裝好的方法不需要作移除操作)。
13.訪問或操作NSArray、NSDictionary等對象時,注意判斷對象和對象內(nèi)要訪問的元素是否為nil。
14.雖然很簡單,但盡量不用new方法而是統(tǒng)一采用Cocoa規(guī)范中的[[ClassName alloc] init]方法
15.Delegate使用weak屬性修飾
@property (nonatomic, weak) delegate;
16.提交代碼前保證無warning和error
17.隨時注釋別人或未來的自己有可能看不懂的代碼
18.邏輯捋清楚前不要寫代碼,因?yàn)橐欢〞貙憽?/h2>
四、設(shè)計(jì)思想
1.精簡
- 方法不超過約一百行,否則就要考慮拆分
- 清除:無用類、方法、資源、注釋、多余空行、Log語句、警告
2.分工明確
- .h文件:核心屬性和方法聲明地帶 (
.h文件由于不參與實(shí)現(xiàn)過程,用@class引用類,將#import "Demo.h"形式的引入統(tǒng)一寫在.m文件中)
- .m文件:私有方法、私有變量聲明以及所有方法的實(shí)現(xiàn)基地
- 分類:不常用、或與主業(yè)務(wù)無關(guān)的方法聚居地(不要濫用分類)
- View層:UI搭建(MVC設(shè)計(jì)模式下可以進(jìn)行簡單的數(shù)據(jù)展示)
- Model層:展示最直白的數(shù)據(jù)結(jié)構(gòu)
- ViewController層:
- 1.容器:容納子VC,構(gòu)建View布局和展示
- 2.控制Viewmodel
- 3.響應(yīng)UI事件(或信號)和代理方法
- 4.不同生命周期邏輯處理
- ViewModel層:用來且只用來處理和數(shù)據(jù)相關(guān)的一切
- 1.數(shù)據(jù)初始化
- 2.請求網(wǎng)絡(luò)數(shù)據(jù)
- 3.數(shù)據(jù)業(yè)務(wù)處理:數(shù)據(jù)持久化、篩選、排序、驗(yàn)證、增刪改查
- 4.將處理后的數(shù)據(jù)在View上展示
- 5.頭文件中返回最終有效readonly數(shù)據(jù)
- 網(wǎng)絡(luò)層:分擔(dān)ViewModel層網(wǎng)絡(luò)請求職責(zé)
3.團(tuán)隊(duì)一致性
上述所有,共同自律,提高整體效率。
.h文件由于不參與實(shí)現(xiàn)過程,用@class引用類,將#import "Demo.h"形式的引入統(tǒng)一寫在.m文件中)- 1.容器:容納子VC,構(gòu)建View布局和展示
- 2.控制Viewmodel
- 3.響應(yīng)UI事件(或信號)和代理方法
- 4.不同生命周期邏輯處理
- 1.數(shù)據(jù)初始化
- 2.請求網(wǎng)絡(luò)數(shù)據(jù)
- 3.數(shù)據(jù)業(yè)務(wù)處理:數(shù)據(jù)持久化、篩選、排序、驗(yàn)證、增刪改查
- 4.將處理后的數(shù)據(jù)在View上展示
- 5.頭文件中返回最終有效readonly數(shù)據(jù)