使用 Objectvice-C 進(jìn)行全面對象編程時,除了需要知道語言本身的語法和面向?qū)ο蟮闹R外,還需要了解Objectvice-C的根類 NSObject 的信息。
NSObject
根類的作用
作為一門動態(tài)編程語言,Objectstvice-C有很多動態(tài)的特性,因此,Objectvice-C不進(jìn)需要編譯環(huán)境,同時還需要一個運行時系統(tǒng)(runtime system)來執(zhí)行編譯好的代碼。運行時系統(tǒng)扮演的角色類似于Objectvice-C的操作系統(tǒng),他負(fù)責(zé)完成對象生成、釋放時的內(nèi)存管理、發(fā)來的消息查找對應(yīng)的處理方法等工作。
通常情況下,程序無法直接使用運行時系統(tǒng)提供的功能。根類方法提供了運行時系統(tǒng)的基本工恩給你。繼承了 NSObject 的所有類都可以自由的使用運行時系統(tǒng)的功能,也就是說,根類就想到于系統(tǒng)的一個借口。
根類通過哪些方式提供了哪些功能對系統(tǒng)有很大的影響。因此,根類不同的系統(tǒng)之間是無法開發(fā)出通用的程序的。
Cocoa 是以O(shè)PENSTEPDE的核心 API 為基礎(chǔ)發(fā)展起來的。OPENSTEP的前身為 NeXTstep。在 NeXTstep 時代,根類是累 Object,而在 OPENSTEP 時代,根類則變?yōu)榱?NSObject,同時類的設(shè)計也得到了大幅度的改進(jìn)。
NSArray,NSString 等等NS前綴類、函數(shù)歸屬于cocoa Fundation基礎(chǔ)類庫,其"NS”的由來據(jù)說是這樣的:喬布斯被蘋果開除后,創(chuàng)立了NeSt公司,而cocoa Fundation基礎(chǔ)類庫就是出自于NeST公司,NeST中的"NS"被作為Fundation中所有成員的前綴
類和實例
NSObject 只是一個實例變量,就是 Class 類型的變量 isa。isa 用于表示實例對象屬于哪個類對象。因為 isa 決定著實例變量和類的關(guān)系,非常重要,所以子類不可以修改 isa 的值。另外,也不能通過直接訪問 isa 來查詢實例變量到底屬于哪個類,而是要通過實例方法 class 來完成查詢。
在運行時的代碼中我們可以查看到objc_class的定義如下:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
下面對類和實例變量的相關(guān)方法進(jìn)行說明。NSObject 的方法與其說是為自己定義的,不如說是為了其子類和所有實例對象而定義的。
- (class) class
返回消息接收者所屬類的類對象
+ (class) class
返回類型對
雖然可以使用類名作為消息的接受者來調(diào)用類方法,但類對象是其他消息的參數(shù),或者將類對象賦值給變量的時候,需要通過這個方法來獲取類的參數(shù)
- (id) self
返回接受者自身。是一個無任何實際動作但很有用的方法。
-(BOOL) isMemberOfClass: (Class) aClass
判斷消息接受者是不是參數(shù) aClass 類的對象
-(BOOL) isKindOfClass: (Class) aClass
判斷消息接受者是否是參數(shù) aClass 類或者 aClass 的子類的實例。這個函數(shù)和 isMemberOfClass:的區(qū)別在于當(dāng)消息的接受者是 aClass 的子類的實例時也返回 YES。
- (BOOL) isSubclassOfClass: (Class) aClass
判斷消息接受者是不是參數(shù) aClass 的子類或自身,如果是則返回 YES
- (Class) superclass
返回消息接受者所在類的父類的類對象。
+ (Class) superclass
返回消息接收類的父類和類對象
實例對象的生成和釋放
+ (id) alloc
生成消息接收類的實例對象。通常和 init 或者 init 開頭的方法連用,生成實例化對象的同事需要對其進(jìn)行初始化。子類中不潤徐重寫 alloc 方法
+ (void) dealloc
釋放實例對象。dealloc 被稱之為 release 的結(jié)果調(diào)用。除了在子類中重寫 dealloc 的情況之外,程序不潤徐直接調(diào)用 dealloc
- (oneway void)release
將消息接受者的引用計數(shù)減1.引用計數(shù)變?yōu)?時,dealloc 方法被調(diào)用,消息接受者被釋放
- (id)retain
為消息接收者的引用計數(shù)加1,同事返回消息接收者
- (id)autorelease
把消息的接受者加入到自動釋放池中,同事返回消息接受者
- (NSUinteger) retainCount
返回消息接受者的引用計數(shù),可在調(diào)試時使用這個方法。NSUInteger 是無符號證書類型
- (void)finalize
垃圾收集器在釋放接受者對象之前會執(zhí)行該 finalize 方法。
上面從 dealloc 到 retainCount 都是手動引用計數(shù)管理內(nèi)存時使用的方法,使用 ARC 時不可用。finalize 僅供垃圾回收有效時使用。
初始化
- (id) init
init 可對 alloc 生成的實例對象進(jìn)行初始化。子類中可以重寫 init 或者自定義的心的以 init 開頭的初始化函數(shù)。
+ (void)initialize
被用于類的初始化,也就是對類中共同使用的變量進(jìn)行初始化設(shè)定等。這個方法會在類收到第一個消息之前被自動執(zhí)行,不需手動調(diào)用
+ (id) new
new 是 alloc 和 init 的組合。new 方法返回的實例對象的所有者就是調(diào)用 new 方法的對象。但是把 alloc 和 init 組合定義為 new 沒有什么優(yōu)點,并不建議使用。
根據(jù)類的實現(xiàn)不同,new 方法并不會每次都返回一個全新的實例對象。有時new 方法會返回對象池中預(yù)先生成的對象,也有可能每次都返回同一個對象。
對象的比較
-(BOOL) isEqual: (id) anObject
消息的接受者如果和參數(shù) anObject 相等則返回 YES
- (NSUInteger) hash
把對象放入容器的時候,返回系統(tǒng)內(nèi)部用的散列表
原則上來講,具有相同 id 值也就是同一個指針指向的對象被認(rèn)為是相等的。而子類在這個基礎(chǔ)上進(jìn)行了擴展,把擁有相同值認(rèn)為是相等。我們可以根據(jù)需求對“想通知”激進(jìn)型定義,但一般都會讓具備“相同值”的對象返回相同的散列表,因此就需要對散列表方法進(jìn)行重新定義。而反之則并不成立,也就是說,散列值相等的兩個對象不一定相等。
另外,有的累中還自定義了 compare:或者isEqualTo 之類的方法。至于到底是哪個方法或者自定義類的時候是否需要定義比較的方法,都需要根據(jù)目的和類的內(nèi)容做具體分析。
對象的內(nèi)容描述
+ (NSString*) description
返回一個 NSString 類型的字符串,表示消息接受者所屬類的內(nèi)容。通常是這個類的名稱。
- (NSString*)description
返回一個 NSString 類型的字符串,表示消息接受者的實例對象的內(nèi)容。通常是類名家 id 值。子類中可以重新定義 description 的返回值。例如 NSString的實例會返回字符串的內(nèi)容,NSArray 的實例會對數(shù)組中的每一個元素調(diào)用 description,然后將調(diào)用結(jié)果用句號進(jìn)行分割,并且一起返回。
消息發(fā)送機制
選擇器和 SEL 類型
至今為止我們把選擇器(方法名)和消息關(guān)鍵字放在一起進(jìn)行說明。程序中的方法名(選擇器)在便有被一個內(nèi)部標(biāo)識符所代替,這個內(nèi)部標(biāo)識符所對應(yīng)的數(shù)據(jù)類型就是 SEL 類型。
Objective-C為了能夠在程序中操作編譯后的選擇器,定義了@selector()指令。通過使用@selector()指令,就可以直接飲用編譯后的選擇器。使用方法如下:
@selector(mutableCopy)
@selector(compare:)
@selector(replaceObjectAtIndex:withObject:)
也可以聲明 SEL 類型的變量
選擇器不同的情況下,編譯器轉(zhuǎn)換后生成的 SEL 類型的值也一定不同,相同的算擇期對應(yīng)的 SEL 類型的值一定相同。Objective-C的程序員不需要知道選擇器對應(yīng)的 SEL 類型的值到底是什么,具體的值是和處理器相關(guān)的。但是如果 SEL 類型的便利功能無效的話,可設(shè)其為 NULL,或者也可以使用(SEL)0這種常見的表達(dá)方式
可以使用 SEL 類型的變量來發(fā)送消息,為此,NSObject 中準(zhǔn)備了如下方法
-(id)performSelector: (SEL) aSelector
向消息的接收者發(fā)送 aSelector代表的消息,返回這個消息的執(zhí)行結(jié)果
-(id)performSelector: (SEL) aSelector
withObject: (id) anObject
與上面的方法一直,不過可以傳遞參數(shù)
例如下面兩個消息表達(dá)式進(jìn)行的處理是相同的
[target description]
[targ performSelector: @selector(description)];
下面的例子展示了如何根據(jù)條件動態(tài)決定執(zhí)行那個方法
SEL method = (void1) ? @selector(activate:) : @selector(hide:);
id obj = (cond2) ? MyDocument : defaultDocument;
[target performSelector:method withObject:obj]
這種調(diào)用方式很想 C 語言中函數(shù)指針的用法,使用函數(shù)指針可以實現(xiàn)和上面的程序同樣的功能。
函數(shù)指針是函數(shù)在內(nèi)存中的地址。指針對應(yīng)的函數(shù)是在編譯的時候決定的,不能夠執(zhí)行指定之外的函數(shù)。SEL 類型就相當(dāng)于方法名,根據(jù)消息接受者的不同(上例子中 target 的賦值),來動態(tài)執(zhí)行不同的方法。
通過 SEL 類型來指定要中子星的方法,這就是 Objectivce-C消息發(fā)送的方式。也正是通過這種方法才實現(xiàn)了 Objectivce-C的動態(tài)性
消息搜索
對象收到一個消息后執(zhí)行哪個方法是動態(tài)決定的。
所有的實例變量都存在一個 Class 類型的 isa 變量,它就是類對象。當(dāng)收到消息后,運行時系統(tǒng)會檢查類內(nèi)是否有和這個消息選擇器相同的方法,如果有就執(zhí)行對應(yīng)的方法,如果沒有就通過類對象中指向父類的指針來查找父類中是否有對應(yīng)的方法。如果一直找到根類都沒有找到對應(yīng)的方法,就會提示執(zhí)行時錯誤。
如果每次收到消息都需要查找對應(yīng)的方法的話,消息發(fā)送過程的開銷就會很大,是針對這種情況,運行時內(nèi)部會緩存一個散列表,表中記錄著某個類擁有和什么樣的選擇器相對應(yīng)的方法、方法被定義在何處等信息。這樣一來,當(dāng)下次在收到同樣的消息時,直接利用上次緩存好的信息即可。
NSObject 中定義了可以動態(tài)檢查一個對象是否能夠響應(yīng)某個選擇器的方法。
- (BOOL) respondsToSelector: (SEL) aSelector
查詢消息的接收者中是否能夠響應(yīng) aSelector 的方法,包括從父類繼承來的方法,如果存在的話則返回 YES
- (BOOL) instancesRespondToSelector: (SEL) aSelector
查詢消息的接收者所屬的類中是否能夠響應(yīng) aSelector 的實例方法,如果存在的話則返回 YES
一函數(shù)的形式來調(diào)用方法
類中定義的方法通常是以函數(shù)的形式來實現(xiàn)的,但通常在編程的時候并不會直接操作方法所對應(yīng)的函數(shù)。
但如果想盡可能第讓程序更快一點,或者需要按照 C 語言的管理傳遞函數(shù)指針的時候,可以直接調(diào)用方法對應(yīng)的函數(shù),以節(jié)省發(fā)送消息的開銷。另外執(zhí)行時動態(tài)加載方法的定義等時,也可以將方法作為函數(shù)調(diào)用。但是需要注意的是,如果以函數(shù)的形式來調(diào)用方法的話,將無法利用面向?qū)ο蟮膭討B(tài)綁定等功能。雖然消息發(fā)送同函數(shù)調(diào)用相比確實慢一點,但卻有面向?qū)ο蟮膭討B(tài)綁定、多態(tài)等優(yōu)點。同這些優(yōu)點相比,速度上略微的損失是不值得一提的。而其實消息發(fā)送的速度也非常的快。
通過使用下面的方法,可以獲得某個對象持有的方法的函數(shù)指針,這些方法都被定義在 NSObject 中。
- (IMP) methodForSelector: (SEL) aSelector
搜索和執(zhí)行選擇器對應(yīng)的方法,并返回指向該方法實現(xiàn)的函數(shù)指針。實例對象和類對象都可以使用這個方法。對實例對象使用時,會返回實例方法對應(yīng)的函數(shù),對類對象使用時,會返回類對象對應(yīng)的函數(shù)
+ (IMP) instanceMethodForSelector: (SEL)aSelector;
搜索和制定選擇器相對應(yīng)的實例方法,并返回該指向?qū)嵗椒▽崿F(xiàn)的函數(shù)指針
IMP 是“implemention”的縮寫,它是一個函數(shù)指針,指向了方法實現(xiàn)代碼的入口
IMP 的定義為:
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
這個被指向的函數(shù)包括 id(self 指針)、調(diào)用的 SEL(方法名),以及其他參數(shù)
例如:
- (id)setBox:(id)obj1 title:(id)obj2;
foo 是這個方法所屬類的一個實例變量。獲取指向 setBox 的函數(shù)指針,并且通過該指針進(jìn)行函數(shù)調(diào)用的過程如下:
IMP funcp;
funcp = [foo methodForSelector:@selector[setBox:title]];
xyz = (*funcp)(foo,@selector[setBox:title:],param1,param2)
類對象和跟對象
因為累對象也是一個對象,所以累對象可以作為根類 NSObject 的某個子類的對象來使用。下面這句話看上去好像比較奇怪,但是實際上他是正確的,會返回 YES
[NSString class] isKindOfClass:[NSObject class]]
這就說明了相當(dāng)于類對象的類的對象是存在的。而類對象的類就被叫做元類(metaclass)。實例對象(instance object)所屬的類是 class,類對象(class object)所屬的類是 metaclass。
Objective-C 中的很多概念都來源于 Smalltalk,元類的概念就是其中之一。但現(xiàn)在的 Objective-C中已經(jīng)不存在元類的概念了,程序中不能操作元類。用于表示對象的 id 類型和表示類的 Class 類實際上都是指向結(jié)構(gòu)體的指針。被詳細(xì)定義在/usr/include/objc 下面的 objc.h 頭文件中。通過查看 objc.h 中 id和 Class 的定義,就會發(fā)現(xiàn)類和元類的關(guān)系如圖所示。Objective-C2.0更新了基本的數(shù)據(jù)結(jié)構(gòu),但是沒有改變類和元類的關(guān)系。

類 A 是 NSObject 的子類,類 B是 A 的子類。類對象和實例對象都存在一個成員變量 isa,它是一個 objc_class 類型的指針
圖中帶有 isa 的實現(xiàn)表明了 isa 指向的對象,帶有 super_class 的虛線則表明了父類的關(guān)系。
類對象中保存的是實例方法,元對象中保存的是累方法。通過這樣的定義能夠統(tǒng)一實現(xiàn)實例方法和類方法的調(diào)用機制。
因為編程時不可以直接操作元類,所以并不需要完全了解圖中的細(xì)節(jié)。大家只需要記住任何一個類對象都是繼承了根類的元對象的一個實例即可。也就是說,類對象可以執(zhí)行根類對象的實例方法。
例如,類對象可以執(zhí)行 NSObject 的實例方法 performSelector:和 respondsToSelector:。當(dāng)然提前是沒有將這些方法作為類方法再次定義。
下面讓我們總結(jié)一下。其中(1)和(2)我們已經(jīng)介紹過了。(3)比較不容易理解,
- 所有類的實例對象都可以執(zhí)行根類的實例方法
- 如果在派生類中重新定義了實例方法,新定義的方法會被執(zhí)行
- 所有類的類對象都可以執(zhí)行根類的類方法
- 如果在派生類中重新定義了類方法,新定義的方法會被執(zhí)行
- 所有類的類對象都可以執(zhí)行根類的實例方法
- 即使在派生類中重新定義了實例方法,根類中的方法也會被執(zhí)行
- 如果在派生類中將實例方法作為類方法重新定義了的話,新定義的方法會被執(zhí)行
參考文獻(xiàn)
① Objectivce-C編程全解(第三版) [日]荻原剛志 著 唐璐 翟俊杰 譯
更多內(nèi)容請關(guān)注我的獨立博客:http://www.aircrayon.xyz