《禅与Objective-C编程艺术》笔记

GitBook for 《禅与 Objective-C 编程艺术》 笔记。

Golden Path

- (void)someMethod {
if (![someOther boolValue]) {
return;
}

//Do something important
}

错误处理

通过参数返回 error 的引用,使用这样的方法时应当检查方法的返回值,而非 error 的引用。

NSError *error = nil;
if (![self trySomethingWithError:&error]) {
// Handle Error
}

一些苹果的 API 在成功的情况下会对 error 参数(如果它非 NULL)写入垃圾值(garbage values),所以如果检查 error 的值可能导致错误 (甚至崩溃)。

命名

常量加类名前缀

static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;

方法名少用 and

- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;

类名应该以三个大写字母作为前缀(双字母前缀为 Apple 的类预留),说明性的部分放在前缀和父类名的在中间(如:ZOCTimelineViewController)。

初始化

推荐的代码组织方式是将 dealloc 方法放在实现文件的最前面(直接在 @synthesize 以及 @dynamic 之后),init 应该跟在 dealloc 方法后面。如果有多个初始化方法, 指定初始化方法 (designated initializer) 应该放在最前面,间接初始化方法 (secondary initializer) 跟在后面,这样更有逻辑性。

指定初始化器: NS_DESIGNATED_INITIALIZER

#define ZOC_UNAVAILABLE_INSTEAD(msg) __attribute__((unavailable("Invoke the designated initializer, use ["msg "].")))
#define ZOC_UNAVAILABLE(msg) __attribute__((unavailable(msg)))
@interface ZOCNewsViewController : UIViewController

- (instancetype)initWithNews:(ZOCNews *)news NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil ZOC_UNAVAILABLE_INSTEAD("initWithNews:");
- (instancetype)init ZOC_UNAVAILABLE_INSTEAD("initWithNews:");

@end

@implementation ZOCNewsViewController

- (id)initWithNews:(ZOCNews *)news
{
// call to 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:nil];
}

@end

属性

@property (nonatomic, readwrite, copy) NSString *name;
  • 属性的参数顺序:原子性,读写 和 内存管理
  • setter 方法中 KVO 通知(willChangeValueForKey, didChangeValueForKey) 会被自动执行。
  • 懒加载是 KVO 不友好的,因为其在 getter 中 change value
  • 永远不要在 init 方法(以及其他初始化方法)里面用 getter 和 setter 方法

方法

  • 善用断言
  • 私有方法不要用 _ 前缀,这是 apple 保留前缀

相等性

  • 你需要同时实现 isEqual 和 hash 方法。如果两个对象是被 isEqual 认为相等的,它们的 hash 方法需要返回一样的值。但是如果 hash 返回一样的值,并不能确保他们相等。
  • isEqualTo<#class-name-without-prefix#>: 这样的格式实现一个相等性检查方法。

Categories

  • category 方法前加小写前缀以及下划线,比如 - (id)zoc_myCategoryMethod

protocols

  • 抽象接口
  • 里氏替换原则: 你应该可以取代任意接口(也就是Objective-C里的的”protocol”)实现,而不用改变客户端或者相关实现。

NSNotification

  • 用类名前缀作为这个通知名字的前缀。
  • 用一个 Did/Will 这样的动词以及用 “Notifications” 后缀来命名这个通知。
// Foo.h
extern NSString * const ZOCFooDidBecomeBarNotification

// Foo.m
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";

代码组织

利用代码块

NSURL *url = ({
NSString *urlString = [NSString stringWithFormat:@"%@/%@", baseURLString, endpoint];
[NSURL URLWithString:urlString];
});

善用 Pragma

- (void)dealloc { /* ... */ }
- (instancetype)init { /* ... */ }

#pragma mark - View Lifecycle (View 的生命周期)

- (void)viewDidLoad { /* ... */ }
- (void)viewWillAppear:(BOOL)animated { /* ... */ }
- (void)didReceiveMemoryWarning { /* ... */ }

#pragma mark - Getter & Setter (自定义访问器)

- (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 { /* ... */ }

明确编译器警告和错误

#error Whoa, buddy, you need to check for zero here!
#warning Dude, don't compare floating point numbers like this!

公开方法属性要有注释

对象通讯

block

- (void)downloadObjectsAtPath:(NSString *)path
completion:(void(^)(NSArray *objects, NSError *error))completion {
if (objects) {
// do something with the data
}
else {
// some error occurred, 'error' variable should not be nil by contract
}
}
  • 若 objects 不为 nil,则 error 必须为 nil
  • 若 objects 为 nil,则 error 必须不为 nil

注意避免 block 可能引起的循环引用问题,采用@weakify/@strongify

委托和数据源

  • 代理方法必须以调用者(即委托者)作为第一个参数
  • 避免双重引用
  • 考虑用多重委托代替 NSNotifications。LBDelegateMatrioska

面向切面编程

Aspect Oriented Programming (AOP,面向切面编程),在 Objective-C 的世界里,这意味着使用运行时的特性来为指定的方法追加切面 。切面所附加的行为可以是这样的:

  • 在类的特定方法调用前运行特定的代码
  • 在类的特定方法调用后运行特定的代码
  • 增加代码来替代原来的类的方法的实现

Aspects