《禅与Objective-C编程艺术》阅读总结
注:《Zen and the Art of the Objective-C Craftsmanship》 中文翻译版阅读总结
原文在这里 ,感谢作者
1、常量命名方法
常量的命名方法 以驼峰法命名,并以相关类名作为前缀
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
常量在头文件中以这样的形式暴露给外部
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
2、类名的规范
(1)类名以是哪个大写字母作为前缀,双字母前缀为Apple的类预留。解决Objective-C没有命名空间所带来的问题。
(2)创建一个子类时,把说明性的部分放在前缀和父类名的中间。如一个 UIViewController
的子类会是 ZOCTimelineViewController
。
3、Initializer 和 dealloc
(1)推荐将dealloc
方法放在实现文件的最前面(直接在 @synthesize
以及 dynamic
之后),
(2)为什么设置 self
为 [super init]
的返回值?
申请分配内存和初始化被分离为两步,alloc
和init
。这个特性叫两步创建
-
alloc
负责创建对象,这个过程包括分配足够的内存来保存对象,写入isa
指针,初始化引用计数,以及重置所有实例变量。 -
init
负责初始化对象,这意味着使对象处于可用状态。通常意味着为对象的实例变量赋予合理有用的值。
alloc
方法将返回一个有效的未初始化的对象实例。每一个对这个实例发送的消息会被转换成一次obj_msgSend()
函数的调用,形参self
的实参是alloc
返回的指针,这样self
在所有方法的作用域内都能够被访问。
init
方法可以通过返回 nil
来告诉调用者,初始化失败了。
4、Designated(指定) 和 Secondary (间接)初始化方法
(1)designated 初始化方法是提供所有的参数,每一个类总有且只有一个。Seconary 初始化方法可以是一个或多个,他们仅仅是提供一个或者更多的默认参数来调用designated初始化的初始化方法。
(2)在类继承中调用任何designated初始化方法都是合法的,应该保证所有的 designated initializer 在类继承中是从祖先(通常是NSObject)到你的类向下调用的。
定义一个新类的三种方式:
- 不需要重载任何的初始化函数
- 重载 designated initializer
- 定义一个新的 designated initializer
第一个方案最简单,不需要天剑类的任何初始化逻辑,只需要依照父类的designated initializer
@implementation YOEViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// custom initialization(自定义的初始化过程)
}
return self;
}
第三种 希望提供自己的初始化函数时,遵循三个步骤来保证获得正确的行为:
- 定义自己designated initializer,确保调用了直接超类的designated initializer
- 重载直接超类的 designated initializer。调用新的 designated initializer
- 为新的designated initializer写文档
正确的实现例子:
- (id)initWithNews:(NSString *)news
{
//call the immediate superclass's designated initializer(调用直接超类的designated initializer)
self = [super initWithNibName:nil bundle:nil];
if (self) {
_news = news;
}
return self;
}
// Override the immediate superclass's designated initializer(重载直接父类的 designated initializer)
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
//Call the new designated initializer
return [self initWithNews:@"defaultNews"];
}
注:如果没有重载initWithNibName: bundle:方法,而调用者决定使用这个方法来初始化这个类(这是完全合法的)。initWithNews:永远也不会调用,这就导致了不正确的初始化流程,新类的特定初始化逻辑没有被执行。
可以使用编译器的指令 NS_DESIGNATED_INITIALIZER
或__attribute__((objc_designated_initializer))
来明确的支出那个方法是designated initializer。这样如果新的designated initializer没有调用超类的designated initializer,编译器会发出警告。
写法:
- (id)initWithNews:(NSString *)news __attribute__((objc_designated_initializer));
- (id)initWithNews:(NSString *)news NS_DESIGNATED_INITIALIZER;
通过另一个编译器指令attribute((unavailable("Invoke the designated initializer")))来修饰一个方法,这样会使在试图调用这个方法的时候产生一个编译错误(实际上代码提示都不会有该方法了)。
写法:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil attribute((unavailable("Invoke the designated initializer")));
处理一个例外:
如果一个对象遵循NSCoding
协议,并且它通过initWithCoder:
初始化。此时应该看超类是否符合NSCoding
协议来区别对待。符合的时候,如只是调用了[super initWithCoder:]
,就需要在designated initializer里面写一些通用的初始化代码,处理这种方法的是把这些代码放在私有方法里面。当超类不符合NSCoding
协议的时候,推荐把initWithCoder:
作为Secondary initializer来对待,并且调用self
的designated initializer。
5、单例的写法
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc]init];
});
return sharedInstance;
}
6、属性定义
(1)属性定义参数按顺序排列:原子性,读写,内存管理。
如 @property(nonatomic,readwrite,copy) NSString *name;
(2)属性可以存储一个代码块,必须使用copy
。block最早在栈里面创建,使用copy
让block拷贝到堆里面去。
(3)声明一个公有的 getter 和一个私有的 setter,应该声明公开的属性为 readonly
,并在类的扩展中重新定义属性为readwrite
。
//.h文件中
@interface YOEClass : NSObject
@property(nonatomic,readonly,strong) NSObject *obj;
@end
//.m文件中
@interface YOEClass ()
@property(nonatomic,readwrite,strong) NSObject *obj;
@end
@implementation YOEClass
- (id)init{
self = [super init];
if (self) {
_obj = @"AA";
}
return self;
}
@end
(4)声明一个BOOL
属性,setter不应该带is
前缀,对应的getter应该带上前缀,So:
@property(assign,getter=isEditable) BOOL editable;
注:在实现文件中应该避免使用@synthesize
,编译器已经实现了
7、NSNotification
定义一个NSNotification
的时候,应该把通知的名字作为一个字符串常量
//.h
extern NSString * const ZOCFooDidBecomeBarNotification;
//.m
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";
8、利用代码块
代码块如果在闭合的圆括号里,会返回最后语句的值
NSURL *url = ({
NSString *urlStr = @"http://www.baidu.com";
[NSURL URLWithString:urlStr];
});
9、self的循环引用
当使用代码块和异步分发的时候,需要避免引用循环。应使用 weak
来引用对象,避免引用循环。
__weak __typeof(self) weakSelf = self;
例子:
单个语句时:
__weak __typeof(self) weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
[weakSelf doSomethingWithData:data];
}];
多个语句时:
[self executeBlock:^(NSData *data, NSError *error) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomethingWithData:data];
[strongSelf doSomethingWithData:data];
}
}];
简单记录self在block俩面的三种情况
- 直接在block里面使用关键字 self。只能在 block 不是作为一个property 的时候使用,否则会导致 retain cycle。
- 在 block 外定义一个
__weak
的引用到self,并且在 block 里面使用这个弱引用。 当 block 被声明一个 property 的时候使用。 - 在block 外定义一个
__weak
的引用到 self,并且在 block 内部通过这个弱引用定义一个__strong
的引用。 和并发执行有关。当涉及异步的服务的时候,block 可以在之后被执行,并且不会发生关于self是否存在的问题。