0x00
不要嵌套 if
语句,使用多个 return
可以避免增加循环的复杂度,并提高代码的可读性。
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
//Do something important
}
0x01
当三元运算符的第二个参数(if
分支)返回和条件语句中已经检查的对象一样的对象的时候,下面的表达方式更灵巧:
result = object ? : [self createObject];
0x02
有些方法通通过参数返回 error
的引用,使用这样的方法时应当检查方法的返回值,而非 error
的引用。
NSError *error = nil;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
0x03
推荐使用常量来代替字符串字面值和数字,这样能够方便复用,而且可以快速修改而不需要查找和替换。常量应该用 static
声明为静态常量,而不要用 #define
,除非它明确的作为一个宏来使用。
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
0x04
使用一个线程安全的模式来创建共享的实例。
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
0x05
在你希望提供你自己的初始化函数的时候,你应该遵守三个步骤来保证正确的行为:
- 定义你的
designated initializer
,确保调用了直接超类的designated initializer
- 重载直接超类的
designated initializer
。调用你的新的designated initializer
- 为新的
designated initializer
写文档。
0x06
在文档中明确哪一个初始化方法是 designated
的,可以用编译器的指令 __attribute__((objc_designated_initializer))
来标记意图。
0x07
应该总是使用 setter
和 getter
方法访问属性,除了 init
和 dealloc
方法。
0x08
总应该用 getter
和 setter
,因为:
- 使用
setter
会遵守定义的内存管理语义(strong
,weak
,copy etc...
) ,这个在ARC
之前就是相关的内容。举个例子,copy
属性定义了每个时候你用setter
并且传送数据的时候,它会复制数据而不用额外的操作。 -
KVO
通知(willChangeValueForKey
,didChangeValueForKey
) 会被自动执行。 - 更容易
debug
:你可以设置一个断点在属性声明上并且断点会在每次getter / setter
方法调用的时候执行,或者你可以在自己的自定义setter/getter
设置断点。 - 允许在一个单独的地方为设置值添加额外的逻辑。
0x09
应该倾向于用 getter
:
- 它是对未来的变化有扩展能力的(比如,属性是自动生成的)。
- 它允许子类化。
- 更简单的
debug
(比如,允许拿出一个断点在getter
方法里面,并且看谁访问了特别的getter
- 它让意图更加清晰和明确:通过访问
ivar _anIvar
你可以明确的访问self->_anIvar
.这可能导致问题。在block
里面访问ivar
(你捕捉并且retain
了self
,即使你没有明确的看到self
关键词)。 - 它自动产生
KVO
通知。 - 在消息发送的时候增加的开销是微不足道的。
0x0A
永远不要在 init
方法(以及其他初始化方法)里面用 getter
和 setter
方法,应当直接访问实例变量。
0x0B
同样在 dealloc
方法中同样需要被注意。
0x0C
任何可以用来用一个可变的对象设置的(比如 NSString
, NSArray
, NSURLRequest
)属性的的内存管理类型必须是 copy
的。
0x0D
懒加载,当实例化一个对象需要耗费很多资源,或者配置一次就要调用很多配置相关的方法而你又不想弄乱这些方法时,我们需要重写 getter
方法以延迟实例化,而不是在 init
方法里给对象分配内存。通常这种操作使用下面这样的模板:
- (NSDateFormatter *)dateFormatter {
if (!_dateFormatter) {
_dateFormatter = [[NSDateFormatter alloc] init];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[_dateFormatter setLocale:enUSPOSIXLocale];
[_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS"];//毫秒是SSS,而非SSSSS
}
return _dateFormatter;
}
0x0E
参数断言,你的方法可能要求一些参数来满足特定的条件(比如不能为 nil
),在这种情况下啊最好使用 NSParameterAssert()
来断言条件是否成立或是抛出一个异常。
0x0F
当你要实现相等性的时候记住这个约定:你需要同时实现 isEqual
和 hash
方法。如果两个对象是被 isEqual
认为相等的,它们的 hash
方法需要返回一样的值。但是如果 hash
返回一样的值,并不能确保他们相等。
0x10
应该在 category
方法前加上自己的小写前缀以及下划线,比如 - (id)zoc_myCategoryMethod
。
0x11
当实现一个 protocol
应该坚持里氏替换原则。这个原则是:你应该可以取代任意接口(也就是 Objective-C
里的的 "protocol
")实现,而不用改变客户端或者相关实现。
0x12
当定义自己的 NSNotification
的时候应该把通知的名字定义为一个字符串常量,就像暴露给其他类的其他字符串常量一样。应该在公开的接口文件中将其声明为 extern
的, 并且在对应的实现文件里面定义。
0x13
用一个 Did/Will
这样的动词以及用 "Notifications
" 后缀来命名这个通知也是一个好的实践。
// Foo.h
extern NSString * const ZOCFooDidBecomeBarNotification
// Foo.m
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";
0x14
利用代码块,一个 GCC
非常模糊的特性,以及 Clang
也有的特性是,代码块如果在闭合的圆括号内的话,会返回最后语句的值
NSURL *url = ({
NSString *urlString = [NSString stringWithFormat:@"%@/%@", baseURLString, endpoint];
[NSURL URLWithString:urlString];
});
0x15
这个特性非常适合组织小块的代码,通常是设置一个类。他给了读者一个重要的入口并且减少相关干扰,能让读者聚焦于关键的变量和函数中。此外,这个方法有一个优点,所有的变量都在代码块中,也就是只在代码块的区域中有效,这意味着可以减少对其他作用域的命名污染。
0x16
#pragma mark -
是一个在类内部组织代码并且帮助你分组方法实现的好办法,建议使用 #pragma mark -
来分离:
0x17
如果描述超过一行,应改用长字符串文档:
- 以
/**
开始 - 换行写一句总结的话,以
?
或者!
或者.结尾。 - 空一行
- 在与第一行对齐的位置开始写剩下的注释
- 最后用
*/
结束。
/**
This comment serves to demonstrate the format of a docstring.
Note that the summary line is always at most one line long, and
after the opening block comment, and each line of text is preceded
by a single space.
*/
0x18
一个函数必须有一个字符串文档,除非它符合下面的所有条件:
- 非公开
- 很短
- 显而易见
0x19
字符串文档应该描述函数的调用符号和语义,而不是它如何实现。
0x1A
当需要的时候,注释应该用来解释特定的代码做了什么。所有的注释必须被持续维护或者干脆就删掉。
0x1B
一个类的文档应该只在 .h
文件里用 Doxygen/AppleDoc
的语法书写。 方法和属性都应该提供文档。
0x1C
使用 block
定义异步接口:
- (void)downloadObjectsAtPath:(NSString *)path
completion:(void(^)(NSArray *objects, NSError *error))completion;
0x1D
当定义一个类似上面的接口的时候,尽量使用一个单独的 block
作为接口的最后一个参数。把需要提供的数据和错误信息整合到一个单独 block
中,比分别提供成功和失败的 block
要好。
0x1E
这里需要遵循以下两点:
- 若
objects
不为nil
,则error
必须为nil
- 若
objects
为nil
,则error
必须不为nil
0x1F
block
一些注意点:
-
block
是在栈上创建的 -
block
可以复制到堆上 -
Block
会捕获栈上的变量(或指针),将其复制为自己私有的const
(变量)。 - (如果在
Block
中修改Block
块外的)栈上的变量和指针,那么这些变量和指针必须用__block
关键字申明
0x20
当使用代码块和异步分发的时候,要注意避免引用循环。
__weak __typeof(self)weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomethingWithData:data];
[strongSelf doSomethingWithData:data];
}
}];
0x21
为了分离概念,应该这样做:
- 委托模式(
delegate pattern
):事件发生的时候,委托者需要通知代理者。 - 数据源模式(
datasource pattern
): 委托者需要从数据源对象拉取数据。
0x22
代理方法必须以调用者(即委托者)作为第一个参数
0x23
Aspect Oriented Programming
(AOP,面向切面编程) 在 Objective-C
社区内没有那么有名,但是 AOP
在运行时可以有巨大威力。
在 Objective-C
的世界里,这意味着使用运行时的特性来为指定的方法追加 切面 。切面所附加的行为可以是这样的:
- 在类的特定方法调用前运行特定的代码
- 在类的特定方法调用后运行特定的代码
- 增加代码来替代原来的类的方法的实现