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

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

在你希望提供你自己的初始化函数的时候,你应该遵守三个步骤来保证正确的行为:

  1. 定义你的 designated initializer,确保调用了直接超类的 designated initializer
  2. 重载直接超类的 designated initializer。调用你的新的 designated initializer
  3. 为新的 designated initializer 写文档。

0x06

在文档中明确哪一个初始化方法是 designated 的,可以用编译器的指令 __attribute__((objc_designated_initializer)) 来标记意图。

0x07

应该总是使用 settergetter 方法访问属性,除了 initdealloc 方法。

0x08

总应该用 gettersetter ,因为:

  • 使用 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 (你捕捉并且 retainself,即使你没有明确的看到 self 关键词)。
  • 它自动产生 KVO 通知。
  • 在消息发送的时候增加的开销是微不足道的。

0x0A

永远不要在 init 方法(以及其他初始化方法)里面用 gettersetter 方法,应当直接访问实例变量。

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

当你要实现相等性的时候记住这个约定:你需要同时实现 isEqualhash 方法。如果两个对象是被 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
  • objectsnil,则 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 的世界里,这意味着使用运行时的特性来为指定的方法追加 切面 。切面所附加的行为可以是这样的:

  • 在类的特定方法调用前运行特定的代码
  • 在类的特定方法调用后运行特定的代码
  • 增加代码来替代原来的类的方法的实现
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容