一個(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是線程安全的。