1.条件语句
1.1 Yonda表达式
名字起源于星球大战中Yonda大师的讲话方式,总是用倒装的语序
Yonda表达式是指:拿一个常量去跟一个变量比较而不是拿变量去跟常量比较。它就像是在表达 “蓝色是不是天空的颜色” 或者 “高个是不是这个男人的属性” 而不是 “天空是不是蓝的” 或者 “这个男人是不是高个子的”
推荐:
if ([myValue isEqual:@42]) {...
不推荐:
if ([@42 isEqual:myValue]) {...
1.2 nil 和 BOOL检查
检查对象是否为nil推荐使用:
if (nil == myValue) {...
而不是:
if (myValue == nil) {...
如果不小心敲错成这样:
if (myValue = nil) {...
这是合法的语句,很难调试出错误,如果把 nil 放在左边,因为它不能被赋值,所以就不会发生这样的错误。
为了避免这些奇怪的问题,可以使用 !作为运算符,因为 nil 是解释到 NO。
NSString *testString = nil;
if (testString) {
NSLog(@"the testString is not nil");
}
if (!testString) {
NSLog(@"the testString is nil");
}
OUTPUT:the testString is nil
同时不要直接把它和 YES 比较,原因参考链接:https://www.jianshu.com/p/bb654c76a50d
1.3 黄金大道
将代码的重要部分放在黄金大道上,不要放在if分支里
推荐:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
// Do something important
}
不推荐:
- (void)someMethod {
if ([someOther boolValue]) {
// Do something important
}
}
1.4 复杂的表达式
当你有一个复杂的if (condition) 语句的时候,你应该把condition提取出来赋给一个BOOL变量,让每个if语句的意义和逻辑体现出来。
推荐:
BOOL namecontainSwift = [sessionName containsString: @"Swift"];
BOOL isCurrentYear = [sessionDateComponent year] == 2014;
BOOL isSWiftSession = namecontainSwift && isCurrentYear;
if (isSWiftSession) {
// Do something here
}
把条件提取成一个变量,而不是将一个复杂的表达式放在if后面。
2.命名
2.1Constant常量
推荐使用常量来代替字符串字面值和数字,这样能够方便服用,而且可以快速修改而不需要查找和替换。
常量应该用 static 声明为静态常量,而不要用 #define,除非它明确的作为一个宏来使用
推荐:
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
不推荐:
#define CompanyName @"Apple Inc."
#define magicNumber 42
常量应该在头文件中以这样的形式暴露给外部,并在实现文件中为它赋值
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
2.2方法命名
方法名与方法类型 (-/+ 符号)之间应该以空格间隔,尽可能少用“and”这个词,它不应该用来阐明有多个参数。
推荐:
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
不推荐:
- (instancetype)initWith:(int)width and:(int)height; // Never do this.
2.3字面值
使用字面值来创建不可变的NSString, NSDictionary, NSArray 和 NSNumber对象。注意不要将 nil 传进NSArray 和 NSDictionary里,因为这样会导致崩溃。
推荐:
NSArray *names = @[@“Amy”, @“Matt”, @"Chris”];
NSDictionary *PM = @{@“iphone”:@“Kate”, @“ipad”: @“Kamal”};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @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 *buildingZIPCode = [NSNumber numberWithInteger:10018];
3.Case语句
当在switch语句里使用一个可枚举的变量时,default是不必要的。比如:
switch (menuType) {
case ZOCEnumNone:
// ...
break;
case ZOCEnumValue1:
// ...
break;
case ZOCEnumValue2:
// ...
break;
}
4.类
4.1类名
类名应该以三个大写字母作为前缀(双字母前缀为Apple的类预留)
另一个好的类的命名规范:当你创建一个子类的时候,你应该把说明性的部分放在前缀和父类名的在中间。
举个例子:如果你有一个 ZOCNetworkClient 类,子类的名字会是ZOCTwitterNetworkClient (注意 "Twitter" 在 "ZOC" 和 "NetworkClient" 之间); 按照这个约定, 一个UIViewController 的子类会是 ZOCTimelineViewController.
4.2Initializer 和 dealloc 初始化
将dealloc方法放在实现文件的最前面(直接在@synthesize以及@dynamic之后)
init应该在dealloc方法后面。如果有多个初始化方法,指定初始化方法(designated initializer)应该放在最前面,间接初始化方法(secondary initializer)跟在后面,这样更有逻辑性。
如今有了ARC,dealloc方法几乎不需要实现,不过把init和dealloc放在一起可以从视觉上强调它们是一对的。通常,在init方法中做的事情需要在dealloc方法中撤销。
init方法应该是这样的结构:
- (instancetype)init {
self = [super init]; // call the designated initializer
if (self) {
// Custom initialization
}
return self;
}
为什么设置 self 为 [super init] 的返回值,以及中间发生了什么呢?这是一个十分有趣的话题。
我们退一步讲:我们常常写 [[NSObject alloc] init] 这样的代码,从而淡化了 alloc 和 init 的区别。Objective-C 的这个特性叫做 两步创建 。 这意味着申请分配内存和初始化被分离成两步,alloc 和 init。
alloc 负责创建对象,这个过程包括分配足够的内存来保存对象,写入 isa 指针,初始化引用计数,以及重置所有实例变量。
init 负责初始化对象,这意味着使对象处于可用状态。这通常意味着为对象的实例变量赋予合理有用的值。
alloc 方法将返回一个有效的未初始化的对象实例。对这个实例发送的消息会被转换成一次objc_msgSend()函数的调用,形参self的实参是alloc返回的指针;这样self在所有方法的作用域内都能够被访问。按照惯例,为了完成两步创建,实例第一个被调用的方法将是init方法。注意,NSObject 在实现init时,只是简单返回了self。
关于init的约定还有一个重要的组成部分,这个方法可以通过返回nil来告诉调用者,初始化失败了;这样我们就能理解为什么总是要调用self = [super init],如果你的父类说自己初始化失败了,[suepr init]就会返回nil,self = nil, 那么你必须假定你正处于一个不稳定的状态,因此在你的实现里不要继续你的初始化并且也返回nil。如果不这样做,你可能会在你的实现里操作一个不可用的对象,它的行为时不可预测的,最终可能会导致你的程序崩溃。
Designated 和 Secondary 初始化方法
designated初始化方法是提供所有的参数,secondary初始化方法可以存在一个或者多个,并且提供一个或者更多的默认参数来调用designated初始化方法。
#import
#import
@implementation ZOCEvent
*// designated 初始化方法*
- (instancetype)initWithTitle:(NSString *)title
date:(NSDate *)date
location:(CLLocation *)loaction{
self = [super init];
if (self) {
_title = title;
_date = date;
_location = location;
}
return self;
}
// 以下两个都是secondary初始化方法
- (instancetype)initWithTitle:(NSString *)title
date:(NSDate *)date {
return [self initWithTitle:title date:date location:nil];
}
- (instancetype)initWithTitle:(NSString *)title
{
return [self initWithTitle:title date:[NSDate date] location:nil];
}
@end
Designated Initializer
一个类应该只有一个designated初始化方法,其他的初始化方法应该调用这个designated的初始化方法。
在类继承中调用任何designated初始化方法都是合法的,而且应该保证所有的 designated initiazlier 都是从祖先开始往下调用的,也就是第一个执行的是最远的祖先,然后从顶向下的类继承,保证你的祖先都有机会执行他们特定的初始化代码。
当定义一个新类的时候有三个不同的方式:
不需要重载任何初始化函数
重载 designated initiazlier
定义一个新的 designated initializer
第一个方案:不需要增加类的任何初始化逻辑,只需要依照父类的designated initiazlier
第二个方案:当你需要提供额外的初始化逻辑时,你可以重载 designated initializer,
一个典型的例子是你创造UIViewController子类的时候重载initWithNibName:bundle:方法。
@implementation ZOCViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
// call to the superclass designated initializer
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization (自定义的初始化过程)
}
return self;
}
@end
第三个方案:在你希望提供你自己的初始化函数的时候,你应该遵守着三个步骤:
定义你的designated initializer,确保调用了直接超类的 designated initializer
重载直接超类的 designated initializer。调用你的新的designated initializer.
为新的designated initializer写文档
正确实现的例子:
@implementation ZOCNewsViewController
*//定义你的 designated initializer*
- (instancetype)initWithNews:(ZOCNews *)news
{
*// (调用直接超类的 designated initializer)*
self = [super initWithNibName:nil bundle:nil];
if (self) {
_news = news;
}
return self;
}
//(重载直接父类的 designated initializer)
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
* // 调用你的新的designated initializer*
return [self initWithNews:nil];
}
@end
Cocoa充满了约定,这些约定可以帮助编辑器变得更加聪明。无论编译器是否遭遇alloc或者init方法,它会知道,即使返回类型都是id,这些方法总是返回接受到的类类型的实例。根据clang的定义,id可以被编译器提升到instancetype。在alloc或者init中,强烈建议对所有返回类的实例的类方法和实例方法使用intancetype类型。
单例
如果可能,请尽量避免使用单例而是依赖注入。如果一定要使用,请使用一个线程安全的模式来创建共享的实例。对于GCG,用dispatch_once()函数就OK。
不推荐:
+ (instancetype)sharedInstance
{
static id sharedInstance;
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[MyClass alloc] init];
}
}
return sharedInstance;
}
推荐:
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
dispatch_once() 的优点是,它更快,而且语法上更干净,因为disptach_once()的意思就是“把一些东西执行一次”.
4.3属性
点符号
当使用 setter getter 方法的时候尽量使用点符号。应该总是用点符号来访问以及设置属性。
推荐:
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
不推荐:
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
使用点符号会让表达更加清晰并且帮助区分属性访问和方法调用
属性定义@property (nonatomic, readwrite, copy) NSString *name;
属性的参数应该按照下面的顺序排列: 原子性,读写 和 内存管理。 这样做你的属性更容易修改正确,并且更好阅读。(译者注:习惯上修改某个属性的修饰符时,一般从属性名从右向左搜索需要修动的修饰符。最可能从最右边开始修改这些属性的修饰符,根据经验这些修饰符被修改的可能性从高到底应为:内存管理 > 读写权限 >原子操作)
4.4方法
永远不要在你的私有方法前加上 _ 前缀。这个前缀是 Apple 保留的。不要冒重载苹果的私有方法的险。
4.5相等性
当你要实现相等性的时候记住这个约定:你需要同时实现isEqual 和 hash方法。如果两个对象是被isEqual认为相等的,它们的 hash 方法需要返回一样的值。但是如果 hash 返回一样的值,并不能确保他们相等。
5.Categories
6.Protocols
区分delegate和data source
7.NSNotification
当你定义自己的NSNotification的时候你应该把你的通知的名字定义为一个字符串常量,就像你暴露给其他类的其他字符串常量一样。你应该在公开的接口文件中将其声明为extern的,并且在对应的实现文件里面定义。
因为你在头文件中暴露了符合,所以你应该按照统一的命名空间前缀法则,用类名前缀作为这个通知名字的前缀。
同时,用一个 Did/Will 这样的动词以及用 "Notifications" 后缀来命名这个通知也是一个好的实践。
// Foo.h
extern NSString * const ZOCFooDidBecomeBarNotification
// Foo.m
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";
8.美化代码
8.1缩进使用4个空格,永远不要使用tab
推荐:
if (user.isHappy) {
//Do something
}
else {
//Do something else
}
不推荐:
if (user.isHappy)
{
//Do something
} else {
//Do something else
}
8.2应该总是让冒号对齐
有一些方法签名可能超过三个冒号,用冒号对齐可以让代码更具有可读性。即使有代码块存在,也应该用冒号对齐方法。
推荐:
[UIView animateWithDuration:1.0
animations:^{
// something
}
completion:^(BOOL finished) {
// something
}];
不推荐:
[UIView animateWithDuration:1.0 animations:^{
// something
} completion:^(BOOL finished) {
// something
}];
9.代码组织
9.1利用代码块
一个 GCC 非常模糊的特性,以及 Clang 也有的特性是,代码块如果在闭合的圆括号内的话,会返回最后语句的值
不推荐:
NSString *urlString = [NSString stringWithFormat:@"%@/%@", baseURLString, endpoint];
NSURL *url = [NSURL URLWithString:urlString];
推荐:
NSURL *url = ({
NSString *urlString = [NSString stringWithFormat:@"%@/%@", baseURLString, endpoint];
[NSURL URLWithString:urlString];
});
这样做的好处:
特别适合组织小块的代码
给了读者一个重要的入口并且减少相关干扰
所有的变量都在代码块中,也就是只在代码块的区域有效,可以减少对其他作用域的命名污染
9.2Pragma
建议使用#pragma mark - 来分离:
不同功能组的方法
protocols的实现
对父类方法的重写
大多数 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 unused():
- (NSInteger)giveMeFive
{
NSString *foo;
#pragma unused (foo)
return 5;
}
9.3自己为暂时无法解决的错误和有可能出现的错误添加提示
// error 提醒,红色
- (NSInteger)divide:(NSInteger)dividend by:(NSInteger)divisor
{
#error Whoa, buddy, you need to check for zero here!
return (dividend / divisor);
}
// warning 提醒,黄色
- (float)divide:(float)dividend by:(float)divisor
{
#warning Dude, don't compare floating point numbers like this!
if (divisor != 0.0) {
return (dividend / divisor);
}
else {
return NAN;
}
}
10.对象间的通信
10.1 Block
10.2 委托和数据源