《Effective Objective-C 2.0》讀書筆記

一個(gè)月沒更新博客了(′?_?`)最近在工作和生活上都肥腸忙碌油宜,導(dǎo)致學(xué)習(xí)的時(shí)間變少了很多(╥﹏╥)。近一個(gè)月唯一的長進(jìn)就是看完了《Effective Objective-C 2.0》這本書躏敢,所以就把看這本書時(shí)想到的幾點(diǎn)問題記下來寫成一篇博客好了。


向前聲明



第2條:在類的頭文件中盡量少引入其它文件:

如果在各自頭文件中引入對方的頭文件整葡,則會(huì)導(dǎo)致“循環(huán)引用”(chicken-and-egg situation)件余。當(dāng)解析其中一個(gè)頭文件時(shí),編譯器會(huì)發(fā)現(xiàn)它引入了另一個(gè)頭文件遭居,而那個(gè)頭文件由回過頭來引用第一個(gè)頭文件啼器。使用#import而非#include指令雖然不會(huì)導(dǎo)致死循環(huán),但卻意味著這兩個(gè)類里有一個(gè)無法被正確編譯俱萍。

“無法被正確編譯”會(huì)帶來什么結(jié)果呢端壳?嘗試了一下,編譯無法通過枪蘑,編譯器報(bào)錯(cuò):

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

字面量



第3條:多用字面量語法损谦,少用與之等價(jià)的方法:

在改用字面量語法來創(chuàng)建數(shù)組時(shí)會(huì)遇到這個(gè)問題岖免。下面這段代碼分別以兩種語法創(chuàng)建數(shù)組:

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照捡,那么會(huì)出現(xiàn)什么情況呢颅湘?按字面量語法創(chuàng)建數(shù)組arrayB時(shí)會(huì)拋出異常。arrayA雖然能創(chuàng)建出來栗精,但是其中卻只含有object1一個(gè)對象闯参。原因在于,“arrayWithObjects:”方法會(huì)依次處理各個(gè)參數(shù)悲立,直到發(fā)現(xiàn)nil為止鹿寨,由于object2是nil,所以該方法會(huì)提前結(jié)束级历。

嘗試了一下释移,創(chuàng)建arrayB的時(shí)候拋出了異常:

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

而從堆棧信息中可以看到叭披,字面量語法創(chuàng)建arrayB的時(shí)候寥殖,使用了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:”方法判定兩個(gè)對象相等嚼贡,那么其hash方法也必須返回同一個(gè)值。但是同诫,如果兩個(gè)對象的hash方法返回同一個(gè)值粤策,那么“isEqual:”方法未必會(huì)認(rèn)為兩者相等。

官方文檔中也有類似的介紹:

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.

當(dāng)需要把一個(gè)對象放入一個(gè)collection中時(shí)误窖,hash方法尤其需要注意叮盘。官方文檔中也說明了,如果一個(gè)可變的對象被放入了依賴hash方法來定位的collection中霹俺,期間這個(gè)對象的hash值不應(yīng)該被改變:

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條:以“類族模式”隱藏實(shí)現(xiàn)細(xì)節(jié):

在使用NSArray的alloc方法來獲取實(shí)例時(shí)柔吼,該方法首先會(huì)分配一個(gè)屬于某類的實(shí)例,此實(shí)例充當(dāng)“占位數(shù)組”丙唧。該數(shù)組稍后會(huì)轉(zhuǎn)為另一個(gè)類的實(shí)例愈魏,而那個(gè)類則是NSArray的實(shí)體子類。

關(guān)于類族深層的東西想际,這本書沒有講太多培漏。但是至少可以了解到,程序員沒法預(yù)料初始化得到的實(shí)例的具體類型胡本,所以在比較類型時(shí)牌柄,使用

[maybeAnArray class] == [NSArray class]

[maybeAnArray isMemberOfClass:[NSArray class]]

很可能得到與預(yù)料中不一致的結(jié)果。


關(guān)聯(lián)對象的key



第10條:在既有類中使用關(guān)聯(lián)對象存放自定義數(shù)據(jù):

我們可以把某對象想象成NSDictionary侧甫,把關(guān)聯(lián)到該讀喜慶的值理解為字典中的條目珊佣,于是傻昙,存取關(guān)聯(lián)對象的值就相當(dāng)于在NSDictionary對象上調(diào)用[object setObject:value forKey:key]與[object objectForKey:key]方法。然而兩者之間有個(gè)重要差別:設(shè)置關(guān)聯(lián)對象時(shí)用的鍵是個(gè)“不透明的指針”(opaque pointer)彩扔。如果在兩個(gè)鍵上調(diào)用“isEqual:”方法的返回值是YES妆档,那么NSDictionary就認(rèn)為二者相等;然而在設(shè)置關(guān)聯(lián)對象值時(shí)虫碉,若想令兩個(gè)鍵匹配到同一個(gè)值贾惦,則二者必須是完全相同的指針才行。鑒于此敦捧,在設(shè)置關(guān)聯(lián)對象值時(shí)须板,通常使用靜態(tài)全局變量做鍵。

雖然用靜態(tài)全局變量做鍵已經(jīng)成為了一種套路兢卵,但是以前真沒想過它的鍵是個(gè)“不透明的指針”這個(gè)問題习瑰。


designated initializer



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

如果創(chuàng)建類實(shí)例的方式不止一種,那么這個(gè)類就會(huì)有多個(gè)初始化方法秽荤。這當(dāng)然很好甜奄,不過仍然要在其中選定一個(gè)作為全能初始化方法(designated initializer),令其它初始化方法都來調(diào)用它窃款。

官方文檔中也有類似的說法:

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.

在定義一個(gè)類的時(shí)候课兄,程序員必須認(rèn)出它父類的designated initializer,然后在子類的designated initializer中給它發(fā)送消息晨继。程序員還必須確保繼承的其它初始化方法也要被處理到烟阐,程序員還可以根據(jù)需要新增幾個(gè)初始化方法。
在設(shè)計(jì)一個(gè)類的初始化方法時(shí)紊扬,確保子類的designated initializer連上了父類的designated initializer蜒茄,而其它的初始化方法必須連上當(dāng)前類的designated initializer。


debugDescription



第17條:實(shí)現(xiàn)description方法:

NSObject協(xié)議中還有個(gè)方法要注意餐屎,那就是debugDescription檀葛,此方法的用意與description非常相似。二者區(qū)別在于啤挎,debugDescription方法時(shí)開發(fā)者在調(diào)試器中以控制臺命令打印對象時(shí)才調(diào)用的驻谆。

以前沒有注意過,查了一下官方文檔中的說明:

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


實(shí)例變量



第27條:使用“class-continuation分類”隱藏實(shí)現(xiàn)細(xì)節(jié):

為什么能在其中定義方法和實(shí)例變量呢庆聘?只因有“穩(wěn)固的ABI”這一機(jī)制胜臊,使得我們無需知道對象大小即可使用它。

第6條:理解“屬性”這一概念:

Objective-C的做法是伙判,把實(shí)例變量當(dāng)作一種存儲偏移量所用的“特殊變量”象对,交由“類對象”保管。偏移量會(huì)在運(yùn)行期查找宴抚,如果類的定義變了勒魔,那么存儲的偏移量也就變了甫煞,這樣的話,無論何時(shí)訪問實(shí)例變量冠绢,總能使用正確的偏移量抚吠。

這是一個(gè)很有趣的問題,好像我以前也從來沒考慮過弟胀。在C++中楷力,對實(shí)例變量的訪問,會(huì)在編譯時(shí)期被轉(zhuǎn)化為對偏移量的方法孵户。我做了個(gè)實(shí)驗(yàn)萧朝,用C++語言寫了個(gè)類:

// 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編譯成靜態(tài)庫,然后寫一個(gè)main函數(shù):

// 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

然后我在靜態(tài)庫的頭文件BaseClass.h中夏哭,注釋掉對a和c這兩個(gè)實(shí)例變量的定義检柬,但保持靜態(tài)庫的.a文件不變,再次運(yùn)行竖配,則輸出了奇怪的:

a=b, b=, c=

這應(yīng)該可以說明何址,如果使用C++語言,那么在BaseClass.cpp編譯的過程中械念,對實(shí)例變量的訪問已經(jīng)被改為了對一個(gè)偏移量的訪問头朱。

但是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.

這個(gè)特性牽涉了編譯龄减、鏈接和運(yùn)行時(shí),感覺如果要深入了解班眯,還需要下很多功夫希停。所以這里就暫時(shí)記錄這么多吧(′?_?`)


dispatch_barrier



第41條:多用派發(fā)隊(duì)列,少用同步鎖

在設(shè)置方法中使用了柵欄塊之后署隘,對屬性的讀取操作依然可以并發(fā)執(zhí)行宠能,但是寫入操作卻必須單獨(dú)執(zhí)行了。

_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方法必須同步才能讀取到有效數(shù)據(jù)违崇,而setter方法可以讓存放數(shù)據(jù)異步操作,不需要等待其操作完成诊霹。使用dispatch_barrier_async是想等到此刻所有g(shù)etter任務(wù)完成后再開始進(jìn)行setter的操作羞延。

但是在查閱了dispatch_barrier_async的官方文檔的過程中,卻發(fā)現(xiàn)了這樣一句說明:

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.

所以書中的這個(gè)例子選用了dispatch_get_global_queue可能不是很恰當(dāng)脾还,按照蘋果的說法伴箩,此處的dispatch_barrier_async其實(shí)就相當(dāng)于一個(gè)dispatch_async?


NSCache



第50條:構(gòu)建緩存時(shí)選用NSCache而非NSDictionary:

NSCache勝過NSDictionary之處在于鄙漏,當(dāng)系統(tǒng)資源將要耗盡時(shí)嗤谚,它可以自動(dòng)刪減緩存棺蛛。
...
NSCache并不會(huì)“拷貝”鍵,而是會(huì)“保留”它巩步。
...
另外旁赊,NSCache是線程安全的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椅野,一起剝皮案震驚了整個(gè)濱河市彤恶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鳄橘,老刑警劉巖声离,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瘫怜,居然都是意外死亡术徊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門鲸湃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赠涮,“玉大人,你說我怎么就攤上這事暗挑∷癯” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵炸裆,是天一觀的道長朝巫。 經(jīng)常有香客問我贿讹,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任佛舱,我火速辦了婚禮硼婿,結(jié)果婚禮上携御,老公的妹妹穿的比我還像新娘纳本。我一直安慰自己,他們只是感情好土思,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布务热。 她就那樣靜靜地躺著,像睡著了一般己儒。 火紅的嫁衣襯著肌膚如雪崎岂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天址愿,我揣著相機(jī)與錄音该镣,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛损合,可吹牛的內(nèi)容都是我干的省艳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼嫁审,長吁一口氣:“原來是場噩夢啊……” “哼跋炕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起律适,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辐烂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后捂贿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纠修,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年厂僧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扣草。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颜屠,死狀恐怖辰妙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情甫窟,我是刑警寧澤密浑,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站粗井,受9級特大地震影響尔破,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜背传,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一呆瞻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧径玖,春花似錦、人聲如沸颤介。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽滚朵。三九已至冤灾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辕近,已是汗流浹背韵吨。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留移宅,地道東北人归粉。 一個(gè)月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓椿疗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親糠悼。 傳聞我的和親對象是個(gè)殘疾皇子届榄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容