Objective-C 编码规范

介绍

这份规范指南概括了使用 Objective-C 时所遵循的代码约定。

示例代码如下:

typedef NS_ENUM(NSInteger, StyleGuideFormat) {
    StyleGuideFormatLeft,
    StyleGuideFormatRight,
    StyleGuideFormatUp,
    StyleGuideFormatDown
};

static NSString * const StyleGuideDidChangedNotification = @"StyleGuideDidChangedNotification";
static NSString * const StyleGuideUserInfoKey = @"StyleGuideUserInfoKey";
static NSString * const StyleGuideInvalidFormatException = @"StyleGuideInvalidFormatException";

static const NSInteger StyleGuideTotalCount = 100;

@interface StyleGuide () <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration> 

@property (nonatomic, readonly, getter = isEditable) BOOL editable;
@property (nonatomic, readwrite) NSDictionary *userInfo;

@end

@implementation StyleGuide

- (void)dealloc {
    [super dealloc];
}

- (instancetype)init {
    self = [super init];
    if (self) {
        //Custom initialization
    }
    
    return self;
}

- (NSInteger)count {
    NSInteger keyCount = 10;
    NSInteger objectCount = 20;
    NSInteger total = keyCount + objectCount;
    
    return total;
}

@end

格式

缩进

一个缩进使用 4 个空格,不要使用制表符(tab)缩进。

空行

不同的模块之间以空行相隔,这有助于视觉清晰度和代码组织性,有以下几种(示例代码参考本指南开头的代码):

  • @interface, @implementation, @protocol, @end, @optional, @required 与相邻的模块之间应该有空一行。
  • 方法体实现之间应该正好空一行。

变量

  • 指针类型变量 * 应紧靠变量名。
  • 在ARC中拥有性限定符(__strong, __weak, __unsafe_unretained, __autoreleasing)以及 __block 应位于类型名之前。
    推荐:
NSString *name;
__weak NSString *name;
__block NSString *name;

反对:

NSString* name;
NSString*name;
NSString * name;

常量

  • 定义指针类型的常量时,const 位于 * 之后,且以空格相隔。
  • 定义值类型的常量是, const 位于类型名之前。
    推荐:
NSString * const Name = @"John";
const NSInteger TotalNum = 100;

运算符

  • 单目运算符如 !, & 等与运算对象之间无空格。
  • 双目运算符与运算对象之间以空格相隔。
  • 三目运算符 ? 应以 () 括起来。
    推荐:
    NSInteger result = (isTrue ? number1 : number2);
    
  • 如果一行出现过多的&&/||而需要分行时,&&/||置于每行行尾。
    推荐:
    if (self.count == another.count &&
        self.name == another.name &&
        self.title == another.name &&) {
    }
    

程序块

方法的大括号和其他的大括号(if/else/switch/while 等等)始终和声明在同一行开始,在新的一行结束。

推荐:

if (user.isHappy) {  
// Do something
} else {
// Do something else
}

方法头

  • 在方法签名中,在 -/+ 符号后应该有一个空格。
  • 返回值类型与方法名之间没有空格。

推荐:

- (void)setExampleText:(NSString *)text image:(UIImage *)image;

多参数方法

当一个方法包含多个参数时,可以考虑每行一个参数,以 : 对齐。

推荐:

[NSError errorWithName:
              withType:
              withCode:];

当方法名中存在名字片段过长或者过短而无法以 : 对齐时,建议从第二行开始每行缩进四个空格,左边对齐。

推荐:

[error short:
    keyName:
    lastLongKeyName:];

(), <>, {}, @[], @{}

  • (), <>, {} 与相邻的模块之间应该以空格分隔。

    推荐:

    if () {
    } else {
    }
    
    @interface NSObject <NSObject>
    

    以下几种情况除外:

  • 方法声明和定义。
    推荐:
```
- (instancetype)initWithImage:(UIImage *)image;
```
  • 强制类型转换。
    推荐:
```
NSInteger count = (NSInteger)5.0f;
NSString *tip = (__bridge_transfer NSString *)CFSTR("Hello World");
```
  • enum 和 bitmask 定义。
    推荐:
```
typedef NS_ENUM(NSUInteger, NSExpressionType)
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions)
```
  • (), <>, @[], @{} 与其包含的元素之间无空格。

    推荐:

    (void)
    id<Protocol1>
    ((x = y))
    @[@1]
    array[0]
    @{@“key”: @"value"}
    dictionary[@"key"]
    

    反对:

    ( void )
    id< Protocol1 >
    ( ( x = y ) )
    @[ @1 ]
    array[ 0 ]
    @{ @“key”: @"value" }
    dictionary[ @"key" ]
    
  • (), <>, @[], @{} 其内包含的元素之间以,空格相隔。

    推荐:

    (1, 2, 3)
    id<Protocol1, Protocol2>
    @[@1, @2, @3]
    @{@"k": @"v", @"m": @"n"}
    

    反对:

    (1,2,3)
    id<Protocol1,Protocol2>
    @[@1,@2,@3]
    @{@"k": @"v",@"m": @"n"}  
    
  • @{} 中键值应以:空格分隔。

    推荐:

    @{@"k": @"v"}
    

    反对:

    @{@"k":@"v"}
    
  • 如果@{}中包含多个键值对,则建议每行一个键值对且左对齐。

推荐:

@{
  @"k": @"v",
  @"m": @"n"
}

反对:

@{@"k": @"v", @"m": @"n"}
  • <> 中包含多个协议,可考虑分行。从次行开始每个协议名左对齐于第一个协议名。

    推荐:

    @protocol UITableViewDelegate <NSObject, 
                                   UIScrollViewDelegate>
    

冒号“: ”两边以空格相连。如下:

推荐:

@interface NSString : NSObject <NSCopying, NSMutableCopying, NSSecureCoding>

属性

按照原子性、可读性、拥有性、以及自定义 Accesor Method 方法顺序排列,默认值可省去。

推荐:

@property (nonatomic, readonly, copy, setter = setTitle) NSString *title;

命名

采用 camel-casing 命名法:将每个单词的首字母大写然后拼接起来。

基本原则

  • 清晰

    • 命名的清晰性高于简单和简洁,应在追求清晰的同时尽量保持简洁。
    • 尽量避免使用缩写,即使很长也要拼写出来。
    • 一些通用的缩写可以看情况使用。
    • 避免二义性。
  • 一致性
    尽量保持命名在整个程序内的一致性,主要包含两点:

    • 同一名字表示相同的意义。
    • 同样的概念使用同一名字表示,切忌同时使用多个名字。

前缀

  • 应用程序代码不建议使用前缀,当开发第三方使用的库时则应该使用前缀。
  • 协议名、函数名、常量名、以及枚举名应以其所关联的类名作为前缀。

推荐:

typedef NS_ENUM(NSInteger, UIScreenOverscanCompensation);

NSString *const UIScreenDidConnectNotification;

NSData *UIImagePNGRepresentation(UIImage *image);

@protocol UIAlertViewDelegate <NSObject>

类名通常应该由名词组成,并完全遵循 camel-casing

协议

协议名字通常根据其包含的方法而定,有以下几类:

  • 包含了一些相关的方法,通常作为一组类的接口,其命名方式为:操作 + ing。

    推荐:

    @protocol NSLocking
    
    - (void)lock;
    - (void)unlock;
    
    @end
             
    @protocol NSCopying
    
    - (id)copyWithZone:(NSZone *)zone;
    
    @end
    
  • 包含一组相关的方法,用于在代理对象和被代理对象之间传递数据,其命名方式为:类名 + DataSource。

    推荐:

    @protocol UITableViewDataSource <NSObject>
    
  • 包含一组相关的方法, 用于响应操作和控制程序流,其命名方式为:类名 + Delegate。

    推荐:

    @protocol UIAlertViewDelegate <NSObject>
    
    @optional
    
    - (void)alertView: clickedButtonAtIndex: ;
    - (BOOL)alertViewShouldEnableFirstOtherButton:;
    

变量

  • 变量名字采用 camel-casing 命名法且首字母小写。

  • 对于数组类型的变量,其命名方式为:名词/词组 + s。

    推荐:

    NSMutableArray *gestureRecognizers;
    

    反对:

    NSMutableArray *gestureRecognizerArray;
    
  • 对于字典类型的变量,其命名方式为:名词/词组 + s/Info。

    推荐:

    NSDictionary *fileAttributes;
    NSDictionary *userInfo;
    

    反对:

    NSDictionary *fileAttributeDictionary;
    NSDictionary *userDictionary;
    
  • 实例变量遵从变量的命名方式,且以 _ 为前缀。

属性

属性遵从变量的命名方式,如果属性名字是形容词,需指定 get 访问器。

推荐:

@property (nonatomic, readonly, getter = isPlayable) BOOL playable;

枚举

枚举常量的名字应以枚举变量的名字为前缀,其命名方式为:枚举类型 + 枚举状态。枚举类型的命名方式为:类名 + 名词/词组。

推荐:

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

通知

通知命名方式:[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification

推荐:

NSSystemTimeZoneDidChangeNotification
MPMediaLibraryDidChangeNotification
UIKeyboardWillShowNotification

异常

异常命名方式:[Name of associated class] + [UniquePartOfName] + Exception。

推荐:

NSRangeException
NSInvalidArgumentException

键指键值对中的键,用在字典中。其命名方式:[Name of associated class] + [UniquePartOfName] + Key。

推荐:

UIKeyboardFrameBeginUserInfoKey
ALAssetLibraryUpdatedAssetsKey

方法

  • 方法命名遵从变量的命名方式。

  • 对于表示动作的方法,以动词开头。

    不要使用 do 或者 does 等很少有意义的语气助词。另外,不要在动词前使用副词或形容词。

  • 对于返回对象属性的方法,建议直接使用属性作为方法名。

    反对使用 get 作为前缀,或者其他的动词,即使该方法并非直接返回结果,而是需要一些运算。存在多个返回值时,则

    推荐:

      - (NSSize)cellSize; 
    

    反对:

      - (NSSize)calcCellSize;
      - (NSSize)getCellSize;
    
  • 在所有参数前都要有关键字。

    推荐:

    - (void)sendAction:(SEL)aSelector toObject:(id)anObject;  
    

    反对:

    - (void)sendAction:(SEL)aSelector :(id)anObject;
    
  • 描述参数的词紧靠在参数之前。

    推荐:

    - (id)viewWithTag:(NSInteger)aTag;
    

    反对:

    - (id)taggedView:(int) aTag;
    
  • 针对 Cocoa 或 第三方库中的类所添加的类别中的方法,需为方法名添加前缀,基于项目名的缩写构成前缀,形如TT_。对于派生自他们的子类,如果担心添加的方法可能会跟基类中得方法名冲突,通用建议使用前缀方法名。

    考虑这样一个场景,在8.0发布之前你为 NSString 添加了一个类别,其中包含一个这样的方法 *containsString:(NSString )aString 。待到8.0发布时,苹果不幸地意识到确实需要一个这样的方法,在 NSString 中新增了同样的一个接口,这绝对是灾难性地!

其他

注释

当需要的时候,注释应该被用来解释 为什么 特定代码做了某些事情。所使用的任何注释必须保持最新否则就删除掉。

通常应该避免一大块注释,代码就应该尽量作为自身的文档。

Warning的要求

除了第三方库,不能引入新的警告

类前置声明

通常引用一个类有两种办法:一种是通过#import方式引入;另一种是通过@class引入;在头文件中通过@class引入这个类作为一个类型使用,减少头文件的重复包,提升编译效率。在实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类。

头文件声明:

@class StyleGuide;

StyleGuide *styleGuide;

实现文件引入头文件:

#import @"StyleGuilde.h"

#pragma mark分组

一些类(尤其是一些控制器类)可能很长,方法和函数弹出菜单可以便于代码导航。此时加入#pragma 指令对代码进行逻辑组织很有效果。提高可读性

例如:

#pragma mark - Initialization

私有方法属性定义

外部不需使用的属性方法成员变量不能暴露到.h文件。在类的.m文件中,采用类别来实现私有方法

例如:.m文件中定义

@interface MyClass()
  - (void)privateMethod;
@end

小贴士

点“.”语法

应该 始终 使用点“.”语法来访问或者修改属性,除此之外,不得使用点“.”语法。

推荐:

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

反对:

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

条件判断

条件判断主体部分应该始终使用大括号括住来防止[出错][Condiationals_1],即使它可以不用大括号(例如它只需要一行)。这些错误包括添加第二行(代码)并希望它是 if 语句的一部分时。还有另外一种[更危险的][Condiationals_2],当 if 语句里面的一行被注释掉,下一行就会在不经意间成为了这个 if 语句的一部分。此外,这种风格也更符合所有其他的条件判断,因此也更容易检查。

推荐:

if (isFinished) {
    return success;
}

反对:

if (isFinished)
    return success;

if (isFinished) return success;

三目运算符

三目运算符,? ,只有当它可以增加代码清晰度或整洁时才使用。单一的条件都应该优先考虑使用,多条件时通常使用 if 语句会更易懂。

推荐:

result = (a > b ? x : y);

反对:

result = (a > b ? x = c > d ? c : d : y);

错误处理

当引用一个返回错误参数(error parameter)的方法时,应该针对返回值,而非错误变量。一些苹果的 API 在成功的情况下会写一些垃圾值给错误参数(如果非空),所以针对错误变量可能会造成虚假结果。

推荐:

NSError *error;
if (![self trySomethingWithError:&error]) {
    // 处理错误
}

反对:

NSError *error;
[self trySomethingWithError:&error];
if (error != nil) {
    // 处理错误
}

init 和 dealloc

dealloc 方法应该放在@implementation的最上面,并且刚好在 @synthesize@dynamic 语句的后面。在任何类中,init 都应该直接放在 dealloc 方法的下面。

init 方法的结构应该像这样:

- (instancetype)init {
    self = [super init]; 
    if (self != nil) {
        // Custom initialization
    }

    return self;
}

字面量

每当创建 NSStringNSDictionaryNSArray,和 NSNumber 类的不可变实例时,应使用 @"", @{}, @[], @()方式生成实例。要注意 nil 值不能传给 NSArrayNSDictionary 字面量,这样做会导致崩溃。

推荐:

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 = [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 函数

当访问一个 CGRectxywidthheight 时,应该使用[CGGeometry 函数][CGRect-Functions_1]代替直接访问结构体成员。苹果的 CGGeometry 参考中说到:

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 frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);

反对:

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-Functions_1

应该尽量避免使用宏定义,除非没有别的选择,通常应当使用常量和枚举,它们具有更强的类型检查,而更加安全。

推荐:

static const CGFloat ThumbnailHeight = 50.0f;

反对:

#define ThumbnailHeight 2.0f

枚举类型

当使用 enum 时,建议使用新的基础类型规范,因为它具有更强的类型检查和代码补全功能。现在 SDK 包含了一个宏来鼓励使用使用新的基础类型 - NS_ENUM()

推荐:

typedef NS_ENUM(NSInteger, UIImageResizingMode) {
    UIImageResizingModeTile,
    UIImageResizingModeStretch,
};

位掩码

当用到位掩码时,使用 NS_OPTIONS 宏。

举例:

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

布尔

typedef signed char BOOL;
#define YES ((BOOL)1)
#define NO  ((BOOL)0)

以上代码片断来自objc.h。永远不将布尔变量直接和 YES 进行比较,因为 YES 被定义为 1,而 BOOL 可以多达 8 位。

if (isAwesome)
if (![someObject boolValue])

反对:

if ([someObject boolValue] == NO)
if (isAwesome == YES) 

单例

单例对象应该使用线程安全的模式创建共享的实例。

+ (instancetype)sharedInstance {
   static id sharedInstance = nil;

   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
      sharedInstance = [[self alloc] init];
   });

   return sharedInstance;
}

通用的缩写

Abbreviation Meaning and comments
alloc Allocate.
alt Alternate.
app Application.
calc Calculate.
dealloc Deallocate.
func Function.
horiz Horizontal.
info Information.
init Initialize.
int Integer.
max Maximum.
min Minimum.
msg Message.
nib Interface Builder archive.
pboard Pasteboard (but only in constants).
rect Rectangle.
Rep Representation.
temp Temporary.
vert Vertical.

以下是一些在计算机领域比较知名的缩写:

ASCII

PDF

XML

HTML

URL

RTF

HTTP

TIFF

JPG

PNG

GIF

LZW

ROM

RGB

CMYK

MIDI

FTP

参考文章

Cocoa 编码指南

Google Objective-C Style Guide

NYTimes Objective-C Style Guide

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容