Objective-C代码规范

Objective-C代码规范

前言

Apple公司提供了一些代码规范文档。若是有内容未在此文档中说起,请参考以下内容:html

适用范围

全部适用Objective-C语言开发的项目。 在这里咱们但愿以相似断言的方式,你们逐条对比写出的代码和下列规范是否吻合,以达到预期的代码的可读性。ios

代码规范

命名

基于iOS objective-c项目对于命名,目前分为变量名和函数名两类git

变量名

在这里咱们把描述一个事物或者抽象事物的描述符统称为变量名。变量名目前分为几类: 类名,协议名,组合名,oc类内部变量,全局变量,枚举类型,block类型,结构体类型。 如下分别例举了几种类型的例子。github

类名
  1. 使用类前缀
  2. 须要包含一个名词用来表示这个类是什么,好比 NSString, NSDate, NSScanner等。
@interface MKUserTrackingBarButtonItem : UIBarButtonItem
协议名
  1. 使用类前缀
  2. 在这里咱们须要考虑一个重要的问题,不要滥用关键字,。面列了两个协议,"delegate"一般用于实现委托功能,而第二个用于实现的重载。
  3. 大部分协议实际是包括一组功能相关的函数,而且和具体用于实现的类没有特别紧密的联系。这时候命名要考虑和具体实现类区分开,好比起名为NSLocking而不是NSLock。
  4. 还有一些协议实际上囊括了不少不相关的功能(或者说像是不少个子协议的组合),这时候就能够和具体的实现类保持一致的名字,好比NSObject。
@protocol MKMapViewDelegate <NSObject>
@protocol MKAnnotation <NSObject>
组合名
  1. 须要类前缀
@interface NSString (NSStringExtensionMethods)
oc类内部变量
  1. 无需类前缀
  2. 尽量使用property定义变量
  3. .对于一些BOOL型变量表示状态的通常是动词+时态来表示一个名词,好比loading和selected。有趣的是,他们的getter方法都写成了 is+变量名,这样用起来的时候就更加接近天然语言。
@property (nonatomic, copy) NSString *title;

@property (nonatomic, readonly, getter=isLoading) BOOL loading;
@property (nonatomic, getter=isSelected) BOOL selected;
全局变量
  1. 必须添加类前缀
  2. 对于全局通知,咱们须要遵照一个标准结构:“[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification”
NSString * const NSSystemClockDidChangeNotification
枚举
  1. 类型名及枚举值均须要添加类前缀
  2. 枚举的具体值的名字为 枚举名+名词
  3. 使用enum枚举,由于它支持强类型检查及自动完成。SDK如今也支持枚举定义宏NS_ENUm()NS_OPTION(),前者的各个选项是互斥的,然后者能够经过按位或|来组合使用。
typedef NS_OPTIONS(NSUInteger, MKDirectionsTransportType) {
  MKDirectionsTransportTypeAutomobile     = 1 << 0,
  MKDirectionsTransportTypeWalking        = 1 << 1,
  MKDirectionsTransportTypeAny            = 0x0FFFFFFF
}
block
  1. 形参名无需前缀,类型名须要添加前缀
  2. 苹果的习惯是以handler结尾表示他的功能
typedef void (^MKDirectionsHandler)(MKDirectionsResponse *response, NSError *error);
结构体
  1. 形参名无需前缀,类型名须要添加前缀
typedef struct {
	CLLocationDegrees latitude;
	CLLocationDegrees longitude;
} CLLocationCoordinate2D;
通用规则
  1. 禁止使用小写下划线形式(snake_case)
  2. 关于类前缀这件事情,对于全局可见的变量须要添加,而对于类的内部变量和结构体内部变量则不须要添加。咱们概括一个原则,即变量的从属关系。对于能够全局可见的类型(类名,协议名,组合名,全局变量,枚举类型,block类型,结构体名)从属于项目名下,因为项目自己没法添加命名空间,即全部属于他名下的变量名须要添加前缀。而类的内部变量从属于他的所属类,结构体内部变量从属于结构体自己。
  3. 这个原则是讨论在考虑层级的原则下如何给变量名起一个合适的名字。上面咱们讨论了从属规则,为了统一原则,咱们将有从属的变量和他的从属合并。好比MKUserTrackingBarButtonItem类内部有个NSString变量叫title,咱们就合并为MKUserTrackingBarButtonItemTitle。对于绝大部分事物咱们均可以认为他是名词或者形容词加名词,在这里诸如userTracking,barButtonItem,title,在一个项目中为了准确的标示一个变量是什么就须要从他的前缀开始逐层向下看他的每一个层级是否能准确的标示这个层级是什么。就像上述例子,userTracking是全局惟一的事物,这里的barButtonItem只属于userTracking,而这里的headline又只属于UserTrackingBarButtonItem,这样咱们能够很明显的看出这个title准确的标示着什么。
  4. 这里讨论一下关于单个层级的命名原则,上面论述过能够把变量拆分为几个层级。对于每一个层级来讲咱们倾向于为一个名词或者名词词组,在使用词组时不添加介词,好比写成nameLabel而不是labelForName。在描述一个层级的时候须要考虑几个问题,是什么,实现什么功能,在什么状况下实现这个功能。而后反序写出来 会变成:限定词+功能+类型 这样一种组合方式。固然这三部分在某些状况下均可以缺省,这个放到后面论述。
  5. 这里讨论在选择用来命名的单词的问题。其实到这里才到了真正的关键点,命名用词的选择!依据apple官方的要求,这里总结了几点。 ######清晰 1.官方对于清晰的要求是不要滥用缩写 好比destinationSelection 不要写成destSel。至于什么时候能用缩写咱们下面讨论。 2.注意用词是否有一个明确的含义,当诸如object,data,flag单独做为变量出现的时候,确定让人无所适从。 ######一致 当不少个类有一样做用的变量时,应该保证他们使用同一个变量名。好比tag用于NSView, NSCell, NSControl中。 ######使用缩写 在前面的“清晰”的要求中提出了不要滥用缩写,那何时推荐用缩写呢?这里有一个准则是使用你们默认的缩写,在apple的官方文档中有一个例子(见下方连接【apple使用的缩写】)。 固然这里不要求全部非一下词汇不可以使用缩写,咱们这里但愿达成几个准则为: 1.缩写不会和别的词汇产生混淆和冲突, 假设咱们把Matrix简写成mtx就很容易和其余词(好比max,mix)产生混淆。 2.在项目中要足够经常使用 3.若是使用就保持全局统一使用,不要同时出现全称和缩写 4.和团队成员达成统一

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

使用dot-notation(.表示法)来获取/更改property。 Bracket notation([]表示法)适用于其余领域。
For example:

view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;

Not:

[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;

@property && ivar

只能在初始化方法、析构方法和自定义getter/setter里面,直接访问实例变量(ivar),其余状况只能经过dot-notation访问property。更多内容参见 here. 不要直接声明实例变量,声明property便可。

格式

工具

使用BBUncrustify来格式化代码,formatter使用Clang,配置文件见.clang-format

Spacing

  • 使用4个空格而非tab符缩进,并检查其是否为Xcode预设值。
  • 方法的大括号另起一行打开({),另起一行关闭(})
  • 其余大括号 (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到新方法里,使用方法名描述其功能。若是必需要要使用方法内注释,使用//注释在所要描述的代码前一行或者同一行末尾。

最佳实践

@interface

.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写入错误参数,因此不要使用该错误变量来判断。

Literals

NSString, NSDictionary, NSArrayNSNumber的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];

CGRect Functions

使用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;

私有Properties

私有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行。
例外:对于顺序执行的初始化函数,若是其中的过程没有提取为独立方法的必要,则没必要限制长度。

  • 单个文件方法数不该超过30个
  • 不要按类别排序(如把IBAction放在一块),应按任务把相关的组合在一块儿
  • 禁止出现超过两层循环的代码,用函数或block替代。

尽早返回错误:

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;
}
相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息