先手围棋项目代码编写标准(iOS)
文件夹命名
采取英文命名(中文解释其含义),遵循驼峰原则,首字母大写的规范
例:
GoodCourse(好课)
文件夹下面遵循
MVC
原则建立子文件夹,首字母大写
GoodCourse(好课)
>Controller
>Model
>View
大模块下子文件按照功能划分文件夹,大模块下不建议放
MVC
,建议放子功能文件夹
Studio(工作室)
>BonusActivities(奖金活动)
>MedicalAssociation(我的医社)
1.类的命名
类名应该以三个大写字母作为前缀(双字母前缀为
Apple
的类预留)。尽管这个规范看起来有些古怪,但是这样做可以减少Objective-C
没有命名空间所带来的问题。
一些开发者在定义模型对象时并不遵循这个规范(对于
Core Data
对象,我们更应该遵循这个规范)。我们建议在定义Core Data
对象时严格遵循这个约定,因为最终你可能需要把你的Managed Object Model
(托管对象模型)与其他(第三方库)的MOMs(Managed Object Model)
合并。
你可能注意到了,这本书里类的前缀(不仅仅是类,也包括公开的常量、
Protocol
等的前缀)是ZOC
。
另一个好的类的命名规范:当你创建一个子类的时候,你应该把说明性的部分放在前缀和父类名的在中间。
举个例子:如果你有一个
ZOCNetworkClient
类,子类的名字会是ZOCTwitterNetworkClient
(注意"Twitter"
在"ZOC"
和"NetworkClient"
之间); 按照这个约定, 一个UIViewController
的子类会是ZOCTimelineViewController
.
2.通用的约定
尽可能遵守 Apple 的命名约定,尤其是和 内存管理规则 (NARC) 相关的地方。
推荐使用长的、描述性的方法和变量名。
推荐:
UIButton *settingsButton;
不推荐:
UIButton *setBut;
3.常量
常量应该以驼峰法命名,并以相关类名作为前缀
推荐:
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
不推荐:
static const NSTimeInterval fadeOutTime = 0.4;
推荐使用常量来代替字符串字面值和数字,这样能够方便复用,而且可以快速修改而不需要查找和替换。常量应该用
static
声明为静态常量,而不要用#define
,除非它明确的作为一个宏来使用。
推荐:
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
不推荐:
#define CompanyName @"Apple Inc."
#define magicNumber 42
4.属性
属性应该尽可能描述性地命名,避免缩写,并且是小写字母开头的驼峰命名。我们的工具可以很方便地帮我们自动补全所有东西(嗯。。几乎所有的,
Xcode
的Derived Data
会索引这些命名)。所以没理由少打几个字符了,并且最好尽可能在你源码里表达更多东西。
推荐:
NSString *text;
不推荐:
NSString* text;
NSString * text;
5.点符号
当使用
setter getter
方法的时候尽量使用点符号。应该总是用点符号来访问以及设置属性。
推荐:
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
不推荐:
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
使用点符号会让表达更加清晰并且帮助区分属性访问和方法调用
6.属性定义
推荐按照下面的格式来定义属性
@property (nonatomic, readwrite, copy) NSString *name;
属性的参数应该按照下面的顺序排列:原子性,读写 和 内存管理。 这样做你的属性更容易修改正确,并且更好阅读。(译者注:习惯上修改某个属性的修饰符时,一般从属性名从右向左搜索需要修动的修饰符。最可能从最右边开始修改这些属性的修饰符,根据经验这些修饰符被修改的可能性从高到底应为:内存管理 > 读写权限 >原子操作)
你必须使用
nonatomic
,除非特别需要的情况。在iOS
中,atomic
带来的锁特别影响性能。
属性可以存储一个代码块。为了让它存活到定义的块的结束,必须使用
copy
(block
最早在栈里面创建,使用copy
让block
拷贝到堆里面去)
为了完成一个共有的
getter
和一个私有的setter
,你应该声明公开的属性为readonly
并且在类扩展中重新定义通用的属性为readwrite
的。
// .h文件中
@interface MyClass : NSObject
@property (nonatomic, readonly, strong) NSObject *object;
@end
// .m文件中
@interface MyClass ()
@property (nonatomic, readwrite, strong) NSObject *object;
@end
@implementation MyClass
// Do Something cool
@end
描述
BOOL
属性的词如果是形容词,那么setter
不应该带is
前缀,但它对应的getter
访问器应该带上这个前缀,如:
@property (assign, getter=isEditable) BOOL editable;
在实现文件中应避免使用
@synthesize
,因为Xcode
已经自动为你添加了
7.私有属性
私有属性应该定义在类的实现文件的类的扩展 (匿名的
category
) 中。不允许在有名字的category
(如ZOCPrivate
)中定义私有属性,除非你扩展其他类。
例子:
@interface ZOCViewController ()
@property (nonatomic, strong) UIView *bannerView;
@end
8.可变对象
任何可以用一个可变的对象设置的((比如
NSString
,NSArray
,NSURLReques
))属性的内存管理类型必须是copy
的。
9.私有方法
永远不要在你的私有方法前加上
_
前缀。这个前缀是Apple
保留的。不要冒重写苹果的私有方法的险。
10.Categories
虽然我们知道这样写很丑, 但是我们应该要在我们的
category
方法前加上自己的小写前缀以及下划线,比如- (id)zoc_myCategoryMethod
。 这种实践同样被苹果推荐。
这是非常必要的。因为如果在扩展的
category
或者其他category
里面已经使用了同样的方法名,会导致不可预计的后果。实际上,实际被调用的是最后被加载的那个category
中方法的实现(译者注:如果导入的多个category
中有一些同名的方法导入到类里时,最终调用哪个是由编译时的加载顺序来决定的,最后一个加载进来的方法会覆盖之前的方法)。
如果想要确认你的分类方法没有覆盖其他实现的话,可以把环境变量
OBJC_PRINT_REPLACED_METHODS
设置为YES
,这样那些被取代的方法名字会打印到Console
中。现在LLVM 5.1
不会为此发出任何警告和错误提示,所以自己小心不要在分类中重写方法。
一个好的实践是在
category
名中使用前缀。
推荐:
@interface NSDate (ZOCTimeExtensions)
- (NSString *)zoc_timeAgoShort;
@end
不推荐:
@interface NSDate (ZOCTimeExtensions)
- (NSString *)timeAgoShort;
@end
11.NSNotification
当你定义你自己的
NSNotification
的时候你应该把你的通知的名字定义为一个字符串常量,就像你暴露给其他类的其他字符串常量一样。你应该在公开的接口文件中将其声明为extern
的, 并且在对应的实现文件里面定义。
因为你在头文件中暴露了符号,所以你应该按照统一的命名空间前缀法则,用类名前缀作为这个通知名字的前缀。
同时,用一个
Did/Will
这样的动词以及用"Notifications"
后缀来命名这个通知也是一个好的实践。
// Foo.h
extern NSString * const ZOCFooDidBecomeBarNotification
// Foo.m
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";
12.美化代码
空格
缩进使用
4
个空格。 永远不要使用tab
, 确保你在Xcode
的设置里面是这样设置的。
方法的大括号和其他的大括号(if
/else
/switch
/while
等) 总是在同一行开始,在新起一行结束。
推荐:
if (user.isHappy) {
// Do something
}
else {
// Do something else
}
不推荐:
if (user.isHappy)
{
// Do something
} else {
// Do something else
}
- 方法之间应该要有一个空行来帮助代码看起来清晰且有组织。 方法内的空格应该用来分离功能,但是通常不同的功能应该用新的方法来定义。
- 优先使用
auto-synthesis
。但是如果必要的话,@synthesize and
@dynamic
- 在实现文件中的声明应该新起一行。
- 应该总是让冒号对齐。有一些方法签名可能超过三个冒号,用冒号对齐可以让代码更具有可读性。即使有代码块存在,也应该用冒号对齐方法。
[UIView animateWithDuration:1.0
animations:^{
// something
}
completion:^(BOOL finished) {
// something
}];
不推荐:
[UIView animateWithDuration:1.0 animations:^{
// something
} completion:^(BOOL finished) {
// something
}];
13.代码组织
来自 Mattt Thompson
code organization is a matter of hygiene (代码组织是卫生问题)
我们十分赞成这句话。清晰地组织代码和规范地进行定义, 是你对自己以及其他阅读代码的人的尊重。
利用代码块
一个 GCC 非常模糊的特性,以及 Clang 也有的特性是,代码块如果在闭合的圆括号内的话,会返回最后语句的值
NSURL *url = ({
NSString *urlString = [NSString stringWithFormat:@"%@/%@", baseURLString, endpoint];
[NSURL URLWithString:urlString];
});
Pragma
Pragma Mark
#pragma mark -
是一个在类内部组织代码并且帮助你分组方法实现的好办法。 我们建议使用 #pragma mark -
来分离:
- 不同功能组的方法
- protocols 的实现
- 对父类方法的重写
- (void)dealloc { /* ... */ }
- (instancetype)init { /* ... */ }
#pragma mark - View Lifecycle (View 的生命周期)
- (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)zoc_privateMethod { /* ... */ }
#pragma mark - UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ }
#pragma mark - ZOCSuperclass
// ... 重写来自 ZOCSuperclass 的方法
#pragma mark - NSObject
- (NSString *)description { /* ... */ }
上面的标记能明显分离和组织代码。你还可以用
cmd+Click
来快速跳转到符号定义地方。 但是小心,即使paragma mark
是一门手艺,但是它不是让你类里面方法数量增加的一个理由:类里面有太多方法说明类做了太多事情,需要考虑重构了。
关于 pragma
在 http://raptureinvenice.com/pragmas-arent-just-for-marks/ 有很好的关于
pragma
的讨论了,在这边我们再做部分说明。
大多数
iOS
开发者平时并没有和很多编译器选项打交道。一些选项是对控制严格检查(或者不检查)你的代码或者错误的。有时候,你想要用pragma
直接产生一个异常,临时打断编译器的行为。
当你使用
ARC
的时候,编译器帮你插入了内存管理相关的调用。但是这样可能产生一些烦人的事情。比如你使用NSSelectorFromString
来动态地产生一个 selector 调用的时候,ARC不知道这个方法是哪个并且不知道应该用那种内存管理方法,你会被提示performSelector may cause a leak because its selector is unknown(执行 selector 可能导致泄漏,因为这个 selector 是未知的)
.
如果你知道你的代码不会导致内存泄露,你可以通过加入这些代码忽略这些警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[myObj performSelector:mySelector withObject:name];
#pragma clang diagnostic pop
注意我们是如何在相关代码上下文中用
pragma
停用-Warc-performSelector-leaks
检查的。这确保我们没有全局禁用。如果全局禁用,可能会导致错误。
全部的选项可以在 The Clang User's Manual 找到并且学习。