iOS代码规范 - OC版

Part1-1: Objective-C命名规范

一般性原则
  • 遵循大部分开发语言的一般性命名原则。
  • 方法名不要使用 new 作为前缀。
  • 驼峰命名规则,第一个单词的首字符小写。
  • 一般方法不使用前缀命名。
  • 私有方法可以使用统一的前缀来分组和辨识。
  • 表示对象行为的方法,名称以动词开头。

以动词开头的方法命名,标识对象的行为

1
- (void) selectTabViewItem:(NSTableViewItem *)tableViewItem

名称中不要出现
do或does,因为这些助动词没什么实际意义。也不要在动词前使用副词或形容词修饰。

  • 如果方法返回方法接收者的某个属性,直接用属性名称命名。不要使用 get,除非是间接返回一个或多个值。

推荐

1
- (NSSize) cellSize;

反对

1
- (NSSize) getCellSize;
  • 参数要用描述该参数的关键字命名

推荐

1
- (void) sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;

反对

1
- (void) sendAction:(SEL)aSelector  :(id)anObject  :(BOOL)flag;
  • 参数前面的单词要能描述该参数

推荐

1
- (id) viewWithTag:(int)aTag;

反对

1
- (id) taggedView:(int)aTag;
  • 细化基类中的已有方法:创建一个新方法,其名称是在被细化方法名称后面追加参数关键词
1
2
- (id)initWithFrame:(CGRect)frameRect;//NSView, UIView.
- (id)initWithFrame:(NSRect)frameRect mode:(int)aMode cellClass:(Class)factoryId numberOfRows:(int)rowsHigh numberOfColumns (int)colsWide;//NSMatrix, a subclass of NSView
  • 不要使用 and 来连接用属性作参数的关键字

推荐

1
- (int)runModalForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes;

反对

1
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;

虽然上面的例子中使用 add 看起来也不错,但当你方法有太多参数关键字时就有问题。

  • 如果方法描述两种独立的行为,使用 and 来串接它们
1
- (BOOL) openFile:(NSString *)fullPath withApplication:(NSString NSWorkspace *)appName andDeactivate:(BOOL)flag;//NSWorkspace.
访问方法

访问方法是对象属性的读取与设置方法。其命名有特定的格式依赖于属性的描述内容。

1
2
- (void) setNoun:(type)aNoun;
- (type) noun;

例如:

1
2
- (void) setgColor:(NSColor *)aColor;
- (NSColor *) color;
  • 如果属性是用形容词描述的,则命名格式为:
1
2
- (void) setAdjective:(BOOL)flag;
- (BOOL) isAdjective;

例如:

1
2
- (void) setEditable:(BOOL)flag;
- (BOOL) isEditable;
  • 如果属性是用动词描述的,则命名格式为:(动词要用现在时时态)
    1
    2
    - (void) setVerbObject:(BOOL)flag;
    - (BOOL) verbObject;
    例如:
1
2
- (void) setShowAlpha:(BOOL)flag;
- (BOOL) showsAlpha;
  • 不要使用动词的过去分词形式作形容词使用

推荐

1
2
3
- (void)setAcceptsGlyphInfo:(BOOL)flag;
- (BOOL)acceptsGlyphInfo;

反对

1
2
- (void)setGlyphInfoAccepted:(BOOL)flag;
- (BOOL)glyphInfoAccepted;
  • 可以使用情态动词(can, should, will 等)来提高清晰性,但不要使用 do 或 does

推荐

1
2
- (void) setCanHide:(BOOL)flag;             
- (BOOL) canHide;

反对

1
2
- (void) setDoseAcceptGlyphInfo:(BOOL)flag;
- (BOOL) doseAcceptGlyphInfo;
  • 只有在方法需要间接返回多个值的情况下,才使用 get
1
- (void) getLineDash:(float *)pattern count:(int *)count phase:(float *)phase;

像上面这样的方法,在其实现里应允许接受 NULL 作为其 in/out 参数,以表示调用者对一个或多个返回 值不感兴趣。

委托方法: 委托方法是那些在特定事件发生时可被对象调用,并声明在对象的委托类中的方法。它们有独特的命名约 定,这些命名约定同样也适用于对象的数据源方法。

  • 名称以标示发送消息的对象的类名开头,省略类名的前缀并小写类第一个字符
1
2
- (BOOL) tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
  • 冒号紧跟在类名之后(随后的那个参数表示委派的对象)。该规则不适用于只有一个 sender 参数的方法
1
- (BOOL) applicationOpenUntitledFile:(NSApplication *)sender;
  • 上面的那条规则也不适用于响应通知的方法。在这种情况下,方法的唯一参数表示通知对象
1
- (void) windowDidChangeScreen:(NSNotification *)notification;
  • 用于通知委托对象操作即将发生或已经发生的方法名中要使用 did 或 will
1
2
- (void) browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *) windowWillReturnUndoManager:(NSWindow *)window;

用于询问委托对象可否执行某操作的方法名中可使用 did 或 will,但最好使用 should

1
- (BOOL) windowShouldClose:(id)sender;
集合方法

管理对象(集合中的对象被称之为元素)的集合类,约定要具备如下形式的方法:

1
2
3
- (void) addElement:(elementType)adObj;
- (void) removeElement:(elementType)anObj;
- (NSArray *)elements;

例如:

1
2
3
- (void) addLayoutManager:(NSLayoutManager *)adObj;
- (void) removeLayoutManager:(NSLayoutManager *)anObj;
- (NSArray *)layoutManagers;

集合方法命名有如下一些限制和约定:

  • 如果集合中的元素无序,返回 NSSet,而不是 NSArray
  • 如果将元素插入指定位置的功能很重要,则需具备如下方法:
1
2
- (void) insertElement:(elementType)anObj atIndex:(int)index;
- (void) removeElementAtIndex:(int)index;

集合方法的实现要考虑如下细节:

  • 以上集合类方法通常负责管理元素的所有者关系,在 add 或 insert 的实现代码里会 retain 元素,在 remove 的实现代码中会 release 元素
  • 当被插入的对象需要持有指向集合对象的指针时,通常使用 set… 来命名其设置该指针的方法,且不 要 retain 集合对象。比如上面的 insertLayerManager:atIndex: 这种情形,NSLayoutManager 类使 用如下方法:
1
2
- (void) setTextStorage:(NSTextStorage *)textStorage;
- (NSTextStorage *)textStorage;

通常你不会直接调用 setTextStorage:,而是覆写它。
另一个关于集合约定的例子来自 NSWindow 类:

1
2
3
4
5
- (void) addChildWindow:(NSWindow *)childWin ordered:(NSWindowOrderingMode)place;
- (void) removeChildWindow:(NSWindow *)childWin;
- (NSArray *)childWindows;
- (NSWindow *) parentWindow;
- (void) setParentWindow:(NSWindow *)window;
方法参数

命名方法参数时要考虑如下规则:

  • 如同方法名,参数名小写第一个单词的首字符,大写后继单词的首字符。如:removeObject:(id)anObject
  • 不要在参数名中使用 pointer 或 ptr,让参数的类型来说明它是指针
  • 避免使用 one, two,…,作为参数名
  • 避免为节省几个字符而缩写

照 Cocoa 惯例,以下关键字与参数联合使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...action:(SEL)aSelector
..alignment:(int)mode
...atIndex:(int)index
...content:(NSRect)aRect
...doubleValue:(double)aDouble
...floatValue:(float)aFloat
...font:(NSFont *)fontObj
...frame:(NSRect)frameRect
...intValue:(int)anInt
...keyEquivalent:(NSString *)charCode
...length:(int)numBytes
...point:(NSPoint)aPoint
...stringValue:(NSString *)aString
...tag:(int)anInt
...target:(id)anObject
...title:(NSString *)aString
私有方法

大多数情况下,私有方法命名相同与公共方法命名约定相同,但通常我们约定给私有方法添加前缀,以便 与公共方法区分开来。即使这样,私有方法的名称很容易导致特别的问题。当你设计一个继承自 Cocoa framework 某个类的子类时,你无法知道你的私有方法是否不小心覆盖了框架中基类的同名方法。

Cocoa framework 的私有方法名称通常以下划线作为前缀(如:_fooData),以标示其私有属性。基于这 样的事实,遵循以下两条建议:

  • 不要使用下划线作为你自己的私有方法名称的前缀,Apple 保留这种用法。
  • 若要继承 Cocoa framework 中一个超大的类(如:NSView),并且想要使你的私有方法名称与基类中的区别开来,你可以为你的私有方法名称添加你自己的前缀。这个前缀应该具有唯一性, 建议用”p_Method”格式,p代表private。

尽管为私有方法名称添加前缀的建议与前面类中方法命名的约定冲突,这里的意图有所不同:为了防止不 小心地覆盖基类中的私有方法。

Part1-2: Objective-C语法规范

点语法

应该 始终 使用点语法来访问或者修改属性,访问其他实例时首选括号。

推荐:

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

反对

1
2
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
间距
  • 一个缩进使用 4 个空格,永远不要使用制表符(tab)缩进。请确保在 Xcode 中设置了此偏好。
  • 方法的大括号和其他的大括号(if/else/switch/while 等等)始终和声明在同一行开始,在新的一行结束。

推荐:

1
2
3
4
5
6
7

if (user.isHappy) {
// Do something
}
else {
// Do something else
}
  • 方法之间应该正好空一行,这有助于视觉清晰度和代码组织性。在方法中的功能块之间应该使用空白分开,但往往可能应该创建一个新的方法。
  • @synthesize@dynamic 在实现中每个都应该占一个新行。
条件判断

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

推荐:

1
2
3
if (!error) {
return success;
}

反对:

1
2
if (!error)
return success;

1
if (!error) return success;
三目运算符

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

推荐:

1
result = a > b ? x : y;

反对:

1
result = a > b ? x = c > d ? c : d : y;
错误处理

当引用一个返回错误参数(error parameter)的方法时,应该针对返回值,而非错误变量。

推荐:

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

反对:

1
2
3
4
5
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// 处理错误
}

一些苹果的 API 在成功的情况下会写一些垃圾值给错误参数(如果非空),所以针对错误变量可能会造成虚假结果(以及接下来的崩溃)。

方法

在方法签名中,在 -/+ 符号后应该有一个空格。方法片段之间也应该有一个空格。

推荐:

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

变量名应该尽可能命名为描述性的。除了 for() 循环外,其他情况都应该避免使用单字母的变量名。 星号表示指针属于变量,例如:NSString *text 不要写成 NSString* text 或者 NSString * text ,常量除外。 尽量定义属性来代替直接使用实例变量。除了初始化方法(init, initWithCoder:,等)dealloc 方法和自定义的 setters getters 内部,应避免直接访问实例变量。更多有关在初始化方法和 dealloc 方法中使用访问器方法的信息,参见这里

推荐:

1
2
3
@interface NYTSection: NSObject
@property (nonatomic) NSString *headline;
@end

反对:

1
2
3
@interface NYTSection : NSObject {
NSString *headline;
}
变量限定符

当涉及到在 ARC 中被引入变量限定符时, 限定符 (__strong, __weak, __unsafe_unretained, __autoreleasing) 应该位于星号和变量名之间,如:NSString * __weak text。

命名

尽可能遵守苹果的命名约定,尤其那些涉及到内存管理规则NARC的。

长的和描述性的方法名和变量名都不错。

推荐:

1
UIButton *settingsButton;

反对:

1
UIButton *setBut;

类名和常量应该始终使用三个字母的前缀(例如 NYT),但 Core Data 实体名称可以省略。为了代码清晰,常量应该使用相关类的名字作为前缀并使用驼峰命名法。

推荐:

1
static const NSTimeInterval NYTArticleViewControllerNavigationFadeAnimationDuration = 0.3;

反对:

1
static const NSTimeInterval fadetime = 1.7;

属性和局部变量应该使用驼峰命名法并且首字母小写。

为了保持一致,实例变量应该使用驼峰命名法命名,并且首字母小写,以下划线为前缀。这与 LLVM 自动合成的实例变量相一致。 如果 LLVM 可以自动合成变量,那就让它自动合成。

推荐:

1
@synthesize descriptiveVariableName = _descriptiveVariableName;

反对:

1
id varnm;
注释

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

通常应该避免一大块注释,代码就应该尽量作为自身的文档,只需要隔几行写几句说明。这并不适用于那些用来生成文档的注释。

init dealloc

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

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

1
2
3
4
5
6
7
- (instancetype)init {
self = [super init]; // 或者调用指定的初始化方法
if (self) {
// Custom initialization
}
return self;
}
字面量

每当创建 NSString NSDictionaryNSArray,和 NSNumber 类的不可变实例时,都应该使用字面量。要注意 nil 值不能传给NSArrayNSDictionary 字面量,这样做会导致崩溃。

推荐:

1
2
3
4
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;

反对:

1
2
3
4
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 函数

当访问一个 CGRect 的 x, y, width, height 时,应该使用CGGeometry 函数代替直接访问结构体成员。苹果的 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.

推荐:

1
2
3
4
5
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);

反对:

1
2
3
4
5
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;
常量

常量首选内联字符串字面量或数字,因为常量可以轻易重用并且可以快速改变而不需要查找和替换。常量应该声明为 static 常量而不是 #define ,除非非常明确地要当做宏来使用。

推荐:

1
2
static NSString * const NYTAboutViewControllerCompanyName = @"The New York Times Company";
static const CGFloat NYTImageThumbnailHeight = 50.0;

反对:

1
2
#define CompanyName @"The New York Times Company"
#define thumbnailHeight 2
枚举类型

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

推荐:

1
2
3
4
typedef NS_ENUM(NSInteger, NYTAdRequestState) {
NYTAdRequestStateInactive,
NYTAdRequestStateLoading
};
位掩码

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

举例:

1
2
3
4
5
6
typedef NS_OPTIONS(NSUInteger, NYTAdCategory) {
NYTAdCategoryAutos = 1 << 0,
NYTAdCategoryJobs = 1 << 1,
NYTAdCategoryRealState = 1 << 2,
NYTAdCategoryTechnology = 1 << 3
};
私有属性

私有属性应该声明在类实现文件的延展(匿名的类目)中。

推荐:

1
2
3
4
5
6
7
8
@interface NYTAdvertisement ()

@property (nonatomic, strong) GADBannerView *googleAdView;
@property (nonatomic, strong) ADBannerView *iAdView;
@property (nonatomic, strong) UIWebView *adXWebView;

@end

图片命名

图片名称应该被统一命名以保持组织的完整。它们应该被命名为一个说明它们用途的驼峰式字符串,其次是自定义类或属性的无前缀名字(如果有的话),然后进一步说明颜色 和/或 展示位置,最后是它们的状态。

推荐:

1
2
RefreshBarButtonItem / RefreshBarButtonItem@2x 和 RefreshBarButtonItemSelected / RefreshBarButtonItemSelected@2x
ArticleNavigationBarWhite / ArticleNavigationBarWhite@2x 和 ArticleNavigationBarBlackSelected / ArticleNavigationBarBlackSelected@2x.

图片目录中被用于类似目的的图片应归入各自的组中。

布尔

因为 nil 解析为 NO,所以没有必要在条件中与它进行比较。永远不要直接和 YES 进行比较,因为 YES 被定义为 1,而 BOOL 可以多达 8 位。

这使得整个文件有更多的一致性和更大的视觉清晰度。

推荐:

1
2
if (!someObject) {
}

反对:

1
2
if (someObject == nil) {
}

对于 BOOL 来说, 这有两种用法:

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

反对:

1
2
if ([someObject boolValue] == NO)
if (isAwesome == YES) // 永远别这么做

如果一个 BOOL 属性名称是一个形容词,属性可以省略“is”前缀,但为 get 访问器指定一个惯用的名字,例如:

1
@property (assign, getter=isEditable) BOOL editable;

内容和例子来自 Cocoa 命名指南 。

单例

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

1
2
3
4
5
6
7
8
9
10
+ (instancetype)sharedInstance {
static id sharedInstance = nil;

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

return sharedInstance;
}

这将会预防有时可能产生的许多崩溃。

导入

如果有一个以上的 import 语句,就对这些语句进行分组。每个分组的注释是可选的。 注:对于模块使用 @import 语法。

1
2
3
4
5
6
7
8
9
// Frameworks
@import QuartzCore;

// Models
#import "NYTUser.h"

// Views
#import "NYTButton.h"
#import "NYTUserView.h"

Part1-3: 代码注释规范

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

通常应该避免一大块注释,代码就应该尽量作为自身的文档,只需要隔几行写几句说明。这并不适用于那些用来生成文档的注释。

#####文件注释
采用Xcode自动生成的注释格式,修改部分参数:

1
2
3
4
5
6
7
//
// AppDelegate.m
// oc code good
//
// Created by roycms on 16-10-30.
// Copyright (c) 2016 roycms.cn LLC. All rights reserved.
//

其中项目名称创建人公司版权需要填写正确。

import注释

如果有一个以上的 import 语句,就对这些语句进行[分组][Import_1]。每个分组的注释是可选的。
注:对于模块使用 [@import][Import_2] 语法。

1
2
3
4
5
6
7
8
9
// Frameworks
@import QuartzCore;

// Models
#import "NYTUser.h"

// Views
#import "NYTButton.h"
#import "NYTUserView.h"
方法注释

采用javadoc的格式,可以使用XCode插件VVDocumenter-Xcode快速添加,只需输入///即可

1
2
3
4
5
6
7
8
9
10
11
/**
* 功能描述
*
* @param tableView 参数说明
* @param section 参数说明
*
* @return 返回值说明
*/
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
return [self.familyNames objectAtIndex:section];
}
代码块注释

单行的用//+空格开头,多汗的采用/* */注释 ##TODO注释 TODO 很不错, 有时候, 注释确实是为了标记一些未完成的或完成的不尽如人意的地方, 这样一搜索, 就知道还有哪些活要干, 日志都省了。

格式://TODO:说明

1
2
3
4
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
//TODO:增加初始化
return YES;
}

更多规范

  • 切图规范

转载

iOSCodeSpecification