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

独奏

技术分享|2016-3-16|最后更新: 2023-2-23|
type
Post
status
Published
date
Mar 16, 2016
slug
summary
tags
开发
category
技术分享
icon
password

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

  • 抽象接口

    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](https://github.com/jspahrsummers/libextobjc/blob/master/extobjc/EXTScope.h)

    委托和数据源

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

    面向切面编程

    Aspect Oriented Programming (AOP,面向切面编程),在 Objective-C 的世界里,这意味着使用运行时的特性来为指定的方法追加切面 。切面所附加的行为可以是这样的:
    • 在类的特定方法调用前运行特定的代码
    • 在类的特定方法调用后运行特定的代码
    • 增加代码来替代原来的类的方法的实现