任何一個傻瓜都能寫出計算機能夠理解的代碼。惟有寫出人類容易理解的代碼,纔是優秀的程序員。 —— 佚名git
本文是筆者結合公司代碼規範要求,和以前看的《禪與 Objective-C 編程藝術》與《Effective Objective-C 2.0》書籍,以及參考相關博客,總結出的一套iOS開發規範。有不足的地方,歡迎博客下留言。程序員
除了 .m 文件中方法,其餘的地方大括號"{"不須要另起一行。推薦:github
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];
複製代碼
不推薦:bash
result = object ? object : [self createObject];
複製代碼
1. 儘可能列出全部的狀況,且給出明確的結果。session
推薦:app
var hintStr;
if (count < 3) {
hintStr = "Good";
} else {
hintStr = "";
}
複製代碼
2. 黃金大道
在使用條件語句編程時,代碼的左邊距應該是一條「黃金」或者「快樂」的大道,也就是說善於使用return來提早返回不符合的狀況。ide
推薦:
- (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;
複製代碼
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)};
複製代碼
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類型建立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. 代理的方法須要明確必須執行和可不執行
@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》