这是一份编程规范,主要参考了raywenderlich、github和苹果官方的Objective-C编程规范。相关文档链接在文末给出。
这里是一些苹果官方描述oc编码规范的文档,如果有些规范这里没有提到,请参照这些文档:
- The Objective-C Programming Language
- Cocoa Fundamentals Guide
- Coding Guidelines for Cocoa
- iOS App Programming Guide
- 语言
- 代码组织
- 对齐
- 注释
- 命名
- 函数
- 变量
- 属性的修饰
- .语法
- subscript和literals
- 常量
- 枚举类型
- switch-case
- 私有属性
- 布尔值
- 条件
- 三元运算符
- 初始化方法
- 类构造方法
- CGRect相关方法
- 黄金路径
- Error处理
- 单例模式
- 换行
- Xcode工程
- 其他代码风格
代码应该使用英国英语。除了注释和字符串常量,不应该使用中文或拼音来命名标识符。
使用 #pragma mark - 来对一个oc文件中的代码进行分组,一般使用如下的框架把重要的分组放在前面位置:
#pragma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Public
- (void)publicMethod {}
#pragma mark - Private
- (void)privateMethod {}
#pragma mark - Draw
- (void)drawRect:(CGRect)rect {}
#pragma mark - Events
- (IBAction)submitData:(id)sender {}
- (void)onBtnClicked:(id)sender {}
#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - Properties
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
#pragma mark - NSObject
- (NSString *)description {}- 使用4个空格来进行代码的缩进。不要使用tab来缩进,因为它在不同的编辑器下可能显示不同的效果,可以在Xcode中设置一个tab自动转换为4个空格。
- 函数的定义中,左
{必须跟函数名在同一行,右}必须使用单独的行。其他一些代码块 (if/else/switch/whileetc.) 也一样。
应该:
if (user.isHappy) {
//Do something
} else {
//Do something else
}不应该:
if (user.isHappy)
{
//Do something
}
else
{
//Do something else
}-
函数之间要有且只有一个空行。函数内部按业务功能用空行进行分隔。
-
应该使用自动synthesis,如果必须使用
@synthesize和@dynamic,它们应该放在@implementation后的前几行。 -
每完成一个函数的定义,尽量使用Xcode的Re-indent的功能进行代码自动缩进。方法为: 选中整个函数,右键->Restruct->Re-Indent。
-
注:我们的代码风格基本上与Mozilla的风格一样,只是把2个字符对齐改成4个字节对齐。 大家可以使用ClangFormat这款Xcode插件进行代码风格统一。我们基于Mozilla主要进行如下调整:
---
Language: Cpp
BasedOnStyle: Mozilla
AccessModifierOffset: -2
DerivePointerAlignment: false
PointerAlignment: Right
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
IndentCaseLabels: false
IndentWidth: 4
ObjCBlockIndentWidth: 4
TabWidth: 4
UseTab: Never
- 代码应该尽量做到清晰可读,做到自解释,而不用加很多的注释。
- 当需要注释时,注释应该更多地用来描述 为什么这样做。
- 注释保持更新,历史的注释应该删除。
- 除了用来产生文档的一些注释(函数声明等),其他注释应该使用行注释,不应该使用块注释。
- 可能存在问题的代码应该加上
// FIXME:的注释,并详细描述(注意用半角的冒号)。 - 需要添加逻辑的代码应该加上
// TODO:,并详细描述。 - 不是源码文件创建者修改了代码,应该在修改的代码上加上
// Modified:,并写明修改人、修改时间、修改原因等信息。
必须尽量遵守苹果官方的命名规范,特别是跟memory management rules (NARC).相关的。
应该使用长的、描述清楚的函数名、变量名。
应该:
UIButton *settingsButton;不应该:
UIButton *setBut;应该使用三个字母的前缀来给类、常量进行命名。
常量应该使用驼峰命名法,并用给它加上相关类或模块的前缀。
应该:
static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;不应该:
static NSTimeInterval const fadetime = 1.7;属性名应该也用驼峰命名法,第一个单词要小写。属性不要手动添加 @synthesize,而是让Xcode自动synthesize。
应该:
@property (strong, nonatomic) NSString *descriptiveVariableName;不应该:
id varnm;使用属性时,成员变量应该使用通过self. 进行访问。有个例外是,在初始化函数中,应该使用_variableName 的方式进行访问。
临时变量不应该加下划线。
在函数签名中(函数声明或定义),在函数类型(-/+符号)后,必须有一个空格。参数之间也要有一个空格。参数的keyword不要为空。除了第一个参数,后面的参数的keyword尽量不要加with、and等连词的前缀,keyword更不应该直接用with、and。
应该:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;不应该:
-(void)setT:(NSString *)text i:(UIImage *)image;
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
- (id)taggedView:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height; // Never do this.变量名应该描述尽量清楚。除了for() 中,不应使用单个字母的变量。
除了常量的定义,变量定义中的* 号,应该紧挨变量,* 号前面加空格,如,NSString *text 而不是 NSString* text 或 NSString * text。
尽量使用私有属性来代替成员变量,因为它更可读。
不应该直接访问变量,而应该用属性去访问,除了在初始化函数(init, initWithCoder:, etc…),dealloc 或 setter和getter中。
应该:
@interface RWTTutorial : NSObject
@property (strong, nonatomic) NSString *tutorialName;
@end不应该:
@interface RWTTutorial : NSObject {
NSString *tutorialName;
}属性的修饰,必须显式地给出。顺序应该是weak/strong/copy后,再nonatomic,因为这与Interface Builder生成的属性是顺序是一致的。
应该:
@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) NSString *tutorialName;不应该:
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic) NSString *tutorialName;拥有可变版本的类型(如 NSString有NSMutableString,NSArray有NSMutableArray) 应该使用copy而不是用strong修饰。这样可以避免用户实际传入mutable版的变量(注意,mutalbe类型的变量千万不能加copy修饰,因为这样这个变量copy后就是不可改变类型,但是却声明为可变类型,修改该变量往往会产生异常)。
应该:
@property (copy, nonatomic) NSString *tutorialName;不应该:
@property (strong, nonatomic) NSString *tutorialName;
@property (copy, nonatomic) NSMutableString *name; // 千万不能这样写objc2.0开始引入.语法,即可以使用self.propertyName 的方式来引用属性。虽然你也可以通过setter和getter函数来访问属性对应的变量,但是还是应该使用.语法。
应该:
view.backgroundColor = [UIColor orangeColor];不应该:
[view setBackgroundColor:[UIColor orangeColor]];NSString, NSDictionary, NSArray, 和 NSNumber 等应该尽量使用subscript和literals。如:
应该:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @10018;
NSString *name = names[1];不应该:
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 *buildingStreetNumber = [NSNumber numberWithInteger:10018];
NSString *name = [names objectAtIndex:1];常量应该尽量用static 语法来写,不应该使用#define 。
应该:
static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";
static CGFloat const RWTImageThumbnailHeight = 50.0;不应该:
#define CompanyName @"RayWenderlich.com"
#define thumbnailHeight 2应该使用新的NS_ENUM 语法,而不应该使用旧的enum 语法,因为新的语法有更强的类型检查和代码补全。
For Example:
typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {
RWTLeftMenuTopItemMain,
RWTLeftMenuTopItemShows,
RWTLeftMenuTopItemSchedule
};你仍然可以显式地给类型赋值:
typedef NS_ENUM(NSInteger, RWTGlobalConstants) {
RWTPinSizeMin = 1,
RWTPinSizeMax = 5,
RWTPinCountMin = 100,
RWTPinCountMax = 500,
};不应该再使用的enum 定义,除非你在写c语言。
不应该:
enum GlobalConstants {
kMaxPinSize = 5,
kMaxPinCount = 500,
};switch-case中,如果一个case包含多行,加{} 否则不加。
switch (condition) {
case 1:
// ...
break;
case 2: {
// ...
// Multi-line example using braces
break;
}
case 3:
// ...
break;
default:
// ...
break;
}
如果分支没有break的话,要加一行fall-through的注释,表明下面的code是通用的。
switch (condition) {
case 1:
// fall-through!
case 2:
// code executed for values 1 and 2
break;
default:
// ...
break;
}私有属性应该放在.m文件中。私有属性放在匿名的categories中,如下:
For Example:
@interface RWTDetailViewController ()
@property (strong, nonatomic) GADBannerView *googleAdView;
@property (strong, nonatomic) ADBannerView *iAdView;
@property (strong, nonatomic) UIWebView *adXWebView;
@endObjective-C使用YES 和 NO来表示布尔值。所以true 和 false 除了CoreFoundation和C、C++代码中,不应该被使用。因为nil 被判断为NO,所以没有必要与nil 进行比较。因为BOOL值可以最大可以达到8位,而YES只是被设置为1,所以尽量不要跟YES 比较。
应该:
if (someObject) {}
if (![anotherObject boolValue]) {}不应该:
if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}
if (isAwesome == YES) {} // Never do this.
if (isAwesome == true) {} // Never do this.如果布尔类型的属性名是一个形容词,那么属性虽然可以忽略is 前缀,但最好仍提供带is 前缀的getter方法,方便调用:
@property (assign, getter=isEditable) BOOL editable;条件的body应该使用括号,即使body代码只有一行。
应该:
if (!error) {
return success;
}不应该:
if (!error)
return success;或
if (!error) return success;三元运算符, ?: , 应该只在它能提高代码整洁性而且逻辑清晰的情况下使用。判断条件应该只有一个,多于一个的判断条件应该用if或临时变量来写。
应该:
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)init {
self = [super init];
if (self) {
// ...
}
return self;
}返回值应该使用instancetype 来代替之前的id。
类构造方法与上面初始化方法类似,返回值应该也使用instancetype 而不是id。
@interface Airplane
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;
@end当访问 CGRect 中的x, y, width, 和 height时,应该使用
CGGeometry functions 来代替直接的结构体访问。
应该:
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 };当if判断有多层嵌套,应该尽量使用黄金路径,即不符合条件的分支先返回,这样可以避免分支嵌套。
应该:
- (void)someMethod {
if (!condition1) {
return;
}
//Do something1
if (!condition2) {
return;
}
// Do something 2
}不应该:
- (void)someMethod {
if (condition1) {
//Do something1
if (condition2) {
// Do something 2
}
}
}当函数返回NSError的引用,调用时要根据返回值进行判断,而不是NSError的值,因为苹果的一些api会往NSError中写一些垃圾数据,如果根据NSError进行判断可能会有问题。
应该:
NSError *error;
if (![self trySomethingWithError:&error]) {
// Handle Error
}不应该:
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// Handle Error
}单例模式应该使用dispatch_once来保证创建时的线程安全。
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}换行是比较重要的,它直接影响到程序的可读性和美观。
For example:
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];这种很长的代码,要做分行。后面几行,用4个字符进行对齐。
self.productsRequest = [[SKProductsRequest alloc]
initWithProductIdentifiers:productIdentifiers];物理文件应该跟Xcode工程中的文件同步。Xcode工程中的分组应该对应到文件系统的物理目录。 尽可能在target的编译设置里面把"Treat Warnings as Errors"打开,而且打开更多的错误警告。如果你确定没有问题,可以使用pragma的ignore来忽略一些警告,Clang's pragma feature。
如果这里的代码风格不适合你,你可以尝试其他的一些编码风格