任何一個傻瓜都能寫出計算機可以理解的程式碼。唯有寫出人類容易理解的程式碼,才是優秀的程式設計師。 —— 佚名
本文是筆者結合公司程式碼規範要求,和之前看的《禪與 Objective-C 程式設計藝術》與《Effective Objective-C 2.0》書籍,以及參考相關部落格,總結出的一套iOS開發規範。有不足的地方,歡迎部落格下留言。
大括號
除了 .m 檔案中方法,其他的地方大括號"{"不需要另起一行。推薦:
if (!error) {
return success;
}
- (void)doHomework
{
if (self.hungry) {
return;
}
//doSomething
}
複製程式碼
運算子
1. 一元運算子與變數之間沒有空格:
!aValue
-aValue //負號
~aValue //位非
++iCount
*strSource
複製程式碼
2. 二元運算子與變數之間必須有空格
fWidth = 5 + 5;
fLength = fWidth * 2;
for(int i = 0; i < 10; i++)
複製程式碼
3.三元運算子
當三元運算子的第二個引數(if 分支)返回和條件語句中已經檢查的物件一樣的物件的時候,下面的表達方式更靈巧:
result = object ? : [self createObject];
複製程式碼
不推薦:
result = object ? object : [self createObject];
複製程式碼
if語句
1. 儘量列出所有的情況,且給出明確的結果。
推薦:
var hintStr;
if (count < 3) {
hintStr = "Good";
} else {
hintStr = "";
}
複製程式碼
2. 黃金大道
在使用條件語句程式設計時,程式碼的左邊距應該是一條“黃金”或者“快樂”的大道,也就是說善於使用return來提前返回不符合的情況。
推薦:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
// Do something important
}
複製程式碼
3. 複雜的表示式
條件表示式如果比較複雜,則需要將他們提取出來賦給一個BOOL變數。
推薦:
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2019;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
複製程式碼
4.尤達表示式
尤達表示式是指,拿一個常量去和變數比較而不是拿變數去和常量比較。
推薦:
if (count == 6) {
}
複製程式碼
if (myValue == nil) {
}
複製程式碼
if (!object ) {
}
複製程式碼
不推薦:
if ( 6 == count) {
}
複製程式碼
if ( nil == object ) {
}
複製程式碼
5. 條件語句體應該總是被大括號包圍
儘管有時候你可以不使用大括號(比如,條件語句體只有一行內容),但是這樣做會帶來問題隱患。
推薦:
if (!error) {
return success;
}
複製程式碼
不推薦:
if (!error)
return success;
複製程式碼
if (!error) return success;
複製程式碼
Switch語句
1. 每個分支都必須用大括號括起來
推薦:
switch (integer) {
case 1: {
// ...
break;
}
case 2: {
// ...
break;
}
case 3: {
// ...
break;
}
default:{
// ...
break;
}
}
複製程式碼
2.除了使用列舉型別以外,都必須有default分支
switch (menuType) {
case menuTypeLeft: {
// ...
break;
}
case menuTypeRight: {
// ...
break;
}
case menuTypeTop: {
// ...
break;
}
case menuTypeBottom: {
// ...
break;
}
}
複製程式碼
在Switch語句使用列舉型別的時候,如果使用了default分支,在將來就無法通過編譯器來檢查新增的列舉型別了。
函式
1. 一個函式的長度儘量限制在50行以內
如果一個方法裡面的程式碼行數過多,程式碼的閱讀體驗極差。
2. 一個函式只做一件事(單一原則)
每個函式的職責都應該劃分的很明確(就像類一樣)。
3. 對於有返回值的函式,確保每個分支都有返回值
推薦:
int function()
{
if(condition1){
return count1
}else if(condition2){
return count2
}else{
return defaultCount
}
}
複製程式碼
4. 外部傳入的引數需要檢驗引數的非空、資料型別的合法性,引數錯誤立即返回或斷言
推薦:
void function(param1,param2)
{
if(!param1){
return;
}
if(!param2){
return;
}
//Do some right thing
}
複製程式碼
5. 多個函式如果有邏輯重複的程式碼,建議將重複的部分抽取出來,成為獨立的函式進行呼叫
6. 如果方法引數過多過長,建議多行書寫,每個引數佔用一行,用冒號進行對齊 推薦:
- (void)initWithAge:(NSInteger)age
name:(NSString *)name
weight:(CGFloat)weight;
複製程式碼
7. 方法名中不應使用and,而且簽名要與對應的引數名保持一致
推薦:
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
複製程式碼
不推薦:
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
複製程式碼
註釋
1.類的註釋
對於類的註釋寫在當前類檔案的頂部。
2.屬性註釋
對於屬性的註釋建議寫在屬性上面,用的時候,會有提示功能。
/// 重新整理按鈕
@property (nonatomic, strong) UIButton *refreshBtn;
複製程式碼
3.方法註釋
對於.h檔案中方法的註釋,通過快捷鍵command+option+/
快速註釋;
對於.m檔案中方法的註釋,在方法的上邊新增//
,註釋符和註釋內容需要間隔一個空格。例如
// load network data
複製程式碼
4.功能註釋
版本迭代中,在同事寫的程式碼基礎上開發,一定要寫上版本功能註釋,方便詢問具體功能。推薦
// 這是一個新加的功能 v5.20.0 by minjing.lin
複製程式碼
變數
1. 變數名必須使用駝峰格式
類,協議使用大駝峰:
HomePageViewController.h
<HeaderViewDelegate>
複製程式碼
物件等區域性變數使用小駝峰:
NSString *personName = @"";
NSUInteger totalCount = 0;
複製程式碼
2.變數的名稱必須同時包含功能與型別
UIButton *addBtn
UILabel *nameLbl
NSString *addressStr
複製程式碼
3. 系統常用類作例項變數宣告時加入字尾
型別 | 字尾 |
---|---|
UIViewController | VC |
UIView | View |
UILabel | Lbl |
UIButton | Btn |
UIImage | Img |
UIImageView | ImagView |
NSArray | Arr |
NSMutableArray | Marr |
NSDictionary | Dict |
NSMutableDictionary | Mdict |
NSString | Str |
NSMutableString | Mstr |
NSSet | Set |
NSMutableSet | Mset |
常量
1. 常量以相關類名作為字首
推薦:
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
複製程式碼
不推薦:
static const NSTimeInterval fadeOutTime = 0.4;
複製程式碼
2. 建議使用型別常量,不建議使用#define預處理命令
首先比較一下這兩種宣告常量的區別:
- 預處理命令:簡單的文字替換,不包括型別資訊,並且可被任意修改。
- 型別常量:包括型別資訊,並且可以設定其使用範圍,而且不可被修改。
推薦:
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
複製程式碼
不推薦:
#define CompanyName @"Apple Inc."
#define magicNumber 42
複製程式碼
3. 對外公開某個常量
如果我們需要傳送通知,那麼就需要在不同的地方拿到通知的“頻道”字串(通知的名稱),那麼顯然這個字串是不能被輕易更改,而且可以在不同的地方獲取。這個時候就需要定義一個外界可見的字串常量。
推薦:
//.h
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
複製程式碼
//.m
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
複製程式碼
巨集
1. 字母全部大寫,單詞與單詞之間用_
分割
#define URL_GAIN_QUOTE_LIST @"/v1/quote/list"
#define URL_UPDATE_QUOTE_LIST @"/v1/quote/update"
複製程式碼
2. 巨集定義中如果包含表示式或變數,表示式和變數必須用小括號括起來
#define MY_MIN(A, B) ((A)>(B)?(B):(A))
複製程式碼
列舉
當使用 enum 的時候,建議使用新的固定的基礎型別定義,因為它有更強大的型別檢查和程式碼補全。
typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) {
UIControlContentVerticalAlignmentCenter = 0,
UIControlContentVerticalAlignmentTop = 1,
UIControlContentVerticalAlignmentBottom = 2,
UIControlContentVerticalAlignmentFill = 3,
};
複製程式碼
範型
建議在定義NSArray和NSDictionary時使用泛型,可以保證程式的安全性:
NSArray<NSString *> *testArr =@[@"hello",@"world"];
NSDictionary<NSString *, NSNumber *> *dic = @{@"key":@(1), @"age":@(10)};
複製程式碼
NSMutableArray
1. addObject之前要非空判斷。
2. 取下標的時候要判斷是否越界。
3. 取第一個元素或最後一個元素的時候使用firtstObject和lastObject
字面量語法
儘量使用字面量值來建立 NSString , NSDictionary , NSArray , NSNumber 這些不可變物件:
推薦:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
複製程式碼
不推薦:
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill" ];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];
複製程式碼
Block
為常用的Block型別建立typedef
如果我們需要重複建立某種block(相同引數,返回值)的變數,我們就可以通過typedef來給某一種塊定義屬於它自己的新型別。
例如:
int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value)
{
// Implementation
return someInt;
}
複製程式碼
這個Block有一個bool引數和一個int引數,並返回int型別。我們可以給它定義型別:
typedef int(^EOCSomeBlock)(BOOL flag, int value);
複製程式碼
再次定義的時候,就可以通過簡單的賦值來實現:
EOCSomeBlock block = ^(BOOL flag, int value){
// Implementation
};
複製程式碼
定義作為引數的Block:
- (void)startWithCompletionHandler: (void(^)(NSData *data, NSError *error))completion;
複製程式碼
這裡的Block有一個NSData引數,一個NSError引數並沒有返回值
typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);
- (void)startWithCompletionHandler:(EOCCompletionHandler)completion;”
複製程式碼
通過typedef定義Block簽名的好處是:如果要某種塊增加引數,那麼只修改定義簽名的那行程式碼即可。
屬性
1.書寫規則
@property、空格、括號、執行緒修飾詞、記憶體修飾詞、讀寫修飾詞、空格、類、物件名稱;
根據不同的場景選擇合適的修飾符。
推薦:
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, strong, readwrite) UIView *headerView;
@property (nonatomic, weak) id<#delegate#> delegate;
複製程式碼
2. Block屬性應該使用copy關鍵字
推薦:
typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);
@property (nonatomic, copy) ErrorCodeBlock errorBlock;
@property (nonatomic, copy) <#returnType#> (^<#Block#>)(<#parType#>);
複製程式碼
3. 形容詞性的BOOL屬性的getter應該加上is字首
推薦:
@property (nonatomic, assign, getter=isEditable) BOOL editable;
複製程式碼
4. 對外儘量使用不可變物件
儘量把對外公佈出來的屬性設定為只讀,在實現檔案內部設為讀寫。具體做法是:
- 在標頭檔案中,設定物件屬性為
readonly
。 - 在實現檔案中設定為
readwrite
。
這樣一來,在外部就只能讀取該資料,而不能修改它,使得這個類的例項所持有的資料更加安全。而且,對於集合類的物件,更應該仔細考慮是否可以將其設為可變的。
如果在公開部分只能設定其為只讀屬性,那麼就在非公開部分儲存一個可變型。所以當在外部獲取這個屬性時,獲取的只是內部可變型的一個不可變版本,例如:
在公共API中:
@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSSet *friends //向外公開的不可變集合
- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName;
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
@end
複製程式碼
在這裡,我們將friends屬性設定為不可變的set。然後,提供了來增加和刪除這個set裡的元素的公共介面。
在實現檔案裡:
@interface EOCPerson ()
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
@end
@implementation EOCPerson {
NSMutableSet *_internalFriends; //實現檔案裡的可變集合
}
- (NSSet*)friends
{
return [_internalFriends copy]; //get方法返回的永遠是可變set的不可變型
}
- (void)addFriend:(EOCPerson*)person
{
[_internalFriends addObject:person]; //在外部增加集合元素的操作
//do something when add element
}
- (void)removeFriend:(EOCPerson*)person
{
[_internalFriends removeObject:person]; //在外部移除元素的操作
//do something when remove element
}
- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName
{
if ((self = [super init])) {
_firstName = firstName;
_lastName = lastName;
_internalFriends = [NSMutableSet new];
}
return self;
}
複製程式碼
我們可以看到,在實現檔案裡,儲存一個可變set來記錄外部的增刪操作。
這裡最重要的程式碼是:
- (NSSet*)friends
{
return [_internalFriends copy];
}
複製程式碼
這個是friends屬性的獲取方法:它將當前儲存的可變set複製了一不可變的set並返回。因此,外部讀取到的set都將是不可變的版本。
代理方法
1. 代理方法的第一個引數必須為委託者
代理方法必須以委託者作為第一個引數(參考UITableViewDelegate)的方法。其目的是為了區分不同委託著的例項。因為同一個控制器是可以作為多個tableview的代理的。例如:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
複製程式碼
2.向代理髮送訊息時需要判斷其是否實現該方法
推薦:
if ([self.delegate respondsToSelector:@selector(signUpViewControllerDidPressSignUpButton:)]) {
[self.delegate signUpViewControllerDidPressSignUpButton:self];
}
複製程式碼
3. 遵循代理過多的時候,換行對齊顯示 推薦:
@interface ShopViewController () <UIGestureRecognizerDelegate,
HXSClickEventDelegate,
UITableViewDelegate,
UITableViewDataSource>
複製程式碼
4. 代理的方法需要明確必須執行和可不執行
- @required:必須實現的方法
- @optional:可選是否實現的方法
@protocol ZOCServiceDelegate <NSObject>
@optional
- (void)generalService:(ZOCGeneralService *)service didRetrieveEntries:(NSArray *)entries;
@end
複製程式碼
類
1. 類的名稱
應該以三個大寫字母為字首;建立子類的時候,應該把代表子類特點的部分放在字首和父類名的中間。
推薦:
//父類
ZOCSalesListViewController
//子類
ZOCDaySalesListViewController
ZOCMonthSalesListViewController
複製程式碼
2. 所有返回類物件和例項物件的方法都應該使用instancetype
將instancetype關鍵字作為返回值的時候,可以讓編譯器進行型別檢查,同時適用於子類的檢查,這樣就保證了返回型別的正確性(一定為當前的類物件或例項物件)
推薦:
- (instancetype)init
{
self = [super init]; // call the designated initializer
if (self) {
// Custom initialization
}
return self;
}
複製程式碼
@interface ZOCPerson
+ (instancetype)personWithName:(NSString *)name;
@end
複製程式碼
不推薦:
@interface ZOCPerson
+ (id)personWithName:(NSString *)name;
@end
複製程式碼
3. 在類的.h
檔案中儘量少引用其他標頭檔案
有時,類A需要將類B的例項變數作為它公共API的屬性。這個時候,我們不應該引入類B的標頭檔案,而應該使用向前宣告(forward declaring)使用class關鍵字,並且在A的實現檔案引用B的標頭檔案。
// EOCPerson.h
#import <Foundation/Foundation.h>
@class EOCEmployer;
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;//將EOCEmployer作為屬性
@end
// EOCPerson.m
#import "EOCEmployer.h"
複製程式碼
優點:
-
不在A的標頭檔案中引入B的標頭檔案,就不會一併引入B的全部內容,這樣就減少了編譯時間。
-
可以避免迴圈引用:因為如果兩個類在自己的標頭檔案中都引入了對方的標頭檔案,那麼就會導致其中一個類無法被正確編譯。
但是個別的時候,必須在標頭檔案中引入其他類的標頭檔案:
- 該類繼承於某個類,則應該引入父類的標頭檔案。
- 該類遵從某個協議,則應該引入該協議的標頭檔案。而且最好將協議單獨放在一個標頭檔案中。
4. 類的佈局
#pragma mark - Life Cycle Methods
- (instancetype)init
- (void)dealloc
- (void)viewWillAppear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
- (void)viewDidDisappear:(BOOL)animated
#pragma mark - Override Methods
#pragma mark - Network Methods
#pragma mark - Target Methods
#pragma mark - Public Methods
#pragma mark - Private Methods
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - Setters and Getters
複製程式碼
可以使用程式碼塊一鍵生成,參考Xcode 快速開發 程式碼塊
相等性的判斷
判斷兩個person類是否相等的合理做法:
- (BOOL)isEqual:(id)object
{
if (self == object) {
return YES; //判斷記憶體地址
}
if (![object isKindOfClass:[ZOCPerson class]]) {
return NO; //是否為當前類或派生類
}
return [self isEqualToPerson:(ZOCPerson *)object];
}
//自定義的判斷相等性的方法
- (BOOL)isEqualToPerson:(Person *)person
{
if (!person) {
return NO;
}
BOOL namesMatch = (!self.name && !person.name) || [self.name isEqualToString:person.name];
BOOL birthdaysMatch = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday];
return haveEqualNames && haveEqualBirthdays;
}
複製程式碼
圖片命名
1.命名規範:不能有中文、大寫、特殊符號、空白
2.命名格式(推薦):
fileType
[function]project
[pageName]imageName
[status]{.png,@2x.png,@3x.png}
萬能公式:類別_功能_模組_頁面_名稱_狀態.png
icon_tab_bookshelf_sel@2x.png
複製程式碼
面試題(風格糾錯)
typedef enum{
UserSex_Man,
UserSex_Woman
}UserSex;
@interface UserModel :NSObject
@property(nonatomic, strong) NSString *name;
@property (assign,nonatomic) int age;
@property (nonatomic,assign) UserSex sex;
-(id)initUserModelWithUserName: (NSString*)name withAge(int age);
-(void)doLogIn;
@end
複製程式碼
參考文獻:
禪與 Objective-C 程式設計藝術
iOS 程式碼規範
看完這個你們團隊的程式碼也很規範
《Effective Objective-C 2.0》