一、命名
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.關于空格與空行
- 數組或字典字面量用逗號隔開的形式書寫時,逗號后留有一個空格
-
if
或switch
語句中,這兩個關鍵字和緊跟的括號之間留有一個空格 - 二元符號如"
=
"、"==
"、">
"、"<
"、"||
"、"&&
"等兩側留有一個空格 - 三元符號中"
?
"和":
"兩側留有一個空格 - 方法名“+”和“-”號和方法名中間留有一個空格;方法實現中“{”和方法名之間留有一個空格。
- 定義屬性時,逗號后留有一個空格,如:
@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 + 鼠標點按
”快捷鍵獲取注釋詳情) - 針對方法內某一行代碼語句進行注釋,可以在此行代碼上方或右側進行“
// + 一個空格 + 說明
”的形式進行注釋,注意將該代碼段和其它代碼段用注釋行或空白行進行分隔。 - 針對方法內某幾行代碼段進行注釋,也注意將該代碼段和其它代碼段用注釋行或空白行進行分隔。
總結:
- 注釋方法和屬性盡量方便代碼使用時用快捷鍵獲取注釋內容
- 注釋內容盡量讓不懂代碼的人能看懂在做的事情
- 注釋代碼行或代碼段要明確區分注釋區域
- 典型的需要注釋的地方包括:頭文件中類名、屬性和方法注釋,
if
或switch
語句注釋,魔術數字或字符串注釋 - 注釋不要和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層網絡請求職責