《Effective Objective-C 2.0》读书笔记

一个月没更新博客了(´・_・`)最近在工作和生活上都肥肠忙碌,导致学习的时间变少了很多(╥﹏╥)。近一个月唯一的长进就是看完了《Effective Objective-C 2.0》这本书,所以就把看这本书时想到的几点问题记下来写成一篇博客好了。


向前声明



第2条:在类的头文件中尽量少引入其它文件:

如果在各自头文件中引入对方的头文件,则会导致“循环引用”(chicken-and-egg situation)。当解析其中一个头文件时,编译器会发现它引入了另一个头文件,而那个头文件由回过头来引用第一个头文件。使用#import而非#include指令虽然不会导致死循环,但却意味着这两个类里有一个无法被正确编译。

“无法被正确编译”会带来什么结果呢?尝试了一下,编译无法通过,编译器报错:

/Users/xsq/Documents/Developer/XSQReferenceDemo/XSQReferenceDemo/ViewController.h:14:31: 
Unknown type name 'XSQObject'; did you mean 'NSObject'?

字面量



第3条:多用字面量语法,少用与之等价的方法:

在改用字面量语法来创建数组时会遇到这个问题。下面这段代码分别以两种语法创建数组:

id object1 = /* ... /;
id object2 = /
... /;
id object3 = /
... */;

NSArray *arrayA = [NSArray arrayWithObjects:object1, object2, object3, nil];
NSArray *arrayB = @[object1, object2, object3];

大家想想:如果object1和object3都指向了有效的Objective-C对象,而object2是nil,那么会出现什么情况呢?按字面量语法创建数组arrayB时会抛出异常。arrayA虽然能创建出来,但是其中却只含有object1一个对象。原因在于,“arrayWithObjects:”方法会依次处理各个参数,直到发现nil为止,由于object2是nil,所以该方法会提前结束。

尝试了一下,创建arrayB的时候抛出了异常:

Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[1]'

而从堆栈信息中可以看到,字面量语法创建arrayB的时候,使用了arrayWithObjects:count:方法,而不是arrayWithObjects:方法。

    frame #0: 0x0000000105a3ddbb libobjc.A.dylib`objc_exception_throw
    frame #1: 0x0000000105e82a52 CoreFoundation`-[__NSPlaceholderArray initWithObjects:count:] + 290
    frame #2: 0x0000000105edf0b4 CoreFoundation`+[NSArray arrayWithObjects:count:] + 52

hash



第8条:理解“对象等同性”这一概念:

如果“isEqual:”方法判定两个对象相等,那么其hash方法也必须返回同一个值。但是,如果两个对象的hash方法返回同一个值,那么“isEqual:”方法未必会认为两者相等。

官方文档中也有类似的介绍:

If two objects are equal, they must have the same hash value. This last point is particularly important if you define isEqual:
in a subclass and intend to put instances of that subclass into a collection. Make sure you also define hash
in your subclass.

当需要把一个对象放入一个collection中时,hash方法尤其需要注意。官方文档中也说明了,如果一个可变的对象被放入了依赖hash方法来定位的collection中,期间这个对象的hash值不应该被改变:

If a mutable object is added to a collection that uses hash values to determine the object’s position in the collection, the value returned by the hash
method of the object must not change while the object is in the collection.


类族



第9条:以“类族模式”隐藏实现细节:

在使用NSArray的alloc方法来获取实例时,该方法首先会分配一个属于某类的实例,此实例充当“占位数组”。该数组稍后会转为另一个类的实例,而那个类则是NSArray的实体子类。

关于类族深层的东西,这本书没有讲太多。但是至少可以了解到,程序员没法预料初始化得到的实例的具体类型,所以在比较类型时,使用

[maybeAnArray class] == [NSArray class]

[maybeAnArray isMemberOfClass:[NSArray class]]

很可能得到与预料中不一致的结果。


关联对象的key



第10条:在既有类中使用关联对象存放自定义数据:

我们可以把某对象想象成NSDictionary,把关联到该读喜庆的值理解为字典中的条目,于是,存取关联对象的值就相当于在NSDictionary对象上调用[object setObject:value forKey:key]与[object objectForKey:key]方法。然而两者之间有个重要差别:设置关联对象时用的键是个“不透明的指针”(opaque pointer)。如果在两个键上调用“isEqual:”方法的返回值是YES,那么NSDictionary就认为二者相等;然而在设置关联对象值时,若想令两个键匹配到同一个值,则二者必须是完全相同的指针才行。鉴于此,在设置关联对象值时,通常使用静态全局变量做键。

虽然用静态全局变量做键已经成为了一种套路,但是以前真没想过它的键是个“不透明的指针”这个问题。


designated initializer



第16条:提供“全能初始化方法”:

如果创建类实例的方式不止一种,那么这个类就会有多个初始化方法。这当然很好,不过仍然要在其中选定一个作为全能初始化方法(designated initializer),令其它初始化方法都来调用它。

官方文档中也有类似的说法:

When you define a subclass, you must be able to identify the designated initializer of the superclass and invoke it in your subclass’s designated initializer through a message to super. You must also make sure that inherited initializers are covered in some way. And you may provide as many convenience initializers as you deem necessary. When designing the initializers of your class, keep in mind that designated initializers are chained to each other through messages to super; whereas other initializers are chained to the designated initializer of their class through messages to self.

在定义一个类的时候,程序员必须认出它父类的designated initializer,然后在子类的designated initializer中给它发送消息。程序员还必须确保继承的其它初始化方法也要被处理到,程序员还可以根据需要新增几个初始化方法。
在设计一个类的初始化方法时,确保子类的designated initializer连上了父类的designated initializer,而其它的初始化方法必须连上当前类的designated initializer。


debugDescription



第17条:实现description方法:

NSObject协议中还有个方法要注意,那就是debugDescription,此方法的用意与description非常相似。二者区别在于,debugDescription方法时开发者在调试器中以控制台命令打印对象时才调用的。

以前没有注意过,查了一下官方文档中的说明:

debugDescription: Returns a string that describes the contents of the receiver for presentation in the debugger.


实例变量



第27条:使用“class-continuation分类”隐藏实现细节:

为什么能在其中定义方法和实例变量呢?只因有“稳固的ABI”这一机制,使得我们无需知道对象大小即可使用它。

第6条:理解“属性”这一概念:

Objective-C的做法是,把实例变量当作一种存储偏移量所用的“特殊变量”,交由“类对象”保管。偏移量会在运行期查找,如果类的定义变了,那么存储的偏移量也就变了,这样的话,无论何时访问实例变量,总能使用正确的偏移量。

这是一个很有趣的问题,好像我以前也从来没考虑过。在C++中,对实例变量的访问,会在编译时期被转化为对偏移量的方法。我做了个实验,用C++语言写了个类:

// BaseClass.h
#ifndef BaseClass_h
#define BaseClass_h

#include <stdio.h>

class BaseClass {
public:
    char a = 'a';
    char b = 'b';
    char c = 'c';
    void printBase();
};

#endif /* BaseClass_h */
// BaseClass.cpp
#include "BaseClass.h"

void BaseClass::printBase() {
    printf("a=%c, b=%c, c=%c", this->a, this->b, this->c);
}

将BaseClass编译成静态库,然后写一个main函数:

// main.cpp
#include <iostream>
#include "BaseClass.h"

int main(int argc, const char * argv[]) {
    BaseClass baseClass = BaseClass();
    baseClass.printBase();
    return 0;
}

能正常输出:

a=a, b=b, c=c

然后我在静态库的头文件BaseClass.h中,注释掉对a和c这两个实例变量的定义,但保持静态库的.a文件不变,再次运行,则输出了奇怪的:

a=b, b=, c=

这应该可以说明,如果使用C++语言,那么在BaseClass.cpp编译的过程中,对实例变量的访问已经被改为了对一个偏移量的访问。

但是Objective-C 2.0不是这样做的。官方文档里有这样的介绍:

The most notable new feature is that instance variables in the modern runtime are “non-fragile”:

In the legacy runtime, if you change the layout of instance variables in a class, you must recompile classes that inherit from it.
In the modern runtime, if you change the layout of instance variables in a class, you do not have to recompile classes that inherit from it.

这个特性牵涉了编译、链接和运行时,感觉如果要深入了解,还需要下很多功夫。所以这里就暂时记录这么多吧(´・_・`)


dispatch_barrier



第41条:多用派发队列,少用同步锁

在设置方法中使用了栅栏块之后,对属性的读取操作依然可以并发执行,但是写入操作却必须单独执行了。

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

- (NSString *)someString
{
    __block NSString *localSomeString;
    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
    return localSomeString;
}

- (void)setSomeString:(NSString *)someString
{
    dispatch_barrier_async(_syncQueue, ^{
        _someString = someString;
    });
}

这里,getter方法必须同步才能读取到有效数据,而setter方法可以让存放数据异步操作,不需要等待其操作完成。使用dispatch_barrier_async是想等到此刻所有getter任务完成后再开始进行setter的操作。

但是在查阅了dispatch_barrier_async的官方文档的过程中,却发现了这样一句说明:

The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_async function.

所以书中的这个例子选用了dispatch_get_global_queue可能不是很恰当,按照苹果的说法,此处的dispatch_barrier_async其实就相当于一个dispatch_async?


NSCache



第50条:构建缓存时选用NSCache而非NSDictionary:

NSCache胜过NSDictionary之处在于,当系统资源将要耗尽时,它可以自动删减缓存。
...
NSCache并不会“拷贝”键,而是会“保留”它。
...
另外,NSCache是线程安全的。

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

推荐阅读更多精彩内容