- Notifications
You must be signed in to change notification settings - Fork8
ChinaRoad/Objective-C-Style-Guide
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
此编码规范在制定的时候参考和借鉴了以下这些优秀的Objective-C编码风格指南:
- Coding Guidelines for Cocoa
- Objective-C Style Guide
- Daniel's Objective-C Coding Style Guidelines
- raywenderlich.com Objective-C style guide
实现文件中的代码结构,提倡以下约定:
用
#pragma mark -将函数或方法按功能进行分组。dealloc方法放到实现文件的最顶部。
这样是为了时刻提醒你要记得释放相关资源。
delgate或协议相关方法放到一般内容之后。
#pragma mark - Lifecycle - (void)dealloc {} - (instancetype)init {} - (void)viewDidLoad {} - (void)viewWillAppear:(BOOL)animated {} - (void)didReceiveMemoryWarning {} #pragma mark - Custom Accessors - (void)setCustomProperty:(id)value {} - (id)customProperty {} #pragma mark - Protocol conformance #pragma mark - UITextFieldDelegate #pragma mark - UITableViewDataSource #pragma mark - UITableViewDelegate #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone {} #pragma mark - NSObject - (NSString *)description {}
只用空格缩进,1个TAB = 4个空格字符
在Xcode->Preferences->Text Editing->Indentation中进行如下设置:
- Prefer indent using: 选择 Spaces
- Tab key:选择 Intents in leading whitespace
- 所有需要填写空格数目的地方都设置成4个
ps. 设置成4个,是因为Xcode的默认缩进是4个空格。大量遗留代码也都是采用的缩进4个空格。
建议:每行代码的长度最多不超过100个字符
为了防止代码过长,也为了兼顾Macbook上的排版效果,将每行长度限制成100个字符。
勾选Xcode->Preferences->Text Editing->Editing,并将长度设置成100个字符来打开行宽指示。
ps. Google倡导的每行80个字符有点少,会带来更频繁的换行,因此增加到100个字符。
建议:尝试将单个函数或方法的实现代码控制在30行内
如果某个函数或方法的实现代码过长,可以考量下是否可以将代码拆分成几个小的拥有单一功能的方法。
ps. 30行是在13寸macbook上Xcode用14号字体时,恰好可以让一个函数的代码做到整屏完全显示的行数。
建议:将单个实现文件里的代码行数控制在500~600行内
为了简洁和便于阅读,建议将单个实现文件的代码行数控制在500~600行以内最好。
当接近或超过800行时,就应当开始考虑分割实现文件了。
最好不要出现代码超过1000行的实现文件。
我们一般倾向于认为单个文件代码行数越长,代码结构就越不好。而且,翻代码翻的手软啊。
可以使用Objective-C的Category特性将实现文件归类分割成几个相对轻量级的实现文件。
可以勾选上Xcode->Preferences->Text Editing->Editing中的Line numbers,开启行号提示。
实现文件中,函数实现或方法实现之间必须至少有一行空行
没有空行,代码过长后,全粘在一起,很影响阅读。
//禁止的 - (void)loadView {//load view... } - (void)viewDidLoad { [superviewDidLoad];//Do Something... }//正确的 - (void)loadView {//load view... } - (void)viewDidLoad { [superviewDidLoad];//Do Something... }
重载父类方法时,遇到必须调用父类方法时。调用super的代码和重载的代码之间留一行空行。
这样做是为了便于区分出对super的调用。通常在iOS SDK中,有许多方法在重载的时候,都要求调用super。有时候忘记调用super就会出现行为怪异的bug。因此这里要求将调用super的代码区隔开来,方便阅读,也方便查找是否忘记了对super的调用。
- (void)viewWillDisappear:(BOOL)animated { [superviewWillDisappear:animated];//空一行,将super方法的调用和重载代码区隔开来。 [NSObjectcancelPreviousPerformRequestsWithTarget:self]; }
实现文件中,函数体的左花括号不另起一行,和函数名同行,并且和函数名之间保持1个空格
此条是为了和Xcode6.1模板生成的文件的代码风格保持一致。
//赞成的 - (void)didReceiveMemoryWarning { [superdidReceiveMemoryWarning];// Dispose of any resources that can be recreated. }//不赞成的 - (void)didReceiveMemoryWarning { [superdidReceiveMemoryWarning];// Dispose of any resources that can be recreated. }
其他地方(
if/else/while/switch等),左花括号不单独另起一行。左花括号后面紧接着的代码块超过5行后,代码块和括号之间要有一行空行;代码块小于5行可以不空行此条是为了和Xcode自动代码补全生成的代码风格保持一致。
//赞成的 - (void)didReceiveMemoryWarning { [superdidReceiveMemoryWarning];// Dispose of any resources that can be recreated.if (somethCondtion) {//DO Something. } }//不赞成的 - (void)didReceiveMemoryWarning { [superdidReceiveMemoryWarning];// Dispose of any resources that can be recreated.if (somethCondtion) {//DO Something } }
建议: if/else中,else与上一条分支语句的右括号之间需要换行
此条是为了防止else和上一个分支的代码块挨在一起,影响阅读,所以建议要换行。
换行后,也便于快速定位到else分支。
//赞成的if (a >0) {//Do Something }else {//Do Something }//不赞成的if (a >0) {//Do Something }else {//Do Something }
如果需要手动使用
@synthesize或@dynamic,每行只能定义一个属性方法调用中,如果block参数需要换行时,block结尾的花括弧要和声明block那一行的第一个字符对齐
[operationsetCompletionBlock:^{ [self.delegatenewDataAvailable]; }];
如果方法调用中部分的代码过长,造成内嵌的block代码缩进过长,可以适当的增加手动换行,以减少代码缩进
//如以下代码在loadWindowWithCompletionBlock前加了手动换行,是被提倡的: [[SessionServicesharedService]loadWindowWithCompletionBlock:^(SessionWindow *window) {if (window) { [selfwindowDidLoad:window]; }else { [selferrorLoadingWindow]; } }];
注释应该尽量保持简洁,代码应该尽量达到能自我解释的程度
当然用于生成文档的注释除外,用于生成文档的注释要尽量详细,特别是你的接口可能有副作用的时候,要注释清楚。
注释必须和代码保持同步。不要出现代码修改了,注释不更新的情况
对函数或API接口的注释,都采用Javadoc风格规范
因为Xcode5支持直接将Javadoc风格的注释生成文档。
也有用于添加Javadoc风格注释的Xcode插件:VVDocumenter-Xcode
Xcode8已经内嵌VVDocumenter, 快捷键: option + command + /
无论什么情况下,都要尽量坚持苹果的命名规范,特别是涉及到内存管理规则时
这里的"内存管理规则",强调的是底层Core Foundation框架中,名字带Create或Copy的函数,返回的对象,你要负责它的释放。
类名、类别名字及协议名字,都采用大驼峰式命名规则
文件名要能反映出它所包含的类的名称
如:NSString.h 和 NSString.m 包含了NSString类的定义和实现
Category的文件名要包含它所扩展的那个类的名称,并且类别名称要尽量能够描述它的功能
UIImage+Resize.h 或 UIImage+TintColor.h
在面向特定应用的代码中,类名尽量避免使用前缀,每个类都使用相同的前缀会影响可读性
面向特定应用的代码,指那些只会在一个项目中使用的代码,不会被用于其他项目中的代码。
在面向多应用的代码中,类名要使用前缀,防止命名冲突
面向多应用的代码,指那些会被多个项目共同使用的代码。
比如CRKit这个类库中,使用了CR前缀。
建议:前缀至少使用三个字母
此条是为了减少命名冲突。但鉴于目前流行前缀大多都是两个字母,所以此条不做强制要求
协议声明或定义中,类型标识符、协议名称、尖括号之间不留空格
@interfaceMyProtocoledClass :NSObject<NSWindowDelegate> {@privateid<MyFancyDelegate> _delegate; } - (void)setDelegate:(id<MyFancyDelegate>)aDelegate;@end
方法名和参数名都采用小驼峰式命名规则。
如:- (BOOL)isFileExistedAtPath:(NSString *)filePath;
方法声明中,-/+和返回值类型之间要空1个空格,方法名和参数类型之间以及参数类型和参数名之间不留空格
- (void)invokeWithTarget:(id)target;//正确 - (void)invokeWithTarget: (id)target;//错误 - (void)invokeWithTarget:(id) target;//错误 - (void)invokeWithTarget: (id) target;//错误
方法声明中,参数过多超过一行时,可以增加手动换行,使每个参数占用一行,以冒号对齐
- (void)doSomethingWith:(GTMFoo *)theFoo rect:(NSRect)theRect interval:(float)theInterval;
方法名第一段比其他部分短时,每个参数占用一行,每行至少缩进4个空格,尽量保持参数以冒号对齐
同时选中多行代码,用快捷键"command+["或"command+]"可以减少或增加缩进。
- (void)short:(GTMFoo *)theFoo longKeyword:(NSRect)theRect evenLongerKeyword:(float)theInterval error:(NSError **)theError;
方法名和参数名应该尽量读起来像一句话。具体参见苹果的方法名命名规范
如:convertPoint:fromRect: 或者 replaceCharactersInRange:withString:
当各个参数是接收者的某个属性时,方法名中不要用"and"来连接
//赞成的 - (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes;//不赞成的 - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
如果方法名描述了两种不同的动作,要使用"and"来连接
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;
getter方法的方法名应该和变量名字相同,不允许使用"get"前缀
本规则仅适用于Objective-C,C++使用C++的相关规范
- (id)delegate;// 正确 - (id)getDelegate;//禁止
类私有方法以下划线开头
如:
- (void)_startDownloadFiles;Objective-C里面没有真正严格意义上私有方法。这里所说的"私有方法"指那些不需要公开的、只会在实现文件中使用的方法。
这样做的好处是,可以直观的快速区别实现文件中的私有方法和公有方法。
这样做会很便于重构。如果某个方法废弃了,需要移除的时候,发现它是以下划线开头的,那么就可以确定这个方法是私有的,只会在这个实现文件中被用到。那么直接在该实现文件搜索这个方法的名字,然后清理掉搜索到的地方就可以了。不必再在整个项目中查找是否没有清理干净。
根据苹果的建议,这种做法可能覆盖掉父类的私有方法。但是目前还没有遇到过这种情况,而且我们认为此条约定带来的好处远远大于它潜在的危险,因此仍然推行这条约定。
函数指纯C函数,这里提倡与苹果风格类似的约定。
函数名采用大驼峰式命名方式,参数名采用小驼峰式命名方式
如果函数和某个特定类型相关,那么函数名前缀要和类型前缀一样
如
CGRectMake()、CGContextCreate()等
创建NSString, NSDictionary, NSArray, 以及NSNumber等常量时,使用Literals语法
//例如:NSArray *names = @[@"Brian",@"Matt",@"Chris",@"Alex",@"Steve",@"Paul"];NSDictionary *productManagers = @{@"iPhone" :@"Kate",@"iPad" :@"Kamal",@"Mobile Web" :@"Bill"};NSNumber *shouldUseLiterals = @YES;NSNumber *buildingZIPCode = @10018;//而不是:NSArray *names = [NSArrayarrayWithObjects:@"Brian",@"Matt",@"Chris",@"Alex",@"Steve",@"Paul",nil];NSDictionary *productManagers = [NSDictionarydictionaryWithObjectsAndKeys:@"Kate",@"iPhone",@"Kamal",@"iPad",@"Bill",@"Mobile Web",nil];NSNumber *shouldUseLiterals = [NSNumbernumberWithBool:YES];NSNumber *ZIPCode = [NSNumbernumberWithInteger:10018];
定义枚举常量时,使用NS_ENUM或NS_OPTIONS
NS_ENUM和NS_OPTIONS都提供了类型检查
//例如:typedefNS_ENUM(NSUInteger, PPNavBarButtonColor) { PPNavBarButtonColorBlack, PPNavBarButtonColorGreen, PPNavBarButtonColorDefault = PPNavBarButtonColorBlack };typedefNS_OPTIONS(NSUInteger, PSTCollectionViewScrollPosition) { PSTCollectionViewScrollPositionNone =0, PSTCollectionViewScrollPositionTop =1 <<0, PSTCollectionViewScrollPositionCenteredVertically =1 <<1, PSTCollectionViewScrollPositionBottom =1 <<2, PSTCollectionViewScrollPositionLeft =1 <<3, PSTCollectionViewScrollPositionCenteredHorizontally =1 <<4, PSTCollectionViewScrollPositionRight =1 <<5 };
定义常量时,除非明确的需要将常量当成宏使用,否则优先使用
const,而非#define只在某一个特定文件里面使用的常量,用static
static关键字保证变量只有文件作用域,可以避免变量名重名造成的链接错误问题。
比如:
static CGFloat const RWImageThumbnailHeight = 50.0;
常量名以小写k开头,采用首字母大写的方式来分割单词
//例如:constintkNumberOfFiles =12;NSString *constkUserKey =@"kUserKey";enum DisplayTinge {kDisplayTingeGreen =1,kDisplayTingeBlue =2 };
和特定类型相关的枚举常量使用类名作为前缀,而不用小写k开头。
typedefNS_OPTIONS(NSUInteger, UICollectionViewScrollPosition) { UICollectionViewScrollPositionNone =0, UICollectionViewScrollPositionTop =1 <<0, UICollectionViewScrollPositionCenteredVertically =1 <<1, UICollectionViewScrollPositionBottom =1 <<2, UICollectionViewScrollPositionLeft =1 <<3, UICollectionViewScrollPositionCenteredHorizontally =1 <<4, UICollectionViewScrollPositionRight =1 <<5 };
属性名和变量名都采用小驼峰式命名规则
实例变量名以下划线开头,局部变量不能以下划线开头
禁止使用匈牙利标记法或含糊不清的缩写单词来命名变量
for循环中的i、j、k这种情况例外。
Objective-C中,变量名应该尽量清楚的描述它的用途。这样可以使别人立即明白代码的意思,不要担心这样会导致代码过长。
//以下这些都是错误的命名规范int w;int nerr;int nCompConns; tix = [[NSMutableArrayalloc]init]; obj = [someObjectobject]; p = [networkport];//以下这些才是赞成的命名规范int numErrors;int numCompletedConnections; tickets = [[NSMutableArrayalloc]init]; userInfo = [someObjectobject]; port = [networkport];
指针符号 "*" 靠近变量名字。(常量定义除外)
NSString *varName;//赞成的NSString* varName;//不赞成的
使用property时,优先使用点语法
使用点语法会让代码简洁。但对于其他情况,都应该使用方括号语法。
//赞成的NSInteger arrayCount = [self.arraycount]; view.backgroundColor = [UIColororangeColor]; [UIApplicationsharedApplication].delegate;//不赞成的NSInteger arrayCount = self.array.count; [viewsetBackgroundColor:[UIColororangeColor]]; UIApplication.sharedApplication.delegate;
通知名字的命名规则:[相关联的类名字] + [Did | Will] + [独一无二的一段名称] + Notification
如:UIApplicationDidBecomeActiveNotification
异常名字的命名规则:[前缀] + [独一无二的一段名称] + Exception
如:NSColorListIOException
Objective-C的布尔值只使用
YES和NOtrue和false只能用于CoreFoundation,C或C++的代码中禁止将某个值或表达式的结果与
YES进行比较因为BOOL被定义成signed char。这意味着除了YES(1)和NO(0)以外,它还可能是其他值。
因此C或C++中的非0为真并不一定就是YES
//以下都是被禁止的 - (BOOL)isBold {return [selffontTraits] & NSFontBoldTrait; } - (BOOL)isValid {return [selfstringValue]; }if ([selfisBold] ==YES) {//... }//以下才是赞成的方式 - (BOOL)isBold {return ([selffontTraits] & NSFontBoldTrait) ?YES :NO; } - (BOOL)isValid {return [selfstringValue] !=nil; } - (BOOL)isEnabled {return [selfisValid] && [selfisBold]; }if ([selfisBold]) {//... }
虽然
nil会被直接解释成NO,但还是建议在条件判断时保持与nil的比较,因为这样代码更直观。//比如,更直观的代码if (someObject !=nil) {//... }//没那么直观的代码if (!someObject) {//... }
在C或C++代码中,要注意NULL指针的检测。
向一个nil的Objective-C对象发送消息不会导致崩溃。但由于Objective-C运行时不会处理给NULL指针的情况,所以为了避免崩溃,需要自行处理对于C/C++的NULL指针的检测。
如果某个
BOOL类型的property的名字是一个形容词,建议为getter方法加上一个"is"开头的别名。@property (assign, getter = isEditable)BOOL editable;在方法实现中,如果有block参数,要注意检测block参数为nil的情况。
- (void)exitWithCompletion:(void(^)(void))completion { // 错误。 如果外部调用此方法时completion传入nil,此处会发生EXC_BAD_ACCESS completion(); // 正确。如果completion不存在则不调用。 if (completion) { completion(); } }
条件语句的语句体,即便只有一行,也不能省略花括弧
这样可以减少失误。比如你在if语句体中增加第二行语句的时候,就可能会因为没有花括号而导致新增的第二行语句没有被包含在if语句体中。另外,这里还提到了其他的一些危险情况。
//赞成的if (error ==nil) {return success; }//不赞成的if (error ==nil)return success;//或if (error ==nil)return success;
多层嵌套的条件语句,优先考虑条件不成立可以立即跳出的情况
Objective-C的代码普遍比较长,如果再加上多层嵌套的条件语句,代码缩进会增多,代码会变得更长,会影响可读性。比如,下面这种情况,换成优先考虑可以跳出的情况,可以有效的减少代码缩进长度:
//一般流程if (a) {if (b) {if (c) {//do something } } }//优先考虑可以跳出的流程if (!a) {return; }if (!b) {return; }if (!c) {return; }//do something
三目运算只有在能增加代码清晰度和整洁度的时候才推荐使用
三目运算符(?:),如果不能增加代码整洁度和清晰度,使用时就要谨慎。特别是,嵌套使用多个三目运算,这种要尽量避免。因为它会使代码更难阅读。
另外,三目运算符中的条件判断是一个语句,最好用小括号括起来。如果直接是一个布尔值则无需括号。例如:
//赞成的NSInteger value =5; result = (value !=0) ? x : y;BOOL isHorizontal =YES; result = isHorizontal ? x : y;//不赞成的 result = a > b ? x = c > d ? c : d : y;
初始化方法的返回类型用
instancetype关于instancetype的介绍参见NSHipster.com。
访问CGRect中的x、y、width或height元素时,不直接访问而是使用CGGeometry相关函数
CGGeometry里面的函数,会对CGRect参数进行隐式的标准化处理,然后再计算结果。因此,你应该避免直接读取或重写CGRect数据结构里面的值,而要使用这些函数来进行相关操作。
什么叫标准化处理,参见CGGeometry Reference的Overview章节。
//赞成的 CGRect frame = self.view.frame; CGFloat x = CGRectGetMinX(frame); CGFloat y = CGRectGetMinY(frame); CGFloat width = CGRectGetWidth(frame); CGFloat height = CGRectGetHeight(frame); CGRect frame = CGRectMake(0.0,0.0, width, height);//不赞成的 CGRect frame = self.view.frame; CGFloat x = frame.origin.x; CGFloat y = frame.origin.y; CGFloat width = frame.size.width; CGFloat height = frame.size.height; CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };
实体文件应该和Xcode工程文件保持同步,防止出现文件不一致
任何手动创建的Xcode Group都应该在文件系统有一个对应的文件夹。代码不仅要根据类型组织,更要以更加清晰的特征来区分归类。
建议:在可能的情况下,始终要勾选在Build设置选项中”Treat Warnings as Errors(将告警视为错误)“选项。同时尽可能多的暴露更多的additional warnings(附加告警)。如果要忽略某类特定Warning(告警),请使用Clang's pragma feature。
此条不做强制要求,但是"将警告视为错误"是你应当要有的态度。
最后贴张图娱乐一下,虽然说Objective-C中长名是美德,但是什么东西还是要有个度。有人写了个脚本统计Cocoa Framework中各种最长的命名,结果发现低估了苹果程序员的造句能力。Mac平台最长的常量名96个字符,最长的方法名150个字符,C函数名都能到68个字符! -_-# 泥煤,自从学会了Objective-C,妈妈再也不用担心我的造句能力了。
About
ChinaRoad Objective-C Style Guide
Topics
Resources
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Contributors2
Uh oh!
There was an error while loading.Please reload this page.
