这是一份编程规范,主要参考了raywenderlich、github和苹果官方的Objective-C编程规范。相关文档链接在文末给出。
这里是一些苹果官方描述oc编码规范的文档,如果有些规范这里没有提到,请参照这些文档:
代码应该使用英国英语。除了注释和字符串常量,不应该使用中文或拼音来命名标识符。
使用 #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 {}
{
必须跟函数名在同一行,右 }
必须使用单独的行。其他一些代码块 (if
/else
/switch
/while
etc.) 也一样。应该:
if (user.isHappy) {
//Do something
} else {
//Do something else
}
不应该:
if (user.isHappy)
{
//Do something
}
else
{
//Do something else
}
@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;
@end
Objective-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。
如果这里的代码风格不适合你,你可以尝试其他的一些编码风格