- Notifications
You must be signed in to change notification settings - Fork3
Tinghui/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
Objective-C Style Guide
Topics
Resources
Uh oh!
There was an error while loading.Please reload this page.
