不规范的代码,读起来身心疲惫,改起来后患无穷
iOS成员编写代码我们应该遵循以下规范:
1. 语言
首选
UIColor *myColor = [UIColor whiteColor];
不可选
UIColor *myColour = [UIColor whiteColor];
2. 代码组织
-
使用#pragma mark -
分类方法在功能分组和协议/委托实现这一总体结构。
#pragma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - IBActions
- (IBAction)submitData:(id)sender {}
#pragma mark - Public
- (void)publicMethod {}
#pragma mark - Private
- (void)privateMethod {}
#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
#pragma mark - NSObject
- (NSString *)description {}
3. 间距
-
使用2空格缩进,不要用tabs进行缩进,一定要在Xcode
的preference
中设置
-
方法括号和其他括号(if/else/switch/while 等)
,开始括号一定要在相同行展开而且关闭括号在新行关闭
首选
if (user.isHappy) {
//Do something
} else {
//Do something else
}
不可选
if (user.isHappy)
{
//Do something
}
else {
//Do something else
}
4. 注释
-
当某些代码需要的时候,就需要有注释去说明这些代码做了什么功能,但当代码有更新的时候注释也需要及时去更新
-
避免一整块的注释,因为代码应该是保持可读性比较高,而只需要一段精简的注释,但是这些注释并不适用于文档的编写
5. 如何命名
-
苹果官方的命名规范我们应该时刻遵守,特别是那些涉及到内存管理方面的规则(NARC)
-
方法名和变量名应该精准地描述其含义
首选
UIButton *settingsButton;
不可选
UIButton *setBut;
-
常量应该“骆峰式”单词大写和相关的类名称前缀的清晰度。
首选
static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;
不可选
static NSTimeInterval const fadetime = 1.7;
-
属性应该驼峰-大小写。使用属性而不是手动auto-synthesis @ synthesize
语句,除非你有充分的理由。
首选
@property (strong, nonatomic) NSString *descriptiveVariableName;
不可选
id varnm;
6. 强调
-
在使用属性时,应该始终使用self
访问和修改实例变量,这意味着所有属性在视觉上都是不同的,因为它们都以self
开头。
-
这里有一个例外:在初始化器内部,应该直接使用 _ 来调用实例变量(即_variableName
),以避免getter /setter
的任何潜在副作用。局部变量不应该包含下划线。
7. 方法
-
在方法实现中,方法类型(-/+符号)后面应该有一个空格。方法段之前应该有一个空格(跟苹果官方风格保持一致)。必须使用包含一个关键字描述该方法段在该方法里面所起到的作用。
-
单词“and”
的用法有所保留,他不应该用于多个参数,如initWithWidth:height: example
首选
- (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.
8. 变量
-
变量应尽可能用描述性的方式命名,除了for()
循环,应该避免使用单字母变量名
-
应该正确规范地命名变量,例如NSString *text
,而不是NSString* text
或者NSString * text
,除非是定义常量
-
应该尽可能使用私有属性来替代实例变量,虽然实例变量是一种有效的命名方式。但是团队通过统一使用属性,则代码风格更加一致
- 除了初始化方法
(init、initWithCoder)、dealloc
方法和自定义setter
和getter
方法中,应该避免直接访问“back”
属性的实例变量。有关初始化方法的更多信息,请参考这里
首选
@interface RWTTutorial : NSObject
@property (strong, nonatomic) NSString *tutorialName;
@end
不可选
@interface RWTTutorial : NSObject {
NSString *tutorialName;
}
9. Property
属性
-
Property
属性应该明确地列出来,这将有助于新程序员阅读代码。属性地顺序应该是存储他们地原子性,这与从Interface Builder
连接UI元素时自动生成地代码保持一致
首选
@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) NSString *tutorialName;
不可选
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic) NSString *tutorialName;
-
对具有可变地属性(例如NSString
)应该更倾向于copy
而不是strong
。为什么?即使你声明了一个属性NSString
,有人有可能会传入一个NSMutableString
地实例,然后在你没有注意到地状况下改变它
首选
@property (copy, nonatomic) NSString *tutorialName;
不可选
@property (strong, nonatomic) NSString *tutorialName;
10. 点 语法的调用
-
点 语法纯粹是一个方便的访问器方法调用包装器。在使用点语法时,仍然调用了getter/setter
方法访问或更改属性,请参考这里
-
点符号应该用于访问和修改属性,因为它使代码更加简洁。在所有其他的实例中,括号表示法是首选的
首选
NSInteger arrayCount = [self.array count];
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
不可选
NSInteger arrayCount = self.array.count;
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
11.不可变实例的赋值
-
在创建这些不可变实例时,应该使用NSString、NSDictionary、NSArray和NSNumber
。特别注意,nil
只不能传递到NSArray
和NSDictionary
中,因为这会导致崩溃
首选
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @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 *buildingStreetNumber = [NSNumber numberWithInteger:10018];
12.常量
-
常量比代码内字符串或者其他的表达方式更受欢迎,因为他们可以方便地复制常用变量,而且不需要查找和替换就可以快速更改。常量应该声明为静态变量,而不是用#
定义,除非显示地用作宏
首选
static NSString * const DeliCompanyAddress = @"www.delicloud.com";
static CGFloat const RWTImageThumbnailHeight = 50.0;
不可选
#define DeliCompanyAddress @"www.delicloud.com"
#define thumbnailHeight 2
13.枚举类型
-
在使用枚举时,建议使用新的固定的底层类型规范,因为它具有更强的类型检查和代码完成能力。Apple SDK提供了包含一个宏来促进和鼓励使用固定的底层类型:NS_ENUM()
举个例子:
typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {
RWTLeftMenuTopItemMain,
RWTLeftMenuTopItemShows,
RWTLeftMenuTopItemSchedule
};
-
您还可以进行显示值赋值(比较老的k-风格常量定义)
typedef NS_ENUM(NSInteger, RWTGlobalConstants) {
RWTPinSizeMin = 1,
RWTPinSizeMax = 5,
RWTPinCountMin = 100,
RWTPinCountMax = 500,
};
-
应该避免使用旧的k-风格常量定义,除非编写CoreFoundation C代码(一般不会用到)
不可选
enum GlobalConstants {
kMaxPinSize = 5,
kMaxPinCount = 500,
};
14.各种类型的判断(Switch
)
-
括号在类型判断中不是必须的,除非编译器强制需要。当我们编写代码超过单行时,此时括号应该需要加上
switch (condition) {
case 1:
// ...
break;
case 2: {
// ...
// 多行需要用到括号
break;
}
case 3:
// ...
break;
default:
// ...
break;
}
-
有时相同的代码可以用于多种情况,并且应该需要一个默认情况。如果情况相同则移除break
,从而允许执行流传递给下一个case
,为了代码的清晰性,应该注释 移除break
的部分
switch (condition) {
case 1:
// ** 注释移除`break`的原因 **
case 2:
// code executed for values 1 and 2
break;
default:
// ...
break;
}
-
当为switch
使用枚举类型时,不需要default
。例如:
RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;
switch (menuType) {
case RWTLeftMenuTopItemMain:
// ...
break;
case RWTLeftMenuTopItemShows:
// ...
break;
case RWTLeftMenuTopItemSchedule:
// ...
break;
}
15. 私有属性
-
私有属性应该在类的实现文件中的类扩展(匿名类别)中声明。命名类别(如RWTPrivate或private)除非扩展另一个类,否则永远不应该使用。可以共享/公开匿名类别,以便使用+Private进行测试
举个例子:
@interface RWTDetailViewController ()
@property (strong, nonatomic) GADBannerView *googleAdView;
@property (strong, nonatomic) ADBannerView *iAdView;
@property (strong, nonatomic) UIWebView *adXWebView;
@end
16. Booleans
-
Objective-C
使用YES
和NO
。因此,true
和false
只能用于CoreFoundation、C或c++
代码。因为nil
解为NO
,所以没有必要在条件下进行比较。永远不要直接比较YES
,因为YES
的定义是1,BOOL
最多可以是8位。
-
通过这种统一,可以使文件之间有更强的一致性和更大的视觉清晰度
首选
if (someObject) {}
if (![anotherObject boolValue]) {}
不可选
if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}
if (isAwesome == YES) {} // Never do this.
if (isAwesome == true) {} // Never do this.
-
如果BOOL
属性的名称表示为形容词,则该属性可以省略“is”
前缀,但指定get访问器的常规名称,例如:
@property (assign, getter=isEditable) BOOL editable;
17. 判断条件if
-
判断条件应该始终使用大括号,即使在没有大括号(例如只有一行代码)的情况下,也应该使用大括号来防止错误。这些错误包括添加第二行并期望它成为if-statement
的一部分。另一个更危险的缺陷可能发生在if-statement
中的“内部”行被注释掉,而下一行不自觉地成为if-statement
的一部分。此外,这种样式更符合所有其他条件,因此更容易扫描
首选
if (!error) {
return success;
}
不可选
if (!error)
return success;
或
if (!error) return success;
18. 三元运算符
-
三元运算符,?:
,它的作用为增加清晰性或者使代码整洁。一个条件通常是所有情况都需要评估的。评估多个条件通常使用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;
19. 初始化方法
-
init
方法应该遵循Apple生成的代码模版提供的约定。还应该使用instancetype
的返回类型而不是id
模版
- (instancetype)init {
self = [super init];
if (self) {
// ...
}
return self;
}
-
有关instancetype
文章的链接,请参阅下方:类构造函数方法
20. 类的构造函数方法
-
在使用类构造函数方法时,这些方法应该总是返回instancetype
的类型,而不是id
。这确保编译器正确地推断结果类型
举个例子:
@interface Airplane
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;
@end
21. CGRect
功能
-
当访问CGRect
的x、y
,宽度或高度时,总是使用CGGeometry
函数而不是直接访问struct
成员,来自苹果的CGGeometry
参考:
在计算矩形的结果之前,将CGRect
数据结构作为输入的所有函数都隐式地标准化了这些矩形。因此,应用程序应该避免直接读写存储在CGRect
数据结构重的数据。相反,使用这里描述的函数来操作矩形并检索他们的特征
首选
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 };
22. 黄金路线
-
在有条件的情况下编码时,代码应该是“黄金”或“快乐”路径。也就是说,不要嵌套if语句,多个返回(return
)语句都可以
首选
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
//Do something important
}
不可选
- (void)someMethod {
if ([someOther boolValue]) {
//Do something important
}
}
23. 黄金路线
-
当方法通过引用错误参数时,请打开返回值,而不是错误变量
首选
NSError *error;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
不可选
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// Handle Error
}
-
在成功的情况下,Apple的一些api会将垃圾值写入错误参数(如果非null),因此,打开错误可能会导致判断否定(然后导致崩溃)
24. 单例
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
25. 换行符
-
换行符是一个重要的主题,因为这个样式决定打印log的情况以及代码的可读性控制
例如
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
-
像这样的一行长代码应该按照这个样式空格分开继续写到第二行
self.productsRequest = [[SKProductsRequest alloc]
initWithProductIdentifiers:productIdentifiers];
26. 换行符
-
笑脸是raywenderlich.com网站一个非常突出的风格特征!有一个正确的微笑是非常重要的,它意味着巨大的快乐和兴奋的编码主题。使用方括号是因为它代表了能够使用ascii
艺术捕获的最大的微笑。如果使用了结束括号,则表示半心半意的微笑,因此不受欢迎
首选
:]
不可选
:)
27. Xcode工程
-
物理文件应该与Xcode项目文件保持同步,以避免文件混乱。创建Xcode组都应该由文件系统中的文件夹反映。代码不仅应该按类型分组,还应该按特性分组,以获得更清晰的内容
-
在可能的情况下,打开目标构建设置中的“将警告视为错误”,并启用尽可能多的附加警告。如果需要忽略特定的警告,请使用Clang的pragma特性
其他Objective-C风格指南
谷歌代码规范