Objective-C作為面向?qū)ο缶幊糖酰皩?duì)象”(object)就是“基本構(gòu)造單元”(building block),開發(fā)者可以通過對(duì)象來存儲(chǔ)并傳遞數(shù)據(jù)杯聚。
在對(duì)象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過程就叫做“消息傳遞”(Messaging)臼婆。
第6條:理解“屬性”這一概念
-
“屬性”(property)是Objective-C的一項(xiàng)特性,用于封裝對(duì)象中的數(shù)據(jù)幌绍。
通過使用屬性這一寫法颁褂,我們可以從中獲取很多的優(yōu)勢(shì)故响,其中就包括編譯器會(huì)自動(dòng)編寫這些屬性所需的方法,此過程叫做“自動(dòng)合成”(autosynthesis)颁独。而且需要強(qiáng)調(diào)的是彩届,這個(gè)過程由編譯器在編譯期執(zhí)行抗俄,所以編輯器里是看不到這些“合成方法”(syntheiszed method)的源代碼霉翔。
除了生成方法代碼以外,編譯器還要自動(dòng)向類中添加適當(dāng)類型的實(shí)例變量柬唯,并且在屬性名前面加下劃線靠柑,以此作為實(shí)例變量的名字寨辩。而一般情況下,不太建議大家去修改默認(rèn)的實(shí)例變量名歼冰,但如果由于個(gè)人原因不能接受下劃線命名方案的話靡狞,也可以改為自己想要的寫法,如下:
@synthesize firstName = myFirstName; @synthesize lastName = myLastName;
-
若不想令編譯器自動(dòng)合成存取方法隔嫡,則可以自己實(shí)現(xiàn)甸怕。如果你只實(shí)現(xiàn)了其中一個(gè)存取方法,那么另外一個(gè)還是會(huì)由編譯器來合成腮恩,除非你使用@dynamic關(guān)鍵字來告訴編譯器:不要自動(dòng)創(chuàng)建實(shí)現(xiàn)屬性所用的實(shí)例變量梢杭,也不要為其創(chuàng)建存取方法。而且庆揪,在編譯器訪問屬性的代碼時(shí)式曲,及時(shí)編譯器發(fā)現(xiàn)沒有定義存取方法,也不會(huì)報(bào)錯(cuò)缸榛,因?yàn)樗嘈胚@些方法會(huì)在運(yùn)行期找到吝羞。
@interface MarkAnimatedView () @property NSString *firstName; @property NSString *lastName; @end @implementation MarkAnimatedView @dynamic firstName, lastName;
編譯器不會(huì)為上面這個(gè)類自動(dòng)合成存取方法或?qū)嵗兞浚褂么a訪問其中的屬性内颗,編譯器也不會(huì)發(fā)出警示信息钧排。
-
內(nèi)存管理語義
屬性用于封裝數(shù)據(jù),而數(shù)據(jù)則要有“具體的所有權(quán)語義”(concrete ownership semantic)均澳。如果自己編寫存取方法恨溜,那么就必須與有關(guān)屬性所具備的特質(zhì)相符。
- assign “設(shè)置方法”只會(huì)執(zhí)行針對(duì)“純量類型”(scalar type找前,例如CGFloat或NSInteger)的簡(jiǎn)單賦值操作糟袁。
- strong 此特征表明該屬性定義了一種“擁有關(guān)系”(owning relationship)。為這種屬性設(shè)置新值時(shí)躺盛,設(shè)置方法會(huì)先保留新值项戴,并釋放舊值,然后再把新值設(shè)置上去槽惫。
- weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系”(nonowning relationship)周叮。為這種屬性設(shè)置新值時(shí)辩撑,設(shè)置方法既不保留新值仿耽,也不釋放舊值合冀。在屬相所指對(duì)象遭到摧毀時(shí),屬性值會(huì)清空项贺。
- unsafe_unretained 此特質(zhì)的語義與assign相同君躺,但是它適用于“對(duì)象類型”(object type),該特質(zhì)表達(dá)了一種“非擁有關(guān)系”(“不保留”敬扛,unretained)晰洒,當(dāng)目標(biāo)對(duì)象遭到摧毀朝抖,屬性值不會(huì)自動(dòng)清空(“不安全”啥箭,unsafe),這一點(diǎn)與weak有區(qū)別治宣。
- copy 此特質(zhì)所表達(dá)的所屬關(guān)系與strong類似急侥,然而設(shè)置方法并不保留新值,而是將其“拷貝”(copy)侮邀。當(dāng)屬性類型為NSString*時(shí)坏怪,經(jīng)常使用此特質(zhì)保護(hù)其封裝性。因?yàn)閭鬟f給設(shè)置方法的新值有可能指向一個(gè)NSMutableString類的實(shí)例绊茧,那么設(shè)置完屬性之后铝宵,字符串的值就可能會(huì)在對(duì)象不知情的情況下遭人更改,所以华畏,這時(shí)就要拷貝一份“不可變”(immutable)的字符串鹏秋,來確保對(duì)象中的字符串值不會(huì)無意間變動(dòng)。
-
方法名
可以通過如下特質(zhì)來指定存取方法的方法名:@property (nonatomic, getter=isOn) BOOL on;
-
atomic與nonatomic的區(qū)別
具備atomic特質(zhì)的獲取方法會(huì)通過鎖定機(jī)制來確保其操作的原子性亡笑,這也就是說侣夷,如果兩個(gè)線程讀寫同一個(gè)屬性,那么無論何時(shí)仑乌,總能看到有效的屬性值百拓。而若是使用nonatomic語義的話,那么當(dāng)其中一個(gè)線程正在改寫某屬性值時(shí)晰甚,另一個(gè)線程也許會(huì)突然闖入衙传,把尚未修改好的屬性值讀取出來。發(fā)生這種情況時(shí)厕九,線程讀到的屬性值可能不對(duì)蓖捶。
但是在iOS開發(fā)中,基本所有的屬性都聲明為nonatomic止剖,這樣做的歷史原因就是:在iOS中使用同步鎖的開銷較大腺阳,這會(huì)帶來性能問題落君。但是在Mac OS程序開發(fā)時(shí),使用atomic屬性通常都不會(huì)有性能瓶頸亭引。
第7條:在對(duì)象內(nèi)部盡量直接訪問實(shí)例變量
-
首先绎速,我們需要先清楚,什么是【通過屬性訪問】焙蚓?什么又是【直接訪問】纹冤?
其實(shí)用最簡(jiǎn)單的話來說就是:使用點(diǎn)語法來訪問的就是【通過屬性訪問】,如: self.firstName = [components objectAtIndex: 0];
而【直接訪問】就是使用下劃線語法訪問购公,如: _firstName = [components objectAtIndex: 0];這兩種寫法有幾個(gè)區(qū)別:
- 由于不經(jīng)過Objective-C的“方法派發(fā)”(method dispatch)步驟萌京,所以直接訪問實(shí)例變量的速度當(dāng)然會(huì)比較快。在這種情況下宏浩,編譯器所生成的代碼會(huì)直接訪問保存對(duì)象實(shí)例變量的那塊內(nèi)存知残。
- 直接訪問實(shí)例變量時(shí),不會(huì)調(diào)用其“設(shè)置方法”比庄,這就繞過了為相關(guān)屬性所定義的“內(nèi)存管理語義”求妹。(如:在ARC下直接訪問一個(gè)聲明為copy的屬性,那么并不會(huì)拷貝該屬性佳窑,只會(huì)保留新值并釋放舊值制恍。)
- 如果直接訪問實(shí)例變量,那就不會(huì)觸發(fā)“鍵值觀察”(Key-Value Observing神凑,KVO)通知净神。(所以是否使用需要根據(jù)實(shí)際的項(xiàng)目需求而定)
- 通過屬性來訪問則有助于排查與之相關(guān)的錯(cuò)誤,因?yàn)榭梢越o“獲取方法”和/或“設(shè)置方法”中新增“斷點(diǎn)”(breakpoint)溉委。
所以總的來說鹃唯,在對(duì)象內(nèi)部讀取數(shù)據(jù)的時(shí)候,我們更推薦通過直接訪問實(shí)例變量的方式進(jìn)行薛躬,而在寫入數(shù)據(jù)的時(shí)候俯渤,則應(yīng)通過屬性訪問的方式來寫。在初始化方法及delloc方法中型宝,就只應(yīng)該使用【直接訪問】來讀寫數(shù)據(jù)了八匠!
第8條:理解“對(duì)象等同性”這一概念
-
按照==操作符比較出來的結(jié)果未必是我們想要的,因?yàn)樵摬僮鞅容^的是兩個(gè)指針本身趴酣,而不是其所指的對(duì)象梨树。應(yīng)該使用NSObject協(xié)議中聲明的“isEqual:”方法來判斷兩個(gè)對(duì)象的等同性。
NSString *foo = @"Badger 123"; NSString *bar = [NSString stringWithFormat:@"Badger %i",123]; BOOL equalA = (foo == bar); BOOL equalB = [foo isEqual:bar]; BOOL equalC = [foo isEqualToString:bar]; NSLog(@"equalA : %d\nequalB : %d\nequalC : %d",equalA,equalB,equalC);
Output:
equalA : 0
equalB : 1
equalC : 1大家這樣就可以看到==與等同性判斷方法之間的差別了岖寞。
注意 : NSString類實(shí)現(xiàn)了一個(gè)自己獨(dú)有的等同性判斷方法抡四,名叫“isEqualToString:”,傳遞給該方法的對(duì)象必須是NSString,否則結(jié)果未定義指巡。調(diào)用該方法比調(diào)用“isEqual:”方法快淑履,后者還要執(zhí)行額外的步驟,因?yàn)樗恢朗軠y(cè)對(duì)象的類型藻雪。
若想在自定義的對(duì)象中正確覆寫判斷等同性的方法秘噪,就必須先理解其約定。但是勉耀,如果兩個(gè)對(duì)象的hash方法返回同一個(gè)值指煎,那么“isEqual:”方法未必會(huì)認(rèn)為兩者想等。
==大概的判斷邏輯思路如下:==
- 首先便斥,直接判斷兩個(gè)指針是否想等(若相等至壤,則其均指向同一個(gè)對(duì)象,所以受測(cè)的對(duì)象也必定相等枢纠。)像街;
- 接下來,比較兩對(duì)象所屬的類(若不屬于同一個(gè)類京郑,則兩對(duì)象不相等宅广。)葫掉;
- 但是在繼承體系中判斷等同性時(shí)些举,經(jīng)常會(huì)遭遇A實(shí)例與其子類實(shí)例想等,所以實(shí)現(xiàn)“isEqual:”方法要考慮到這種情況俭厚!
- 最后户魏,檢測(cè)每個(gè)屬性是否相等。(只要其中有不相等的屬性挪挤,就判定兩對(duì)象不等叼丑,否則兩對(duì)象相等。)
??接下來該實(shí)現(xiàn)hash方法了扛门,根據(jù)等同性約定:若兩對(duì)象相等鸠信,則其哈希碼也相等,但是兩個(gè)哈希碼相同的對(duì)象卻未必相等论寨。這就是能否正確覆寫“isEqual:”方法的關(guān)鍵所在星立。下面這種寫法完全可行:
```
- (NSUInteger)hash{
return 1337;
}
```
不過若是這么寫的話,在collection中使用這種對(duì)象將會(huì)產(chǎn)生性能問題葬凳,因?yàn)閏ollection在檢索哈希表時(shí)绰垂,會(huì)用對(duì)象的哈希碼做引索。假如某個(gè)collection是用set集合實(shí)現(xiàn)的火焰,那么set可能會(huì)根據(jù)哈希碼把對(duì)象分裝到不同的數(shù)組中劲装。在向set中添加新對(duì)象的時(shí)候,要根據(jù)其哈希碼找到與之相關(guān)的那個(gè)數(shù)組,依次檢查其中各個(gè)元素占业,看數(shù)組中已有的對(duì)象是否和將要添加的新對(duì)象相等绒怨。
由此可知,如果令每個(gè)對(duì)象都返回相同的哈希碼谦疾,那么在set中已有1 000 000個(gè)對(duì)象的情況下窖逗,若是繼續(xù)向其中添加對(duì)象,則需將這1 000 000個(gè)對(duì)象全部掃描一遍餐蔬。
??下面給大家推薦一種技能保持較高效率碎紊,又能使生成的哈希碼至少位于一定范圍內(nèi),而不會(huì)過于頻繁地重復(fù)樊诺。當(dāng)然仗考,此算法生成的哈希碼還是會(huì)碰撞,不過至少可以保證哈希碼有多種可能的取值词爬。編寫hash方法時(shí)秃嗜,應(yīng)該用當(dāng)前的對(duì)象做做實(shí)驗(yàn),以便在減少碰撞頻度與降低運(yùn)算復(fù)雜程度之間取舍顿膨。
```
- (NSUInteger)hash{
NSUInteger firstNameHash = [_firstName hash];
NSUInteger lastNameHash = [_lastName hash];
NSUInteger ageHash = _age;
return firstNameHash ^ lastNameHash ^ ageHash;
}
```
-
特定類所具有的等同性判定方法
除了剛剛上面提到的NSString之外锅锨,NSArray與NSDictionary類也具有特殊的等同性判定方法,由于使用這些特殊的等同性判定方法在編譯期是不做強(qiáng)類型檢查(strong type checking)恋沃,所以判定的速度會(huì)比較快必搞,但是開發(fā)者要保證所傳對(duì)象的類型是正確的。
-
等同性判定的執(zhí)行深度
NSArray的檢測(cè)方式為先看兩個(gè)數(shù)組所含對(duì)象個(gè)數(shù)是否相同囊咏,若相同恕洲,則在每個(gè)對(duì)應(yīng)位置的兩個(gè)對(duì)象身上調(diào)用其“isEqual:”方法。如果對(duì)應(yīng)位置上的對(duì)象均相等梅割,那么這兩個(gè)數(shù)組就相等霜第,這叫做“深度等同性判定”(deep equality)。
??不過有時(shí)候無須將所有數(shù)據(jù)逐個(gè)比較户辞,只根據(jù)其中部分?jǐn)?shù)據(jù)即可判明二者是否等同泌类。(如:A類的實(shí)例是根據(jù)數(shù)據(jù)庫里面的數(shù)據(jù)創(chuàng)建而來,那么其中就可能含有另外一個(gè)屬性底燎,此屬性是“唯一標(biāo)識(shí)符”刃榨,在這種情況下,我們也許只會(huì)根據(jù)標(biāo)識(shí)符來判斷等同性书蚪,尤其是在此屬性聲明為readonly時(shí)更應(yīng)該如此喇澡。) -
容器中可變類的等同性
現(xiàn)在跟大家分享一種情況:用一個(gè)NSMutableSet與幾個(gè)NSMutableArray對(duì)象測(cè)試一下,就能讓大家發(fā)現(xiàn)問題了殊校。
NSMutableSet *set = [NSMutableSet new]; NSMutableArray *arrayA = [@[@1, @2] mutableCopy]; [set addObject:arrayA]; NSLog(@"set = %@", set); //Output:set = {((1,2))}
現(xiàn)在set里含有一個(gè)數(shù)組對(duì)象晴玖,數(shù)組包含兩個(gè)對(duì)象。如果再向set中加入一個(gè)與現(xiàn)有數(shù)組一模一樣的數(shù)組對(duì)象的話,那set里面的對(duì)象將不會(huì)有變化呕屎,所以我們接下來添加一個(gè)和set已有對(duì)象不同的數(shù)組:
NSMutableArray *arrayC = [@[@1] mutableCopy]; [set addObject:arrayC]; //Output:set = {((1),(1,2))}
正如大家所料让簿,由于arrayC與set里已有的對(duì)象不相等,所以現(xiàn)在set里有兩個(gè)數(shù)組了秀睛。最后尔当,我們來改變一下arrayC的內(nèi)容,令到其和arrayA相等:
[arrayC addObject:@2]; NSLog(@"set = %@", set); //Output:set = {((1, 2),(1, 2))}
set中居然可以包含兩個(gè)彼此相等的數(shù)組u灏病M钟!根據(jù)set語義是不允許出現(xiàn)這種情況的田盈,然而現(xiàn)在卻無法保證這一點(diǎn)了畜号,因?yàn)槲覀冃薷牧藄et中的已有對(duì)象。所以允瞧,我們得出一個(gè)結(jié)論:==如果把某對(duì)象放入set之后简软,又修改其內(nèi)容,那么后面的行為將很難預(yù)料==
當(dāng)然述暂,舉這個(gè)例子只是為了提醒大家痹升,把某對(duì)象加入collection之后,改變其內(nèi)容將會(huì)帶來的后果畦韭,并沒有說絕對(duì)不要這么做疼蛾,而是讓大家注意在這樣做的時(shí)候,要用相應(yīng)的代碼去處理可能出現(xiàn)的問題廊驼。
==**要點(diǎn)概括**==
1. 若想檢測(cè)對(duì)象的等同性据过,請(qǐng)?zhí)峁癷sEqual:”與hash方法;
2. 相同的對(duì)象必須具有相同的哈希碼妒挎,但是兩個(gè)哈希碼相同的對(duì)象卻未必相同;
3. 不要盲目地逐個(gè)檢測(cè)每天屬性西饵,而是應(yīng)該依照具體需求來制定檢測(cè)方法酝掩;
4. 編寫hash方法時(shí),應(yīng)該使用計(jì)算速度快且哈希碼碰撞幾率低的算法眷柔;
第9條:以“類族模式”隱藏實(shí)現(xiàn)細(xì)節(jié)
-
類族模式
“類族”(class cluster)是一種模式(pattern)期虾。該模式可以靈活應(yīng)對(duì)多個(gè)類,將它們的實(shí)現(xiàn)細(xì)節(jié)隱藏在“抽象基類”(abstract base class)后面驯嘱,以保持接口簡(jiǎn)潔镶苞。開發(fā)者無須自己創(chuàng)建子類實(shí)例,只需調(diào)用基類方法來創(chuàng)建即可鞠评。
-
創(chuàng)建類族
MarkEmployee頭文件:
// // MarkEmployee.h #import <Foundation/Foundation.h> /* 抽象基類 */ typedef NS_ENUM(NSUInteger, MarkEmployeeType) { MarkEmployeeTypeDeveloper, MarkEmployeeTypeDesigner, MarkEmployeeTypeFinance,}; @interface MarkEmployee : NSObject /** 名字 */ @property(nonatomic,copy) NSString *name; /** 薪水 */ @property(nonatomic,assign) NSUInteger salary; // 工廠方法(類方法):創(chuàng)建雇員對(duì)象 + (MarkEmployee *)employeeWithType:(MarkEmployeeType)type;; // 職員干自己的工作 - (void)doADaysWork; @end
MarkEmployee實(shí)現(xiàn)文件:
// // MarkEmployee.m // #import "MarkEmployee.h" @implementation MarkEmployee /* 將子類的實(shí)例的創(chuàng)建隱藏在基類的實(shí)現(xiàn)方法中 */ // 根據(jù)職員類型創(chuàng)建子類各自的實(shí)例 +(MarkEmployee *)employeeWithType:(MarkEmployeeType)type { switch (type) { case MarkEmployeeTypeDeveloper: return [MarkEmployeeDeveloper new]; break; case MarkEmployeeTypeDesigner: return [MarkEmployeeDesigner new]; break; case MarkEmployeeTypeFinance: return [MarkEmployeeFinance new]; break; default: break; } } //Make Employees do their respective day's work -(void)doADaysWork { // 在子類的實(shí)現(xiàn)文件中各自實(shí)現(xiàn)其工作 } @end
MarkEmployee的子類MarkEmployeeDeveloper的實(shí)現(xiàn)文件:
#import "MarkEmployeeDeveloper.h" @implementation MarkEmployeeDeveloper - (void)doADaysWork{ // 子類其工作的實(shí)現(xiàn)細(xì)節(jié) [self writeCode]; } - (void)writeCode{ NSLog(@"writeCode"); } @end
main函數(shù):
#import "MarkEmployee.h" int main(int argc, const char * argv[]) { @autoreleasepool { MarkEmployee *developer = [MarkEmployee emplyeeWithType:MarkEmployeeTypeDeveloper]; NSLog(@"%@",[developer class]);// output MarkEmployeeDeveloper /* * 總結(jié): * 工廠模式 * 通過MarkEmployee類的工廠方法創(chuàng)建出來的實(shí)例是MarkEmployee類的子類的實(shí)例 */ } return 0; }
-
Cocoa里的族類
系統(tǒng)框架中有許多類族茂蚓。大部分collection類都是某個(gè)類族中的抽象基類。NSArray與NSMutableArray實(shí)際上有兩個(gè)抽象基類,但是仍然算是一個(gè)類族聋涨,意味著兩者在實(shí)現(xiàn)各自類型的數(shù)組時(shí)可以共用實(shí)現(xiàn)代碼晾浴,此外,還能把可變數(shù)組復(fù)制為不可變數(shù)組牍白,反之亦然脊凰。
id maybeAnArray = /* ... */; if ([maybeAnArray class] == [NSArray class]){// 永遠(yuǎn)不可為真 // will nerver be hit }
注意 : [maybeAnArray class] 所返回的類絕不可能是NSArray類本身,因?yàn)橛蒒SArray的初始化方法所返回的那個(gè)實(shí)例所屬的類型是“隱藏在類族公共接口后面的那個(gè)內(nèi)部類型”茂腥。要判斷出某個(gè)實(shí)例所屬的類是否位于類族之中需要用類型信息查詢方法狸涌。
id maybeAnArray = /* ... */; if (maybeAnArray isKindOfClass:[NSArray class]){ // will be hit }
向Cocoa中NSArray這樣的類族新增子類所要遵守的規(guī)則
- 子類應(yīng)該繼承自類族中的抽象基類。
- 子類應(yīng)該定義自己的數(shù)據(jù)存儲(chǔ)方式最岗。
子類必須用一個(gè)實(shí)例變量來存放數(shù)組中的對(duì)象杈抢,而NSArray本身只不過是包在其他隱藏對(duì)象外面的殼,它僅僅定義了所有數(shù)組都需具備的一些接口仑性。對(duì)于這個(gè)自定義的數(shù)組子類來說惶楼,可以用NSArray來保存其實(shí)例。 - 子類應(yīng)當(dāng)覆寫超類文檔中指明需要覆寫的方法诊杆。
在類族中實(shí)現(xiàn)子類時(shí)所需遵循的規(guī)范一般都會(huì)定義基類的文檔之中歼捐,編碼前應(yīng)該先看看。
要點(diǎn)
- 類族模式可以把實(shí)現(xiàn)細(xì)節(jié)隱藏在一套簡(jiǎn)單的公共接口(抽象基類)后面晨汹。
- 系統(tǒng)框架中經(jīng)常使用類族豹储。
- 從類族的公共抽象基類中繼承子類時(shí)要當(dāng)心,若有開發(fā)文檔淘这,則應(yīng)首先閱讀剥扣。
第10條:在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)
- 有時(shí)候我們需要在對(duì)象中存放相關(guān)的信息,此時(shí)铝穷,我們通常的做法是會(huì)從對(duì)象所屬的類中去繼承一個(gè)子類钠怯,然后改用這個(gè)子類對(duì)象。然而曙聂,并非所有情況下都能這么做晦炊,有時(shí)候類的實(shí)例可能是由某種機(jī)制所創(chuàng)建的,而開發(fā)者無法令這種機(jī)制創(chuàng)建出自己所寫的子類實(shí)例宁脊。
- 但是断国,Objective-C中有一項(xiàng)強(qiáng)大的特性可以解決此問題,這就是“關(guān)聯(lián)對(duì)象(Associated Object)”榆苞。
- 可以給某對(duì)象關(guān)聯(lián)許多其他對(duì)象稳衬,這些對(duì)象通過“鍵”來區(qū)分,這就是關(guān)聯(lián)對(duì)象坐漏。存儲(chǔ)對(duì)象值的時(shí)候薄疚,可以指明“存儲(chǔ)策略”(storage policy)碧信,用以維護(hù)相應(yīng)的“內(nèi)存管理語義”
| 關(guān)聯(lián)類型 | 等效的@property屬性 |
| --- | --- |
| OBJC_ASSOCIATION_ASSIGN | assign |
| OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic,retain |
| OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic, copy |
| OBJC_ASSOCIATION_RETAIN | retain |
| OBJC_ASSOCIATION_COPY | copy |
**下列方法可以管理管理對(duì)象:**
-
void objc_setAssociatedObject(id object, void*key, id value, objc_AssociationPolicy)
此方法以給定的鍵和策略為某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象值输涕。
-
id objc_getAssociatedObject(id object, void*key)
此方法根據(jù)給定的鍵從某對(duì)象中獲取相應(yīng)的關(guān)聯(lián)對(duì)象值音婶。
-
void objc_removeAssociatedObjects(id object)
此方法移除指定對(duì)象的全部關(guān)聯(lián)對(duì)象。
由于設(shè)置關(guān)聯(lián)對(duì)象時(shí)所用的鍵是個(gè)“不透明的指針”(opaque pointer)莱坎,所以在設(shè)置關(guān)聯(lián)對(duì)象值時(shí)衣式,若想令兩個(gè)鍵匹配到同一個(gè)值,則兩者必須是完全相同的指針才行檐什。鑒于此碴卧,在設(shè)置關(guān)聯(lián)對(duì)象值時(shí),通常使用靜態(tài)全局變量做鍵乃正。
-
關(guān)聯(lián)對(duì)象用法舉例
以我們最常用的UIAlertView為例住册,給大家講解一下關(guān)聯(lián)對(duì)象的用法:
這是我們常用于處理一些向用戶提示某些消息,并根據(jù)用戶點(diǎn)擊的按鈕來處理下一步動(dòng)作的代碼:
- (void)askUserAQuestion{ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question" message:@"What do you want to do ?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Continue", nil]; [alert show]; } //UIAlertView Delegate -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 0) { [self doCancel]; }else{ [self doContinue]; } }
如果想在同一個(gè)類里處理多個(gè)AlertView的話瓮具,代碼就會(huì)變得更為復(fù)雜荧飞,我們必須在delegate方法中檢查傳入的alertView參數(shù),并據(jù)此選用相應(yīng)對(duì)的邏輯名党。要是能在創(chuàng)建AlertView的時(shí)候直接把處理每個(gè)按鈕的邏輯都寫好叹阔,那豈不是簡(jiǎn)單多了!
??接下來我們就用關(guān)聯(lián)對(duì)象的方式來實(shí)現(xiàn)同樣的功能:#import <objc/runtime.h> static void *MarkMyAlertViewKey = "MarkMyAlertViewKey"; - (void)askUserAQuestion{ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question" message:@"What do you want to do ?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Continue", nil]; void(^block)(NSInteger) = ^(NSInteger buttonIndex){ if (buttonIndex == 0) { [self doCancel]; }else{ [self doContinue]; } }; objc_setAssociatedObject(alert, MarkMyAlertViewKey, block, OBJC_ASSOCIATION_COPY); [alert show]; } //UIAlertView Delegate -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { void (^block)(NSInteger) = objc_getAssociatedObject(alertView, MarkMyAlertViewKey); block(buttonIndex); }
在一個(gè)類里創(chuàng)建一個(gè)警告視圖后传睹,設(shè)定一個(gè)與之關(guān)聯(lián)的“塊”并將它們放在同一作用域里耳幢,等到執(zhí)行delegate方法時(shí)再將其讀出來。這種方式創(chuàng)建的UIAlertView與處理操作結(jié)果的代碼都放在一起欧啤,更易讀懂睛藻。==但是,由于塊可能要捕獲某些變量邢隧,也會(huì)造成“保留環(huán)”==店印。
??所以,這種做法只應(yīng)該在其他辦法行不通時(shí)才去考慮用它府框。而對(duì)于多次用到UIAlertView視圖吱窝,有個(gè)更好的辦法,那就是從中繼承子類迫靖,把塊保存為子類中的屬性。要點(diǎn)
- 可以通過“關(guān)聯(lián)對(duì)象”機(jī)制來把兩個(gè)對(duì)象連起來兴使。
- 定義關(guān)聯(lián)對(duì)象時(shí)可指定內(nèi)存管理語義系宜,用以模仿定義屬性時(shí)所采用的“擁有關(guān)系”與“非擁有關(guān)系”。
- 只有在其他做法不可行時(shí)才應(yīng)選用關(guān)聯(lián)對(duì)象发魄,因?yàn)檫@種做法通常會(huì)引起難于查找的bug盹牧。
第11條:理解objc_msgSend的作用
在Objective-C中俩垃,如果向某對(duì)象傳遞消息,那就會(huì)使用動(dòng)態(tài)綁定機(jī)制來決定需要調(diào)用的方法汰寓。在底層口柳,所有方法都是普通的C語言函數(shù),然而對(duì)象收到消息之后有滑,究竟該調(diào)用哪個(gè)方法則完全于運(yùn)行期決定跃闹,甚至可以在程序運(yùn)行時(shí)改變,這些特性使得Objective-C成為一門真正的動(dòng)態(tài)語言毛好。
-
消息派發(fā)的一般過程
id returnValue = [someObject messageName:parameter];
解釋:someObject是“接收者”望艺,messageName是“選擇器”,選擇器與參數(shù)合起來成為“消息”肌访。
編譯器在看到此消息后找默,將其轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語言函數(shù)調(diào)用objc_msgSend,其原型如下:
void objc_msgSend(id self, SEL cmd, ...)
解釋:這是個(gè)參數(shù)可變的函數(shù)吼驶。第一個(gè)參數(shù)代表接收者惩激,第二個(gè)參數(shù)代表選擇器,后續(xù)參數(shù)就是消息中的那些參數(shù)蟹演,其順序不變风钻。
對(duì)先前例子中的消息進(jìn)行轉(zhuǎn)換,如下:
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
解釋:objc_msgSend函數(shù)會(huì)依據(jù)接收者與選擇器的類型來調(diào)用適當(dāng)?shù)姆椒ü熘摹榱送瓿纱瞬僮髌枪荆摲椒ㄐ枰诮邮照咚鶎俚念愔兴褜て洹胺椒斜怼保绻苷业脚c選擇器名稱相符的方法蚌父,就跳至其實(shí)現(xiàn)代碼哮兰。若是找不到,那就沿著繼承體系繼續(xù)向上查找苟弛,等找到合適的方法之后再跳轉(zhuǎn)喝滞。如果最終還是找不到相符的方法,那就執(zhí)行“消息轉(zhuǎn)發(fā)”(message forwarding)操作膏秫。
-
消息派發(fā)的特殊情況
- objc_msgSend_stret:如果待發(fā)送的消息要返回結(jié)構(gòu)體右遭,那么可交由此函數(shù)處理。
- objc_msgSend_fpret:如果消息返回的是浮點(diǎn)數(shù)缤削,那么交由此函數(shù)處理窘哈。
- objc_msgSendSuper:如果要給超類發(fā)消息,例如[super message:parameter]亭敢,那么就交由此函數(shù)處理滚婉。
-
尾調(diào)用優(yōu)化
objc_msgSend等函數(shù)一旦找到應(yīng)該調(diào)用的方法實(shí)現(xiàn)之后,就會(huì)“跳轉(zhuǎn)過去”帅刀。之所以能這么做让腹,是因?yàn)镺bjective-C對(duì)象的每個(gè)方法都可以視為簡(jiǎn)單的C函數(shù)远剩,其原型如下:
<return_type> Class_selector(id self, SEL _cmd, ...)
解釋:每個(gè)類里都有一張表格,其中的指針都會(huì)指向這種函數(shù)骇窍,而選擇器的名稱則是查表時(shí)所用的“鍵”瓜晤。objc_msgSend等函數(shù)正是通過這張表格來尋找應(yīng)該執(zhí)行的方法并跳至其實(shí)現(xiàn)的。其中腹纳,所使用的“尾調(diào)用優(yōu)化”(tail-call optimization)技術(shù)痢掠,可以讓“跳至方法實(shí)現(xiàn)”這一操作變得更簡(jiǎn)單些。但是只估,只有當(dāng)某函數(shù)的最后一個(gè)操作僅僅是調(diào)用其他函數(shù)而不會(huì)將其返回值另作他用時(shí)志群,才能執(zhí)行“尾調(diào)用優(yōu)化”。
要點(diǎn)
- 消息由接收者蛔钙、選擇器及參數(shù)構(gòu)成锌云。給某對(duì)象“發(fā)送消息”(invoke a message)也就相當(dāng)于在該對(duì)象上“調(diào)用方法”(call a method)。
- 發(fā)給某對(duì)象的全部信息都要由“動(dòng)態(tài)消息派發(fā)系統(tǒng)”(dynamic message dispatch system)來處理吁脱,該系統(tǒng)會(huì)查出對(duì)應(yīng)的方法桑涎,并執(zhí)行其代碼。
第12條:理解消息轉(zhuǎn)發(fā)機(jī)制
當(dāng)對(duì)象接收到無法解讀的消息后兼贡,就會(huì)啟動(dòng)“消息轉(zhuǎn)發(fā)”機(jī)制攻冷,開發(fā)者可經(jīng)由此過程告訴對(duì)象應(yīng)該如何處理未知消息。
-
消息轉(zhuǎn)發(fā)分為兩大階段:
第一階段:先征詢接受者所屬的類遍希,看其是否能動(dòng)態(tài)添加方法等曼,以處理當(dāng)前這個(gè)“未知的選擇子”,這叫做“動(dòng)態(tài)方法解析”凿蒜。
第二階段:涉及“完整的消息轉(zhuǎn)發(fā)機(jī)制”禁谦。
運(yùn)行時(shí)系統(tǒng)會(huì)請(qǐng)求接收者以“動(dòng)態(tài)新增方法”之外的手段來處理與消息相關(guān)的方法調(diào)用,這又細(xì)分為兩小步废封。
??首先州泊,請(qǐng)接收者看看有沒有其他對(duì)象能處理這條消息。若有漂洋,則運(yùn)行期系統(tǒng)會(huì)把消息轉(zhuǎn)給那個(gè)對(duì)象遥皂,于是消息轉(zhuǎn)發(fā)過程結(jié)束。
??若沒有“備援的接收者”刽漂,則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制演训,運(yùn)行時(shí)系統(tǒng)會(huì)把與消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對(duì)象中,再給接收者最后一次機(jī)會(huì)贝咙,令其設(shè)法解決當(dāng)前還沒處理的這條消息仇祭。 -
動(dòng)態(tài)方法解析
對(duì)象在收到無法解讀的消息后,首先將調(diào)用其所屬類的下列類方法:+ (BOOL)resolveInstanceMethod:(SEL)selector
解釋:selector是未知的選擇器颈畸,返回值為Boolean類型乌奇,表示這個(gè)類是否能新增一個(gè)實(shí)例方法用以處理此選擇器。
在繼續(xù)往下執(zhí)行轉(zhuǎn)發(fā)之前眯娱,本類有機(jī)會(huì)新增一個(gè)處理未知選擇器的方法礁苗,便是通過調(diào)用“resolveInstanceMethod:”或“resolveClassMethod:”方法來實(shí)現(xiàn)的。
但是徙缴,使用這種辦法有個(gè)前提:相關(guān)方法的實(shí)現(xiàn)代碼已經(jīng)寫好试伙,只等著運(yùn)行的時(shí)候動(dòng)態(tài)插在類里面就可以了。此方案常用來實(shí)現(xiàn)@dynamic屬性于样。
id autoDictionaryGetter(id self, SEL _cmd); void autoDictionarySetter(id self, SEL _cmd, id value); + (BOOL)resolveInstanceMethod:(SEL)selector{ NSString *selectorString = NSStringFromSelector(selector);// 將選擇器轉(zhuǎn)換為字符串 if(/* selector is from a @dynamic property */){// 使用了@dynamic屬性 if([selectorString hasPrefix:@"set"]){ class_addMethod(self, selector, (IMP)autoDictionarySetter, "V@:@"); }else{ class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:"); } return YES; } return [super resolveInstanceMethod:selector]; }
-
備援接受者
在第二階段的第一小步中疏叨,運(yùn)行期系統(tǒng)會(huì)問未知的選擇器能不能把這條消息轉(zhuǎn)發(fā)給其他接收者來處理。與該步驟對(duì)應(yīng)的處理方法如下:
- (id)forwardingTargetForSelector:(SEL)selector
解釋:selector代表未知的選擇器穿剖,若當(dāng)前接收者能找到備援對(duì)象蚤蔓,則將其返回礁哄,若找不到讽坏,就返回nil撬陵。
??通過此方案慢味,可以用“組合”(composition)來模擬出“多重繼承”(multiple inheritance)的某些特性菲茬。
??注意:開發(fā)者無法操作經(jīng)由這一步所轉(zhuǎn)發(fā)的消息赶促。若是想在發(fā)送給備援接收者之前先修改消息內(nèi)容妒蔚,那就得通過完整的消息轉(zhuǎn)發(fā)機(jī)制來做了疮鲫。 -
完整的消息轉(zhuǎn)發(fā)
若沒有“備援的接收者”蘸劈,則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制昏苏,運(yùn)行時(shí)系統(tǒng)會(huì)把與尚未處理的那條消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對(duì)象中。
在觸發(fā)NSInvocation對(duì)象時(shí)威沫,“消息派發(fā)系統(tǒng)”(message-dispatch system)將親自出馬贤惯,把消息指派給目標(biāo)對(duì)象。如下:
- (void)forwardInvocation:(NSInvocation*)invacation
此方法比較有用的實(shí)現(xiàn)方式為:在觸發(fā)消息前壹甥,先以某種方式改變消息內(nèi)容救巷,比如追加另外一個(gè)參數(shù),或是改換選擇器句柠,等等浦译。
-
消息轉(zhuǎn)發(fā)全流程
-
以完整的例子演示動(dòng)態(tài)方法解析
MarkAutoDictionary頭文件#import <Foundation/Foundation.h> @interface MarkAutoDictionary : NSObject @property(nonatomic,strong) NSString *string; @property(nonatomic,strong) NSNumber *number; @property(nonatomic,strong) NSDate *date; @property(nonatomic,strong) id opaqueObject; @end
MarkAutoDictionary實(shí)現(xiàn)文件
#import "MarkAutoDictionary.h" #import <objc/runtime.h> @interface MarkAutoDictionary () @property(nonatomic,strong) NSMutableDictionary *backingStore; @end @implementation MarkAutoDictionary // @dynamic會(huì)阻止編譯器自動(dòng)生成相關(guān)的存取方法,而由開發(fā)者自己創(chuàng)建存取方法 @dynamic string, number, date, opaqueObject; - (id)init{ if (self = [super init]) { _backingStore = [NSMutableDictionary new]; // 延遲加載 } return self; } // 動(dòng)態(tài)添加新方法 + (BOOL)resolveInstanceMethod:(SEL)sel{ NSString *selectorString = NSStringFromSelector(sel); if ([selectorString hasPrefix:@"set"]) { class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@"); }else{ class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:"); } return YES; } // getter函數(shù) id autoDictionaryGetter(id self, SEL _cmd){ // 從MarkAutoDictionary對(duì)象獲取backingStore字典 EOCAutoDictionary *typedSelf = (MarkAutoDictionary*)self; NSMutableDictionary *backingStore = typedSelf.backingStore; // 將選擇器轉(zhuǎn)換為字符串溯职,并將其設(shè)為key NSString *key = NSStringFromSelector(_cmd); // 返回backingStore字典中key所對(duì)應(yīng)的值 return [backingStore objectForKey:key]; } // setter函數(shù) void autoDictionarySetter(id self, SEL _cmd, id value){ // 從MarkAutoDictionary對(duì)象獲取backingStore字典 MarkAutoDictionary *typedSelf = (MarkAutoDictionary*)self; NSMutableDictionary *backingStore = typedSelf.backingStore; // 將選擇器轉(zhuǎn)換為字符串精盅,并將其拷貝為可變字符串 NSString *selectorString = NSStringFromSelector(_cmd); NSMutableString *key = [selectorString mutableCopy]; // 移除key中尾部的“:” [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)]; // 移除key中前面的“set” [key deleteCharactersInRange:NSMakeRange(0, 3)]; // 取出現(xiàn)有的key中的首字母,將其小寫化并替代掉原來的首字母 NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString]; [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar]; // 根據(jù)key給backingStore存儲(chǔ)相關(guān)的值 if (value) { [backingStore setObject:value forKey:key]; }else{ [backingStore removeObjectForKey:key]; } } @end
main函數(shù):
int main(int argc, const char * argv[]) { @autoreleasepool { EOCAutoDictionary *autoDict = [EOCAutoDictionary new]; // autoDict.date == [autoDict setDate] // 由于接收者沒有相應(yīng)的方法可調(diào)用谜酒,因?yàn)锧dynamic特性叹俏,所以可以動(dòng)態(tài)新增方法 autoDict.date = [NSDate dateWithTimeIntervalSince1970:3140907998]; NSLog(@"%@",autoDict.date); } return 0; }
輸出結(jié)果為:
2016-03-09 20:29:25.552 第12條.演示動(dòng)態(tài)方法解析[7000:347059] 2069-07-13 02:26:38 +0000 Program ended with exit code: 0
總結(jié):要想添加新屬性,只需要用@property來定義僻族,并將其聲明為@dynamic即可粘驰。
要點(diǎn)
- 若對(duì)象無法響應(yīng)某個(gè)選擇器屡谐,則進(jìn)入消息轉(zhuǎn)發(fā)流程。
- 通過運(yùn)行期的動(dòng)態(tài)方法解析功能蝌数,我們可以在需要用到某個(gè)方法時(shí)再將其加入類中愕掏。
- 對(duì)象可以把其無法解讀的某些選擇器轉(zhuǎn)交給其他對(duì)象(備援接收者)來處理。
- 經(jīng)過上述兩步之后顶伞,如果還是沒辦法處理選擇器饵撑,那就啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制。
第13條:用“方法調(diào)配技術(shù)”調(diào)試“黑盒方法”
在給定的選擇子名稱相對(duì)應(yīng)的方法也可以在運(yùn)行期改變唆貌,若能善用此特性滑潘,則可發(fā)揮巨大優(yōu)勢(shì),因?yàn)槲覀?strong>既不需要源代碼锨咙,也不需要通過繼承子類來覆寫方法就能改變這個(gè)類本身的功能语卤。這么一來,新功能將在本類的所有實(shí)例中生效蓖租,而不是僅限于覆寫了相關(guān)方法的那些子類實(shí)例粱侣。此方案經(jīng)常稱為“方法調(diào)配”(method swizzling)。
-
實(shí)現(xiàn)原理
類的方法列表會(huì)把選擇器的名稱映射到相關(guān)的方法實(shí)現(xiàn)之上蓖宦,使得“動(dòng)態(tài)消息派發(fā)系統(tǒng)”能夠據(jù)此找到應(yīng)該調(diào)用的方法齐婴。這些方法均以函數(shù)指針(IMP)的形式來表示,其原型如下:id (*IMP)(id, SEL, ...)
以NSString類的選擇器映射表為例 圖例如下:
Objective-C運(yùn)行時(shí)系統(tǒng)提供的幾個(gè)方法都能夠用來操作這種表。開發(fā)者可以向其中新增選擇器稠茂,也可以改變某選擇器所對(duì)應(yīng)的方法實(shí)現(xiàn)柠偶,還可以交換兩個(gè)選擇器所映射的指針。
Method class_getInstanceMethod(Class aClass, SEL aSelector) :根據(jù)給定的選擇器從類中取出與之相關(guān)的方法睬关。
void method_exchangeImplementations( Method m1, Method m2):可以交換兩個(gè)方法實(shí)現(xiàn)诱担。
-
應(yīng)用案例
編寫一個(gè)方法,在此方法中實(shí)現(xiàn)所需的附加功能电爹,并調(diào)用原有實(shí)現(xiàn)蔫仙。
分類 NSString (MarkMyAdditions)頭文件:@interface NSString (MarkMyAdditions) - (NSString*)mark_myLowercaseString; @end
分類 NSString (MarkMyAdditions)實(shí)現(xiàn)文件:
@implementation NSString (MarkMyAdditions) // 新增一個(gè)方法,實(shí)現(xiàn)附加功能 - (NSString *)mark_myLowercaseString{ NSString *lowercase = [self eoc_myLowercaseString]; NSLog(@"%@ => %@", self, lowercase); return lowercase; } @end
main函數(shù):
#import <Foundation/Foundation.h> #import <objc/runtime.h> #import "NSString+MarkMyAdditions.h" int main(int argc, const char * argv[]) { @autoreleasepool { // 1.交換方法 Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString)); Method swappedMethod = class_getInstanceMethod([NSString class], @selector(mark_myLowercaseString)); // 2.交換方法 method_exchangeImplementations(originalMethod, swappedMethod); // 3.調(diào)用lowercaseString方法丐箩,但實(shí)現(xiàn)的功能卻是分類中新增方法的附加功能 NSString *string = @"THIs is tHe StRiNg"; NSString *lowercaseString = [string lowercaseString]; } return 0; }
輸出結(jié)果:
THIs is tHe StRiNg => this is the string
總結(jié):通過此方案摇邦,開發(fā)者可以為那些“完全不知道其具體實(shí)現(xiàn)的”黑盒方法增加日志記錄功能,這非常有助于程序調(diào)試屎勘。然而施籍,此做法只在調(diào)試程序時(shí)有用,禁止濫用概漱。
要點(diǎn)
- 在運(yùn)行期丑慎,可以向類中新增或替換選擇器所對(duì)應(yīng)的方法實(shí)現(xiàn)。
- 使用另一份實(shí)現(xiàn)來替換原有的方法實(shí)現(xiàn),這道工序叫做“方法調(diào)配”竿裂,開發(fā)者常用此技術(shù)向原有實(shí)現(xiàn)中添加新功能玉吁。
- 一般來說,只有調(diào)試程序的時(shí)候才需要在運(yùn)行期修改方法實(shí)現(xiàn)铛绰,這種做法不宜濫用诈茧。
第14條:理解“類對(duì)象”的用意
-
在運(yùn)行期檢視對(duì)象類型的操作叫做“類型信息查詢”(introspection,內(nèi)饰骊)。在程序中不要直接比較對(duì)象所屬的類曾沈,明智的做法是調(diào)用“類型信息查詢方法”这嚣。
typedef struct objc_object *id; struct objc_object { Class isa; }; // 等價(jià)于 typedef sturct objc_object { Class isa; } *id;
解釋:該結(jié)構(gòu)體描述了Objective-C對(duì)象所用的數(shù)據(jù)結(jié)構(gòu)。其中塞俱,isa指針定義了對(duì)象所屬的類姐帚。
typedef struct objc_class *Class; struct objc_class { Class isa; Class super_class; const char *name; long version; long info; long instance_size; struct objc_ivar_list *ivars; #if defined(Release3CompatibilityBuild) struct objc_method_list *methods; #else struct objc_method_list **methodLists; #endif struct objc_cache *cache; struct objc_protocol_list *protocols; };
解釋:該結(jié)構(gòu)體存放類的“元數(shù)據(jù)”。其中障涯,isa指針定義了另外一個(gè)類——元類(metaclass)罐旗,用來表述類對(duì)象本身所具備的元數(shù)據(jù)。super_class定義了本類的超類唯蝶。
類方法 :類方法可以理解成類對(duì)象的實(shí)例方法九秀,每個(gè)類僅有一個(gè)“類對(duì)象”,而每個(gè)“類對(duì)象”僅有一個(gè)與之相關(guān)的“元類”粘我。 -
類繼承體系 圖例
總結(jié):通過這張布局關(guān)系圖即可執(zhí)行“類型信息查詢”鼓蜒。開發(fā)者可以查出對(duì)象是否能響應(yīng)某個(gè)選擇器,是否遵從某項(xiàng)協(xié)議征字,并且能看出此對(duì)象位于“類繼承體系”(class hierarchy)的哪一部分都弹。
-
在類繼承體系中查詢類型信息
-
類型信息查詢方法
可以用類型信息查詢方法來檢視類繼承體系。
“isMemberOfClass:”能夠判斷出對(duì)象是否為某個(gè)特定類的實(shí)例匙姜,而“isKindOfClass:”則能夠判斷出對(duì)象是否為某類或其派生類的實(shí)例畅厢。 -
等同性判斷方法
使用“==”操作符來比較類對(duì)象是否等同。原因在于氮昧,類對(duì)象是“單例”(singleton)框杜,在應(yīng)用程序范圍內(nèi),每個(gè)類的Class僅有一個(gè)實(shí)例郭计,也就是說霸琴,借助“==”操作符可以精確判斷出對(duì)象是否為某類實(shí)例。如:
id object = /* ... */; if ([object class] == [EOCSomeClass class]){ // 'object' is an instance of EOCSomeClass }
總結(jié):應(yīng)該盡量使用類型信息查詢方法昭伸,而不應(yīng)該直接比較兩個(gè)類對(duì)象是否等同梧乘。
要點(diǎn)
- 每個(gè)實(shí)例都有一個(gè)指向Class對(duì)象的指針,用以表明其類型,而這些Class對(duì)象則構(gòu)成了類的繼承體系选调。
- 如果對(duì)象類型無法在編譯期確定夹供,那么就應(yīng)該使用類型信息查詢方法(內(nèi)省)來探知仁堪。
- 盡量使用類型信息查詢方法來確定對(duì)象類型哮洽,而不要直接比較類對(duì)象,因?yàn)槟承?duì)象可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能弦聂。
-