Apple公司提供了一些代码规范文档。若是有内容未在此文档中说起,请参考以下内容:html
全部适用Objective-C语言开发的项目。 在这里咱们但愿以相似断言的方式,你们逐条对比写出的代码和下列规范是否吻合,以达到预期的代码的可读性。ios
基于iOS objective-c项目对于命名,目前分为变量名和函数名两类git
在这里咱们把描述一个事物或者抽象事物的描述符统称为变量名。变量名目前分为几类: 类名,协议名,组合名,oc类内部变量,全局变量,枚举类型,block类型,结构体类型。 如下分别例举了几种类型的例子。github
@interface MKUserTrackingBarButtonItem : UIBarButtonItem
@protocol MKMapViewDelegate <NSObject> @protocol MKAnnotation <NSObject>
@interface NSString (NSStringExtensionMethods)
@property (nonatomic, copy) NSString *title; @property (nonatomic, readonly, getter=isLoading) BOOL loading; @property (nonatomic, getter=isSelected) BOOL selected;
NSString * const NSSystemClockDidChangeNotification
enum
枚举,由于它支持强类型检查及自动完成。SDK如今也支持枚举定义宏NS_ENUm()
和NS_OPTION()
,前者的各个选项是互斥的,然后者能够经过按位或|
来组合使用。typedef NS_OPTIONS(NSUInteger, MKDirectionsTransportType) { MKDirectionsTransportTypeAutomobile = 1 << 0, MKDirectionsTransportTypeWalking = 1 << 1, MKDirectionsTransportTypeAny = 0x0FFFFFFF }
typedef void (^MKDirectionsHandler)(MKDirectionsResponse *response, NSError *error);
typedef struct { CLLocationDegrees latitude; CLLocationDegrees longitude; } CLLocationCoordinate2D;
apple使用的缩写objective-c
######一些习惯 1.上述咱们说一个层级变量起名为 限定词+功能+类型。这里咱们有个例外的地方,对于NSString, NSArray, NSNumber, BOOL类型咱们无需指定类型。安全
2.对于命名一个复数形式的变量,若是它不是NSArray或者NSSet最好指定类型。 3.对于其余类型,好比Image, Indicator这样的特殊类型或UI组件在变量命名的后半部分指定它的类型是有必要的。 尤为对于XXXManager类型的变量写成好比fontManager是必须的,不然没法理解它的含义。
一些习惯的例子app
oc语言实际上很贴近天然语言。先抛开一般做为全局函数用的c/cpp函数,oc的类内部函数一般看起来就像是一个句子。在这个命名规范里不去结合语法分析这个了,一下会根据函数常有的功能去作个分类。iphone
#####以动词开头方法ide
- (void)insertOverlay:(id <MKOverlay>)overlay atIndex:(NSUInteger)index level:(MKOverlayLevel)level; - (BOOL)createSymbolicLinkAtURL:(NSURL *)url withDestinationURL:(NSURL *)destURL error:(NSError **)error;
1.对于以动词开头的函数,表示去执行某一个任务。 2.咱们通常定义他的返回值为void, 当须要获得他是否执行成功的状态时能够以BOOL做为返回值 3.有一个例外是init开头通常是用于构造,返回一个构造的实例。
#####以名词开头方法svn
- (MKAnnotationView *)viewForAnnotation:(id <MKAnnotation>)annotation; + (instancetype)circleWithCenterCoordinate:(CLLocationCoordinate2D)coord radius:(CLLocationDistance)radius;
1.对于以动词开头的函数,表示返回某一个具体事物。 2.当函数做为回调函数存在时,它是例外的
#####回调函数
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row; - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender; - (void)browserDidScroll:(NSBrowser *)sender; - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window; - (BOOL)windowShouldClose:(id)sender; - (void)windowDidChangeScreen:(NSNotification *)notification;
1.消息发送者必须做为参数。若是函数参数只有消息发送者自己,将他放到函数最后。若是有2个及以上参数则放到第一位。 2.did和will常常用在回调函数当作标记'已经发生'或者'将要发生'。 3.'should'应用场景一般是询问代理行为是否应该发生,一般返回BOOL。 4.经过通知的回调通常来讲全部数据都放在notification内部,因此不须要返回值以及其余参数 5.全部回调函数均以名词开头,标示是什么引起的回调。咱们能够认为去掉开头的名词和调用者参数,基本和咱们以前定的规范一致。
#####一些通用规则和建议 1.参数冒号以前用名词指代明确的参数类型 2.多个参数不须要用and链接 3.一些介词有助于提高函数名的可读性,好比:for,with,from,in,on,at等。
####处理魔术变量 使用常量而非内联的字串literal或魔术数,由于这样能更方便地修改它们。
使用static const
常量,禁止使用#define
宏来定义变量,使用宏没有类型检查,并易被覆盖定义而很难检测。
For example:
static NSString * const ZDAboutViewControllerCompanyName = @"The New York Times Company"; static const CGFloat ZDAboutViewThumbImageHeight = 50.0;
Not:
#define CompanyName @"The ZDWorks Company" #define thumbnailHeight 2
使用dot-notation(.表示法)来获取/更改property。 Bracket notation([]表示法)适用于其余领域。
For example:
view.backgroundColor = [UIColor orangeColor]; [UIApplication sharedApplication].delegate;
Not:
[view setBackgroundColor:[UIColor orangeColor]]; UIApplication.sharedApplication.delegate;
只能在初始化方法、析构方法和自定义getter/setter里面,直接访问实例变量(ivar),其余状况只能经过dot-notation访问property。更多内容参见 here. 不要直接声明实例变量,声明property便可。
使用BBUncrustify来格式化代码,formatter使用Clang,配置文件见.clang-format
{
),另起一行关闭(}
)if
/else
/switch
/while
/block
etc.)在当前行打开,另起一行关闭For example:
- (void)foo:(User *)user { if (user.isHappy) { //Do something } else { //Do something else } }
@dynamic
或@synthesize
占据一行,Xcode4.4之后省略@synthesize
。条件语句的body必须被括号包含,即便只有一行。这样便于在body内新增操做而不会出错,同时可读性更强。
For example:
if (!error) { return success; }
Not:
if (!error) return success;
or
if (!error) return success;
OC的方法,须要在符号+/-后添加一个空格。前一个参数和后一个中缀之间有且仅有一个空格,好比下方示例的text参数和image中缀之间。
For Example:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
指针变量的*与指针类型中间隔一个空格,与变量名中间无空格,e.g., NSString *text
not NSString* text
or NSString * text
。
对外接口必须写注释
注释能够采用/* */
和//
两种注释符号,涉及到多行注释时,尽可能使用/* */
。方法里的注释只能使用//
,由于嵌套/**/
极可能带来没法预知的问题。
Xcode会生成一段默认注释,咱们须要在此基础上扩充,加入功能描述和修改记录部分。虽然svn/git可以看到完整的修改记录以及经过blame查找责任人,可是commit太多的时候很难定位。
For example:
// // ClockTricksController.m // ZDClock // // Created by John_Ma on 13-11-26. // Copyright (c) 2013年 ZDworks Co., Ltd. All rights reserved. // 功能描述: // 修改记录:(仅记录功能修改) // 张三 2012-02-02 建立该单元 // 小明 2010-03-02 增长本地点单功能。 //
方法注释通常出如今.h文件里,.m文件里尽可能保持简洁,使用方法名完整描述功能和参数。方法注释使用VVDocument插件生成,并在每次修改后及时更新。
For example:
/** * <#Description#> * * @param application <#application description#> * @param launchOptions <#launchOptions description#> * * @return <#return value description#> */
尽可能不要出现方法内注释,若有可能将相关代码Extract到新方法里,使用方法名描述其功能。若是必需要要使用方法内注释,使用//
注释在所要描述的代码前一行或者同一行末尾。
.h文件中只暴露目前被其余类使用的接口、属性。内部使用的接口、属性在extension(匿名category)中定义,好比IBOutlet等。
在.h实现protocol亦是如此,会暴露该protocol包含的接口。若是外部无需使用相关接口,则移到extesion中。
For example:
// .m file @interface Test ()<UITableViewDataSource> @property (nonatomic, strong) IBOutlet UIButton *refreshButton; - (void)privateDoSth; @end
Not:
// .m file @interface Test : NSObject<UITableViewDataSource> @property (nonatomic, strong) IBOutlet UIButton *refreshButton; - (void)privateDoSth; @end
只有当可以提升代码的可读性时,才应该使用三目运算符?:,好比单一判断条件。若是有多个判断条件,使用if会更好些。
For example:
result = a > b ? x : y;
Not:
result = a > b ? x = c > d ? c : d : y;
当方法使用引用返回表示错误的参数时,使用返回值判断,而非该错误变量。
For example:
NSError *error; if (![self trySomethingWithError:&error]) { // Handle Error }
Not:
NSError *error; [self trySomethingWithError:&error]; if (error) { // Handle Error }
在成功的状况下,Apple的一些API会将奇怪的值而非nil写入错误参数,因此不要使用该错误变量来判断。
NSString
, NSDictionary
, NSArray
和NSNumber
的immutable实例应该使用literal来建立,mutable实例也建议经过这种方式及mutableCopy方法来建立。须要注意的是须要作nil检测。
For example:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"]; NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"}; NSNumber *shouldUseLiterals = @YES; NSNumber *buildingZIPCode = @10018;
Not:
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil]; NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil]; NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES]; NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];
使用CGGeometry
functions而非结构体的数据成员来获取x
, y
, width
, or height
的值。From Apple's CGGeometry
reference:
All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.
全部在这里定义、使用CGRect结构体做为输入参数的方法,先对这些矩形作标准化操做,再计算它们的方绘制。因此咱们应该直接经过这些方法,而非访问结构体的数据成员来得到这些矩形的属性。
For example:
CGRect frame = self.view.frame; CGFloat x = CGRectGetMinX(frame); CGFloat y = CGRectGetMinY(frame); CGFloat width = CGRectGetWidth(frame); CGFloat height = CGRectGetHeight(frame);
Not:
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;
私有property应该定义在类扩展(匿名类别)中。这样有个好处是,当你须要将其暴露给外部,直接command+x、command+v到.h文件中便可。
For example:
@interface ZDAdvertisement () @property (nonatomic, strong) GADBannerView *googleAdView; @property (nonatomic, strong) ADBannerView *iAdView; @property (nonatomic, strong) UIWebView *adXWebView; @end
在OC中,使用以下线程安全的方式来建立单例
+ (instancetype)sharedInstance { static id sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; }
这种方式能够防止 可能的崩溃.
函数长度(行数)不该超过2/3屏幕,禁止超过70行。
例外:对于顺序执行的初始化函数,若是其中的过程没有提取为独立方法的必要,则没必要限制长度。
尽早返回错误:
For example:
- (Task *)creatTaskWithPath:(NSString *)path { if (![path isURL]) { return nil; } if (![fileManager isWritableFileAtPath:path]) { return nil; } if ([taskManager hasTaskWithPath:path]) { return nil; } Task *aTask = [[Task alloc] initWithPath:path]; return aTask; }
Not:
- (Task *)creatTaskWithPath:(NSString *)path { Task *aTask; if ([path isURL]) { if ([fileManager isWritableFileAtPath:path]) { if (![taskManager hasTaskWithPath:path]) { aTask = [[Task alloc] initWithPath:path]; } else { return nil; } } else { return nil; } } else { return nil; } return aTask; }