一.总体原则
- 需求是暂时的,只有变化才是永恒的,面向变化编程,而不是面向需求编程。
- 不要过分追求技巧,降低程序的可读性
- 简洁的代码可以让bug无处藏身。要写出明显没有bug的代码,而不是没有明显bug的代码
- 先把眼前的问题解决掉,解决好,再考虑将来的扩展问题。
二.编码规范
1.if表达式写法
推荐:
if (!error) {
return success;
}
不推荐:
if (!error)
return success;
和:
if (!error) return success;
- 变量和常量比较
推荐:
[myValue isEqual:@42];
不推荐:
[@42 isEqual:myValue];
- nil和BOOL值检查
推荐:
if (someObject) { ...
if (![someObject boolValue]) { ...
if (!someObject) { ...
不推荐:
if (someObject == YES) { ... // Wrong
if (myRawValue == YES) { ... // Never do this.
if ([someObject boolValue] == NO) { ...
- 避免嵌套if语句,合理使用return可以避免增加代码复杂度,提高代码可读性。
推荐:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
// Do something important
}
不推荐:
- (void)someMethod {
if ([someOther boolValue]) {
// Do something important
}
}
2.三元运算符
三元运算符 ? 应该只用在它能让代码更加清楚的地方。 一个条件语句的所有的变量应该是已经被求值了的。
推荐:
result = a > b ? x : y;
不推荐:
result = a > b ? x = c > d ? c : d : y;
当三元运算符的第二个参数(if 分支)返回和条件语句中已经检查的对象一样的对象的时候,下面的表达方式更灵巧:
推荐:
result = object ? : [self createObject];
不推荐:
result = object ? object : [self createObject];
3.Case语句
除非编译器强制要求,括号在 case 语句里面是不必要的。但是当一个 case 包含了多行语句的时候,需要加上括号。
switch (condition) {
case 1:
// ...
break;
case 2: {
// ...
// 多行语句需要加上括号
break;
}
case 3:
// ...
break;
default:
// ...
break;
}
有时候可以使用 fall-through 在不同的 case 里面执行同一段代码。一个 fall-through 是指移除 case 语句的 “break” 然后让下面的 case 继续执行。
switch (condition) {
case 1:
case 2:
// code executed for values 1 and 2
break;
default:
// ...
break;
}
当在 switch 语句里面使用一个可枚举的变量的时候,default
是不必要的。比如:
switch (menuType) {
case ZOCEnumNone:
// ...
break;
case ZOCEnumValue1:
// ...
break;
case ZOCEnumValue2:
// ...
break;
}
4.函数
-
单一原则
一个函数只做一件事,每个函数的职责都应该划分的很明确。
推荐:
dataConfiguration()
viewConfiguration()
不推荐:
void dataConfiguration()
{
...
viewConfiguration()
}
- 需要对参数的正确性和有效性进行检查
- 对相同功能进行封装
- 将函数内部比较复杂的逻辑提取出来作为单独的函数
三.命名规范
1.统一要求
推荐使用长的、描述性的方法和变量名。尽可能遵守Apple命名约定
推荐:
UIButton *settingsButton;
不推荐:
UIButton *setBut;
2.类名
大驼峰式命名:每个单词的首字母都采用大写字母
例如:MainViewController
3.私有变量
- 小驼峰式命名:第一个单词以小写字母开始,后面的单词的首字母大写。
- 私有变量:以下划线开头 例如:
NSString *_peopleName
4.属性
- 小驼峰式命名:第一个单词以小写字母开始,后面的单词的首字母大写。
- 关键字顺序:原子性、读写权限、内存管理
- Block、NSString属性使用copy修饰
5.常量
-
常量应该以驼峰法命名,并以相关类名作为前缀。
推荐:
static const NSTimeInterval VAReportViewControllerFadeOutTime = 0.4;
不推荐:
static const NSTimeInterval fadeOutTime = 0.4;
- 推荐使用常量来代替字符串字面值和数字,这样能够方便复用,而且可以快速修改而不需要查找和替换。常量应该用
static
声明为静态常量,而不要用#define
,除非它明确的作为一个宏来使用。
推荐:
static const NSTimeInterval VAReportViewControllerFadeOutTime = 0.4;
static NSString * const VAReportViewControllerCellIdentifier = @"VAReportViewControllerCell";
不推荐:
#define VAReportViewControllerFadeOutTime 0.4
#define VAReportViewControllerCellIdentifier @"VAReportViewControllerCell"
对于外部可见的常量,在头文件中以这样的形式暴露给外部:
extern NSString *const VAReportViewControllerCellIdentifier;
并在实现文件中为其赋值。
只有公有的常量才需要添加命名空间作为前缀。尽管实现文件中私有常量的命名可以遵循另外一种模式,你仍旧可以遵循这个规则。
6.枚举
- 命名规则和类的命名一致。
- 枚举内容的命名需要以该枚举类型名称开头。
NS_ENUM 定义通用枚举 NS_OPTIONS 定义位移枚举
例如:
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone,
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
typedef NS_OPTIONS(NSUInteger, UIControlState) {
UIControlStateNormal = 0,
UIControlStateHighlighted = 1 << 0,
UIControlStateDisabled = 1 << 1,
UIControlStateSelected = 1 << 2
};
7.指定初始化方法和间接初始化方法
Objective-C 有指定初始化方法(Designated Initializer)和间接(Secondary Initializer)初始化方法的观念。designated 初始化方法是提供所有的参数,secondary 初始化方法是一个或多个,并且提供一个或者更多的默认参数来调用 designated 初始化的初始化方法。
-
在你希望提供你自己的初始化函数的时候,需要遵循下列原则:
- 定义你的 Designated Initializer,确保调用了直接父类的Designated Initializer。
- 重写直接父类的Designated Initializer。调用你的新的Designated Initializer。
- 为新的Designated Initializer 进行文档注释。
正确的例子:
#import
@interface VAMessageTableViewCell : UITableViewCell
- (__kindof VAMessageTableViewCell *)initWithName:(NSString *)name date:(NSDate *)date identifier:(NSString *)identifier VA_DESIGNATED_INITIALIZER;
- (__kindof VAMessageTableViewCell *)initWithName:(NSString *)name identifier:(NSString *)identifier;
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier VA_UNAVAILABLE_INITIALIZER;
- (instancetype)initWithCoder:(NSCoder *)aDecoder VA_UNAVAILABLE_INITIALIZER;
- (instancetype)init VA_UNAVAILABLE_INITIALIZER;
@end
#import "VAMessageTableViewCell.h"
@interface VAMessageTableViewCell ()
@property (nonatomic,copy) NSString *name;
@property (nonatomic,strong) NSDate *date;
@end
@implementation VAMessageTableViewCell
- (VAMessageTableViewCell *)initWithName:(NSString *)name date:(NSDate *)date identifier:(NSString *)identifier {
//调用直接父类的designated initializer
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
if (self) {
_name = name;
_date = date;
}
return self;
}
//Secondary Initializer
- (VAMessageTableViewCell *)initWithName:(NSString *)name identifier:(NSString *)identifier {
return [self initWithName:name date:nil identifier:identifier];
}
//重写直接父类的Designated Initializer
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
return [self initWithName:nil date:nil identifier:reuseIdentifier];
}
//重写直接父类的Designated Initializer
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
return [self initWithName:nil date:nil identifier:nil];
}
@end
其中VA_DESIGNATED_INITIALIZER
和VA_UNAVAILABLE_INITIALIZER
定义如下:
#define VA_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
#define VA_UNAVAILABLE_INITIALIZER __attribute__((unavailable("please use designated initializer")))
相关宏介绍:
__attribute__((objc_designated_initializer))
:用来修饰类的designated initializer初始化方法,如果修饰的方法里没有调用父类的 designated initializer,编译器会发出警告。
__attribute__((unavailable))
:可以用来修饰变量,方法,类和协议,表明不可用,如果使用,编译器会发出错误。同deprecated,可以添加说明。
8.方法
推荐:
- (__kindof People *)initWithName:(NSString *)name age:(NSInteger)age birthday:(NSDate *)birthday;
不推荐:
-(instancetype)initWithName:(NSString *)name andAge:(NSInteger)age andBirthday:(NSDate *)birthday;
建议所有返回类的实例的类方法和实例方法使用__kindof
,不要使用id
或者instancetype
,原因如下:
-
id修饰
- 用id修饰返回值类型,不会在编译时进行类型判断。
- 返回值类型没有确切提示。
-
instancetype修饰
虽然会自动识别当前对象的类,但是仍然没有类型提示。
9.通知
当你定义你自己的 NSNotification
的时候你应该把你的通知的名字定义为一个字符串常量,就像你暴露给其他类的其他字符串常量一样。你应该在公开的接口文件中将其声明为 extern
的, 并且在对应的实现文件里面定义。
因为你在头文件中暴露了符号,所以你应该按照统一的命名空间前缀法则,用类名前缀作为这个通知名字的前缀。
同时,用一个 Did/Will 这样的动词以及用 "Notifications" 后缀来命名这个通知也是一个好的实践。
// Foo.h
extern NSString * const ZOCFooDidBecomeBarNotification
// Foo.m
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";
四.注释规范
优秀的代码大部分是可以自描述的,我们完全可以用代码本身来表达它到底在干什么,而不需要注释的辅助。
但并不是说一定不能写注释,有以下三种情况比较适合写注释:
- 公共接口(注释要告诉阅读代码的人,当前类能实现什么功能)
- 涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
- 容易产生歧义的代码(但是严格来说,容易让人产生歧义的代码是不允许存在的)。
除了上述这三种情况,如果别人只能依靠注释才能读懂你的代码的时候,就要反思代码出现了什么问题。
1.import注释
如果有一个以上的import
语句,就对这些语句进行分组,每个分组的注释是可选的。
// Frameworks
#import ;
// Models
#import "NYTUser.h"
// Views
#import "NYTButton.h"
#import "NYTUserView.h"
2.属性注释
使用//
注释,在属性之后,用一个空格隔开。
@property (nonatomic,copy) NSString *name; //用户名
3.方法注释
使用Xcode快捷键command
+option
+/
进行注释:
/**
方法描述
@param name 参数描述
@param date 参数描述
@param identifier 参数描述
@return 返回值
*/
- (__kindof VAMessageTableViewCell *)initWithName:(NSString *)name date:(NSDate *)date identifier:(NSString *)identifier VA_DESIGNATED_INITIALIZER;
4.代码块注释
单行使用//
多行使用/**/
5.#pragma
-
#pragma mark -
是一个在类内部组织代码并且帮助你分组方法实现的好办法。分离示范:
#pragma mark - Get
#pragma mark - Set
#pragma mark - Life Cycle
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
}
#pragma mark - Super Class
#pragma mark - Event Responder
#pragma mark - TableView Delegate DataSource
6.TODO MARK FIXME 标记
//MARK:标记一下
//TODO:通知即将要做
//FIXME:你想要修改的bug
参考资料:
https://github.com/oa414/objc-zen-book-cn/
https://www.jianshu.com/p/21f059f04181
Coding Guidelines for Cocoa