任何一個傻瓜都能寫出計算機(jī)可以理解的代碼。唯有寫出人類容易理解的代碼氛魁,才是優(yōu)秀的程序員暮顺。 —— 佚名
本文是筆者結(jié)合公司代碼規(guī)范要求,和之前看的《禪與 Objective-C 編程藝術(shù)》與《Effective Objective-C 2.0》書籍秀存,以及參考相關(guān)博客捶码,總結(jié)出的一套iOS開發(fā)規(guī)范。有不足的地方或链,歡迎博客下留言惫恼。
大括號
除了 .m 文件中方法,其他的地方大括號"{"不需要另起一行澳盐。推薦:
if (!error) {
return success;
}
- (void)doHomework
{
if (self.hungry) {
return;
}
//doSomething
}
運(yùn)算符
1. 一元運(yùn)算符與變量之間沒有空格:
!aValue
-aValue //負(fù)號
~aValue //位非
++iCount
*strSource
2. 二元運(yùn)算符與變量之間必須有空格
fWidth = 5 + 5;
fLength = fWidth * 2;
for(int i = 0; i < 10; i++)
3.三元運(yùn)算符
當(dāng)三元運(yùn)算符的第二個參數(shù)(if 分支)返回和條件語句中已經(jīng)檢查的對象一樣的對象的時候尤筐,下面的表達(dá)方式更靈巧:
result = object ? : [self createObject];
不推薦:
result = object ? object : [self createObject];
if語句
1. 盡量列出所有的情況,且給出明確的結(jié)果洞就。
推薦:
var hintStr;
if (count < 3) {
hintStr = "Good";
} else {
hintStr = "";
}
2. 黃金大道
在使用條件語句編程時盆繁,代碼的左邊距應(yīng)該是一條“黃金”或者“快樂”的大道,也就是說善于使用return來提前返回不符合的情況旬蟋。
推薦:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
// Do something important
}
3. 復(fù)雜的表達(dá)式
條件表達(dá)式如果比較復(fù)雜油昂,則需要將他們提取出來賦給一個BOOL變量。
推薦:
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2019;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
4.尤達(dá)表達(dá)式
尤達(dá)表達(dá)式是指倾贰,拿一個常量去和變量比較而不是拿變量去和常量比較夕膀。
推薦:
if (count == 6) {
}
if (myValue == nil) {
}
if (!object ) {
}
不推薦:
if ( 6 == count) {
}
if ( nil == object ) {
}
5. 條件語句體應(yīng)該總是被大括號包圍
盡管有時候你可以不使用大括號(比如烛芬,條件語句體只有一行內(nèi)容),但是這樣做會帶來問題隱患。
推薦:
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分支耙替,在將來就無法通過編譯器來檢查新增的枚舉類型了。
函數(shù)
1. 一個函數(shù)的長度盡量限制在50行以內(nèi)
如果一個方法里面的代碼行數(shù)過多言秸,代碼的閱讀體驗(yàn)極差。
2. 一個函數(shù)只做一件事(單一原則)
每個函數(shù)的職責(zé)都應(yīng)該劃分的很明確(就像類一樣)迎捺。
3. 對于有返回值的函數(shù)举畸,確保每個分支都有返回值
推薦:
int function()
{
if(condition1){
return count1
}else if(condition2){
return count2
}else{
return defaultCount
}
}
4. 外部傳入的參數(shù)需要檢驗(yàn)參數(shù)的非空、數(shù)據(jù)類型的合法性凳枝,參數(shù)錯誤立即返回或斷言
推薦:
void function(param1,param2)
{
if(!param1){
return;
}
if(!param2){
return;
}
//Do some right thing
}
5. 多個函數(shù)如果有邏輯重復(fù)的代碼抄沮,建議將重復(fù)的部分抽取出來,成為獨(dú)立的函數(shù)進(jìn)行調(diào)用
6. 如果方法參數(shù)過多過長岖瑰,建議多行書寫,每個參數(shù)占用一行叛买,用冒號進(jìn)行對齊
推薦:
- (void)initWithAge:(NSInteger)age
name:(NSString *)name
weight:(CGFloat)weight;
7. 方法名中不應(yīng)使用and,而且簽名要與對應(yīng)的參數(shù)名保持一致
推薦:
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
不推薦:
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
注釋
1.類的注釋
對于類的注釋寫在當(dāng)前類文件的頂部蹋订。
2.屬性注釋
對于屬性的注釋建議寫在屬性上面率挣,用的時候,會有提示功能辅辩。
/// 刷新按鈕
@property (nonatomic, strong) UIButton *refreshBtn;
3.方法注釋
對于.h文件中方法的注釋难礼,通過快捷鍵command+option+/
快速注釋;
對于.m文件中方法的注釋玫锋,在方法的上邊添加//
蛾茉,注釋符和注釋內(nèi)容需要間隔一個空格。例如
// load network data
4.功能注釋
版本迭代中撩鹿,在同事寫的代碼基礎(chǔ)上開發(fā)谦炬,一定要寫上版本功能注釋,方便詢問具體功能节沦。推薦
// 這是一個新加的功能 v5.20.0 by minjing.lin
變量
1. 變量名必須使用駝峰格式
類键思,協(xié)議使用大駝峰:
HomePageViewController.h
<HeaderViewDelegate>
對象等局部變量使用小駝峰:
NSString *personName = @"";
NSUInteger totalCount = 0;
2.變量的名稱必須同時包含功能與類型
UIButton *addBtn
UILabel *nameLbl
NSString *addressStr
3. 系統(tǒng)常用類作實(shí)例變量聲明時加入后綴
類型 | 后綴 |
---|---|
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. 常量以相關(guān)類名作為前綴
推薦:
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
不推薦:
static const NSTimeInterval fadeOutTime = 0.4;
2. 建議使用類型常量,不建議使用#define預(yù)處理命令
首先比較一下這兩種聲明常量的區(qū)別:
- 預(yù)處理命令:簡單的文本替換甫贯,不包括類型信息吼鳞,并且可被任意修改。
- 類型常量:包括類型信息叫搁,并且可以設(shè)置其使用范圍赔桌,而且不可被修改。
推薦:
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
不推薦:
#define CompanyName @"Apple Inc."
#define magicNumber 42
3. 對外公開某個常量
如果我們需要發(fā)送通知渴逻,那么就需要在不同的地方拿到通知的“頻道”字符串(通知的名稱)疾党,那么顯然這個字符串是不能被輕易更改,而且可以在不同的地方獲取惨奕。這個時候就需要定義一個外界可見的字符串常量雪位。
推薦:
//.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. 宏定義中如果包含表達(dá)式或變量梨撞,表達(dá)式和變量必須用小括號括起來
#define MY_MIN(A, B) ((A)>(B)?(B):(A))
枚舉
當(dāng)使用 enum 的時候雹洗,建議使用新的固定的基礎(chǔ)類型定義香罐,因?yàn)樗懈鼜?qiáng)大的類型檢查和代碼補(bǔ)全。
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. 取下標(biāo)的時候要判斷是否越界幽勒。
3. 取第一個元素或最后一個元素的時候使用firtstObject和lastObject
字面量語法
盡量使用字面量值來創(chuàng)建 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類型創(chuàng)建typedef
如果我們需要重復(fù)創(chuàng)建某種block(相同參數(shù)嗜侮,返回值)的變量,我們就可以通過typedef來給某一種塊定義屬于它自己的新類型啥容。
例如:
int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value)
{
// Implementation
return someInt;
}
這個Block有一個bool參數(shù)和一個int參數(shù)锈颗,并返回int類型。我們可以給它定義類型:
typedef int(^EOCSomeBlock)(BOOL flag, int value);
再次定義的時候咪惠,就可以通過簡單的賦值來實(shí)現(xiàn):
EOCSomeBlock block = ^(BOOL flag, int value){
// Implementation
};
定義作為參數(shù)的Block:
- (void)startWithCompletionHandler: (void(^)(NSData *data, NSError *error))completion;
這里的Block有一個NSData參數(shù)击吱,一個NSError參數(shù)并沒有返回值
typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);
- (void)startWithCompletionHandler:(EOCCompletionHandler)completion;”
通過typedef定義Block簽名的好處是:如果要某種塊增加參數(shù),那么只修改定義簽名的那行代碼即可遥昧。
屬性
1.書寫規(guī)則
@property覆醇、空格、括號炭臭、線程修飾詞永脓、內(nèi)存修飾詞、讀寫修飾詞鞋仍、空格常摧、類、對象名稱威创;
根據(jù)不同的場景選擇合適的修飾符落午。
推薦:
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, strong, readwrite) UIView *headerView;
@property (nonatomic, weak) id<#delegate#> delegate;
2. Block屬性應(yīng)該使用copy關(guān)鍵字
推薦:
typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);
@property (nonatomic, copy) ErrorCodeBlock errorBlock;
@property (nonatomic, copy) <#returnType#> (^<#Block#>)(<#parType#>);
3. 形容詞性的BOOL屬性的getter應(yīng)該加上is前綴
推薦:
@property (nonatomic, assign, getter=isEditable) BOOL editable;
4. 對外盡量使用不可變對象
盡量把對外公布出來的屬性設(shè)置為只讀,在實(shí)現(xiàn)文件內(nèi)部設(shè)為讀寫肚豺。具體做法是:
- 在頭文件中溃斋,設(shè)置對象屬性為
readonly
。 - 在實(shí)現(xiàn)文件中設(shè)置為
readwrite
吸申。
這樣一來梗劫,在外部就只能讀取該數(shù)據(jù),而不能修改它呛谜,使得這個類的實(shí)例所持有的數(shù)據(jù)更加安全在跳。而且,對于集合類的對象隐岛,更應(yīng)該仔細(xì)考慮是否可以將其設(shè)為可變的猫妙。
如果在公開部分只能設(shè)置其為只讀屬性,那么就在非公開部分存儲一個可變型聚凹。所以當(dāng)在外部獲取這個屬性時割坠,獲取的只是內(nèi)部可變型的一個不可變版本,例如:
在公共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屬性設(shè)置為不可變的set。然后彼哼,提供了來增加和刪除這個set里的元素的公共接口对妄。
在實(shí)現(xiàn)文件里:
@interface EOCPerson ()
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
@end
@implementation EOCPerson {
NSMutableSet *_internalFriends; //實(shí)現(xiàn)文件里的可變集合
}
- (NSSet*)friends
{
return [_internalFriends copy]; //get方法返回的永遠(yuǎn)是可變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;
}
我們可以看到,在實(shí)現(xiàn)文件里敢朱,保存一個可變set來記錄外部的增刪操作剪菱。
這里最重要的代碼是:
- (NSSet*)friends
{
return [_internalFriends copy];
}
這個是friends屬性的獲取方法:它將當(dāng)前保存的可變set復(fù)制了一不可變的set并返回。因此拴签,外部讀取到的set都將是不可變的版本孝常。
代理方法
1. 代理方法的第一個參數(shù)必須為委托者
代理方法必須以委托者作為第一個參數(shù)(參考UITableViewDelegate)的方法。其目的是為了區(qū)分不同委托著的實(shí)例蚓哩。因?yàn)橥粋€控制器是可以作為多個tableview的代理的构灸。例如:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
2.向代理發(fā)送消息時需要判斷其是否實(shí)現(xiàn)該方法
推薦:
if ([self.delegate respondsToSelector:@selector(signUpViewControllerDidPressSignUpButton:)]) {
[self.delegate signUpViewControllerDidPressSignUpButton:self];
}
3. 遵循代理過多的時候,換行對齊顯示
推薦:
@interface ShopViewController () <UIGestureRecognizerDelegate,
HXSClickEventDelegate,
UITableViewDelegate,
UITableViewDataSource>
4. 代理的方法需要明確必須執(zhí)行和可不執(zhí)行
- @required:必須實(shí)現(xiàn)的方法
- @optional:可選是否實(shí)現(xiàn)的方法
@protocol ZOCServiceDelegate <NSObject>
@optional
- (void)generalService:(ZOCGeneralService *)service didRetrieveEntries:(NSArray *)entries;
@end
類
1. 類的名稱
應(yīng)該以三個大寫字母為前綴岸梨;創(chuàng)建子類的時候喜颁,應(yīng)該把代表子類特點(diǎn)的部分放在前綴和父類名的中間。
推薦:
//父類
ZOCSalesListViewController
//子類
ZOCDaySalesListViewController
ZOCMonthSalesListViewController
2. 所有返回類對象和實(shí)例對象的方法都應(yīng)該使用instancetype
將instancetype關(guān)鍵字作為返回值的時候曹阔,可以讓編譯器進(jìn)行類型檢查半开,同時適用于子類的檢查,這樣就保證了返回類型的正確性(一定為當(dāng)前的類對象或?qū)嵗龑ο螅?/p>
推薦:
- (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的實(shí)例變量作為它公共API的屬性稿茉。這個時候,我們不應(yīng)該引入類B的頭文件芥炭,而應(yīng)該使用向前聲明(forward declaring)使用class關(guān)鍵字漓库,并且在A的實(shí)現(xiàn)文件引用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"
優(yōu)點(diǎn):
不在A的頭文件中引入B的頭文件园蝠,就不會一并引入B的全部內(nèi)容渺蒿,這樣就減少了編譯時間。
可以避免循環(huán)引用:因?yàn)槿绻麅蓚€類在自己的頭文件中都引入了對方的頭文件彪薛,那么就會導(dǎo)致其中一個類無法被正確編譯茂装。
但是個別的時候,必須在頭文件中引入其他類的頭文件:
- 該類繼承于某個類善延,則應(yīng)該引入父類的頭文件少态。
- 該類遵從某個協(xié)議,則應(yīng)該引入該協(xié)議的頭文件易遣。而且最好將協(xié)議單獨(dú)放在一個頭文件中彼妻。
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 快速開發(fā) 代碼塊
相等性的判斷
判斷兩個person類是否相等的合理做法:
- (BOOL)isEqual:(id)object
{
if (self == object) {
return YES; //判斷內(nèi)存地址
}
if (![object isKindOfClass:[ZOCPerson class]]) {
return NO; //是否為當(dāng)前類或派生類
}
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.命名規(guī)范:不能有中文、大寫侨歉、特殊符號屋摇、空白
2.命名格式(推薦):
fileType
[function]project
[pageName]imageName
[status]{.png,@2x.png,@3x.png}
萬能公式:類別_功能_模塊_頁面_名稱_狀態(tài).png
icon_tab_bookshelf_sel@2x.png
面試題(風(fēng)格糾錯)
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
參考文獻(xiàn):
禪與 Objective-C 編程藝術(shù)
iOS 代碼規(guī)范
看完這個你們團(tuán)隊(duì)的代碼也很規(guī)范
《Effective Objective-C 2.0》