使用 Objectvice-C 進(jìn)行全面對(duì)象編程時(shí)囊扳,除了需要知道語(yǔ)言本身的語(yǔ)法和面向?qū)ο蟮闹R(shí)外,還需要了解Objectvice-C的根類 NSObject 的信息扛门。
NSObject
根類的作用
作為一門(mén)動(dòng)態(tài)編程語(yǔ)言熬荆,Objectstvice-C有很多動(dòng)態(tài)的特性讥此,因此悔醋,Objectvice-C不進(jìn)需要編譯環(huán)境摩窃,同時(shí)還需要一個(gè)運(yùn)行時(shí)系統(tǒng)(runtime system)來(lái)執(zhí)行編譯好的代碼。運(yùn)行時(shí)系統(tǒng)扮演的角色類似于Objectvice-C的操作系統(tǒng)芬骄,他負(fù)責(zé)完成對(duì)象生成猾愿、釋放時(shí)的內(nèi)存管理、發(fā)來(lái)的消息查找對(duì)應(yīng)的處理方法等工作账阻。
通常情況下蒂秘,程序無(wú)法直接使用運(yùn)行時(shí)系統(tǒng)提供的功能。根類方法提供了運(yùn)行時(shí)系統(tǒng)的基本工恩給你淘太。繼承了 NSObject 的所有類都可以自由的使用運(yùn)行時(shí)系統(tǒng)的功能姻僧,也就是說(shuō),根類就想到于系統(tǒng)的一個(gè)借口蒲牧。
根類通過(guò)哪些方式提供了哪些功能對(duì)系統(tǒng)有很大的影響撇贺。因此,根類不同的系統(tǒng)之間是無(wú)法開(kāi)發(fā)出通用的程序的造成。
Cocoa 是以O(shè)PENSTEPDE的核心 API 為基礎(chǔ)發(fā)展起來(lái)的显熏。OPENSTEP的前身為 NeXTstep雄嚣。在 NeXTstep 時(shí)代晒屎,根類是累 Object,而在 OPENSTEP 時(shí)代喘蟆,根類則變?yōu)榱?NSObject,同時(shí)類的設(shè)計(jì)也得到了大幅度的改進(jìn)鼓鲁。
NSArray,NSString 等等NS前綴類蕴轨、函數(shù)歸屬于cocoa Fundation基礎(chǔ)類庫(kù),其"NS”的由來(lái)?yè)?jù)說(shuō)是這樣的:?jiǎn)滩妓贡惶O(píng)果開(kāi)除后,創(chuàng)立了NeSt公司,而cocoa Fundation基礎(chǔ)類庫(kù)就是出自于NeST公司,NeST中的"NS"被作為Fundation中所有成員的前綴
類和實(shí)例
NSObject 只是一個(gè)實(shí)例變量,就是 Class 類型的變量 isa骇吭。isa 用于表示實(shí)例對(duì)象屬于哪個(gè)類對(duì)象橙弱。因?yàn)?isa 決定著實(shí)例變量和類的關(guān)系,非常重要燥狰,所以子類不可以修改 isa 的值棘脐。另外,也不能通過(guò)直接訪問(wèn) isa 來(lái)查詢實(shí)例變量到底屬于哪個(gè)類龙致,而是要通過(guò)實(shí)例方法 class 來(lái)完成查詢蛀缝。
在運(yùn)行時(shí)的代碼中我們可以查看到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;
};
下面對(duì)類和實(shí)例變量的相關(guān)方法進(jìn)行說(shuō)明。NSObject 的方法與其說(shuō)是為自己定義的目代,不如說(shuō)是為了其子類和所有實(shí)例對(duì)象而定義的屈梁。
- (class) class
返回消息接收者所屬類的類對(duì)象
+ (class) class
返回類型對(duì)
雖然可以使用類名作為消息的接受者來(lái)調(diào)用類方法,但類對(duì)象是其他消息的參數(shù)榛了,或者將類對(duì)象賦值給變量的時(shí)候在讶,需要通過(guò)這個(gè)方法來(lái)獲取類的參數(shù)
- (id) self
返回接受者自身。是一個(gè)無(wú)任何實(shí)際動(dòng)作但很有用的方法霜大。
-(BOOL) isMemberOfClass: (Class) aClass
判斷消息接受者是不是參數(shù) aClass 類的對(duì)象
-(BOOL) isKindOfClass: (Class) aClass
判斷消息接受者是否是參數(shù) aClass 類或者 aClass 的子類的實(shí)例构哺。這個(gè)函數(shù)和 isMemberOfClass:的區(qū)別在于當(dāng)消息的接受者是 aClass 的子類的實(shí)例時(shí)也返回 YES。
- (BOOL) isSubclassOfClass: (Class) aClass
判斷消息接受者是不是參數(shù) aClass 的子類或自身战坤,如果是則返回 YES
- (Class) superclass
返回消息接受者所在類的父類的類對(duì)象遮婶。
+ (Class) superclass
返回消息接收類的父類和類對(duì)象
實(shí)例對(duì)象的生成和釋放
+ (id) alloc
生成消息接收類的實(shí)例對(duì)象。通常和 init 或者 init 開(kāi)頭的方法連用湖笨,生成實(shí)例化對(duì)象的同事需要對(duì)其進(jìn)行初始化旗扑。子類中不潤(rùn)徐重寫(xiě) alloc 方法
+ (void) dealloc
釋放實(shí)例對(duì)象。dealloc 被稱之為 release 的結(jié)果調(diào)用慈省。除了在子類中重寫(xiě) dealloc 的情況之外臀防,程序不潤(rùn)徐直接調(diào)用 dealloc
- (oneway void)release
將消息接受者的引用計(jì)數(shù)減1.引用計(jì)數(shù)變?yōu)?時(shí),dealloc 方法被調(diào)用边败,消息接受者被釋放
- (id)retain
為消息接收者的引用計(jì)數(shù)加1袱衷,同事返回消息接收者
- (id)autorelease
把消息的接受者加入到自動(dòng)釋放池中,同事返回消息接受者
- (NSUinteger) retainCount
返回消息接受者的引用計(jì)數(shù)笑窜,可在調(diào)試時(shí)使用這個(gè)方法致燥。NSUInteger 是無(wú)符號(hào)證書(shū)類型
- (void)finalize
垃圾收集器在釋放接受者對(duì)象之前會(huì)執(zhí)行該 finalize 方法。
上面從 dealloc 到 retainCount 都是手動(dòng)引用計(jì)數(shù)管理內(nèi)存時(shí)使用的方法排截,使用 ARC 時(shí)不可用嫌蚤。finalize 僅供垃圾回收有效時(shí)使用辐益。
初始化
- (id) init
init 可對(duì) alloc 生成的實(shí)例對(duì)象進(jìn)行初始化。子類中可以重寫(xiě) init 或者自定義的心的以 init 開(kāi)頭的初始化函數(shù)脱吱。
+ (void)initialize
被用于類的初始化智政,也就是對(duì)類中共同使用的變量進(jìn)行初始化設(shè)定等。這個(gè)方法會(huì)在類收到第一個(gè)消息之前被自動(dòng)執(zhí)行箱蝠,不需手動(dòng)調(diào)用
+ (id) new
new 是 alloc 和 init 的組合续捂。new 方法返回的實(shí)例對(duì)象的所有者就是調(diào)用 new 方法的對(duì)象。但是把 alloc 和 init 組合定義為 new 沒(méi)有什么優(yōu)點(diǎn)宦搬,并不建議使用牙瓢。
根據(jù)類的實(shí)現(xiàn)不同,new 方法并不會(huì)每次都返回一個(gè)全新的實(shí)例對(duì)象间校。有時(shí)new 方法會(huì)返回對(duì)象池中預(yù)先生成的對(duì)象一罩,也有可能每次都返回同一個(gè)對(duì)象。
對(duì)象的比較
-(BOOL) isEqual: (id) anObject
消息的接受者如果和參數(shù) anObject 相等則返回 YES
- (NSUInteger) hash
把對(duì)象放入容器的時(shí)候撇簿,返回系統(tǒng)內(nèi)部用的散列表
原則上來(lái)講聂渊,具有相同 id 值也就是同一個(gè)指針指向的對(duì)象被認(rèn)為是相等的。而子類在這個(gè)基礎(chǔ)上進(jìn)行了擴(kuò)展四瘫,把擁有相同值認(rèn)為是相等汉嗽。我們可以根據(jù)需求對(duì)“想通知”激進(jìn)型定義,但一般都會(huì)讓具備“相同值”的對(duì)象返回相同的散列表找蜜,因此就需要對(duì)散列表方法進(jìn)行重新定義饼暑。而反之則并不成立,也就是說(shuō)洗做,散列值相等的兩個(gè)對(duì)象不一定相等弓叛。
另外,有的累中還自定義了 compare:或者isEqualTo 之類的方法诚纸。至于到底是哪個(gè)方法或者自定義類的時(shí)候是否需要定義比較的方法撰筷,都需要根據(jù)目的和類的內(nèi)容做具體分析。
對(duì)象的內(nèi)容描述
+ (NSString*) description
返回一個(gè) NSString 類型的字符串畦徘,表示消息接受者所屬類的內(nèi)容毕籽。通常是這個(gè)類的名稱。
- (NSString*)description
返回一個(gè) NSString 類型的字符串井辆,表示消息接受者的實(shí)例對(duì)象的內(nèi)容关筒。通常是類名家 id 值。子類中可以重新定義 description 的返回值杯缺。例如 NSString的實(shí)例會(huì)返回字符串的內(nèi)容蒸播,NSArray 的實(shí)例會(huì)對(duì)數(shù)組中的每一個(gè)元素調(diào)用 description,然后將調(diào)用結(jié)果用句號(hào)進(jìn)行分割萍肆,并且一起返回袍榆。
消息發(fā)送機(jī)制
選擇器和 SEL 類型
至今為止我們把選擇器(方法名)和消息關(guān)鍵字放在一起進(jìn)行說(shuō)明胀屿。程序中的方法名(選擇器)在便有被一個(gè)內(nèi)部標(biāo)識(shí)符所代替,這個(gè)內(nèi)部標(biāo)識(shí)符所對(duì)應(yīng)的數(shù)據(jù)類型就是 SEL 類型蜡塌。
Objective-C為了能夠在程序中操作編譯后的選擇器碉纳,定義了@selector()指令勿负。通過(guò)使用@selector()指令馏艾,就可以直接飲用編譯后的選擇器。使用方法如下:
@selector(mutableCopy)
@selector(compare:)
@selector(replaceObjectAtIndex:withObject:)
也可以聲明 SEL 類型的變量
選擇器不同的情況下奴愉,編譯器轉(zhuǎn)換后生成的 SEL 類型的值也一定不同琅摩,相同的算擇期對(duì)應(yīng)的 SEL 類型的值一定相同。Objective-C的程序員不需要知道選擇器對(duì)應(yīng)的 SEL 類型的值到底是什么锭硼,具體的值是和處理器相關(guān)的房资。但是如果 SEL 類型的便利功能無(wú)效的話,可設(shè)其為 NULL檀头,或者也可以使用(SEL)0這種常見(jiàn)的表達(dá)方式
可以使用 SEL 類型的變量來(lái)發(fā)送消息轰异,為此,NSObject 中準(zhǔn)備了如下方法
-(id)performSelector: (SEL) aSelector
向消息的接收者發(fā)送 aSelector代表的消息暑始,返回這個(gè)消息的執(zhí)行結(jié)果
-(id)performSelector: (SEL) aSelector
withObject: (id) anObject
與上面的方法一直搭独,不過(guò)可以傳遞參數(shù)
例如下面兩個(gè)消息表達(dá)式進(jìn)行的處理是相同的
[target description]
[targ performSelector: @selector(description)];
下面的例子展示了如何根據(jù)條件動(dòng)態(tài)決定執(zhí)行那個(gè)方法
SEL method = (void1) ? @selector(activate:) : @selector(hide:);
id obj = (cond2) ? MyDocument : defaultDocument;
[target performSelector:method withObject:obj]
這種調(diào)用方式很想 C 語(yǔ)言中函數(shù)指針的用法,使用函數(shù)指針可以實(shí)現(xiàn)和上面的程序同樣的功能廊镜。
函數(shù)指針是函數(shù)在內(nèi)存中的地址牙肝。指針對(duì)應(yīng)的函數(shù)是在編譯的時(shí)候決定的,不能夠執(zhí)行指定之外的函數(shù)嗤朴。SEL 類型就相當(dāng)于方法名配椭,根據(jù)消息接受者的不同(上例子中 target 的賦值),來(lái)動(dòng)態(tài)執(zhí)行不同的方法雹姊。
通過(guò) SEL 類型來(lái)指定要中子星的方法股缸,這就是 Objectivce-C消息發(fā)送的方式。也正是通過(guò)這種方法才實(shí)現(xiàn)了 Objectivce-C的動(dòng)態(tài)性
消息搜索
對(duì)象收到一個(gè)消息后執(zhí)行哪個(gè)方法是動(dòng)態(tài)決定的吱雏。
所有的實(shí)例變量都存在一個(gè) Class 類型的 isa 變量乓序,它就是類對(duì)象。當(dāng)收到消息后坎背,運(yùn)行時(shí)系統(tǒng)會(huì)檢查類內(nèi)是否有和這個(gè)消息選擇器相同的方法替劈,如果有就執(zhí)行對(duì)應(yīng)的方法,如果沒(méi)有就通過(guò)類對(duì)象中指向父類的指針來(lái)查找父類中是否有對(duì)應(yīng)的方法得滤。如果一直找到根類都沒(méi)有找到對(duì)應(yīng)的方法陨献,就會(huì)提示執(zhí)行時(shí)錯(cuò)誤。
如果每次收到消息都需要查找對(duì)應(yīng)的方法的話懂更,消息發(fā)送過(guò)程的開(kāi)銷就會(huì)很大眨业,是針對(duì)這種情況急膀,運(yùn)行時(shí)內(nèi)部會(huì)緩存一個(gè)散列表,表中記錄著某個(gè)類擁有和什么樣的選擇器相對(duì)應(yīng)的方法龄捡、方法被定義在何處等信息卓嫂。這樣一來(lái),當(dāng)下次在收到同樣的消息時(shí)聘殖,直接利用上次緩存好的信息即可晨雳。
NSObject 中定義了可以動(dòng)態(tài)檢查一個(gè)對(duì)象是否能夠響應(yīng)某個(gè)選擇器的方法。
- (BOOL) respondsToSelector: (SEL) aSelector
查詢消息的接收者中是否能夠響應(yīng) aSelector 的方法奸腺,包括從父類繼承來(lái)的方法餐禁,如果存在的話則返回 YES
- (BOOL) instancesRespondToSelector: (SEL) aSelector
查詢消息的接收者所屬的類中是否能夠響應(yīng) aSelector 的實(shí)例方法,如果存在的話則返回 YES
一函數(shù)的形式來(lái)調(diào)用方法
類中定義的方法通常是以函數(shù)的形式來(lái)實(shí)現(xiàn)的突照,但通常在編程的時(shí)候并不會(huì)直接操作方法所對(duì)應(yīng)的函數(shù)帮非。
但如果想盡可能第讓程序更快一點(diǎn),或者需要按照 C 語(yǔ)言的管理傳遞函數(shù)指針的時(shí)候讹蘑,可以直接調(diào)用方法對(duì)應(yīng)的函數(shù)末盔,以節(jié)省發(fā)送消息的開(kāi)銷。另外執(zhí)行時(shí)動(dòng)態(tài)加載方法的定義等時(shí)座慰,也可以將方法作為函數(shù)調(diào)用陨舱。但是需要注意的是,如果以函數(shù)的形式來(lái)調(diào)用方法的話角骤,將無(wú)法利用面向?qū)ο蟮膭?dòng)態(tài)綁定等功能隅忿。雖然消息發(fā)送同函數(shù)調(diào)用相比確實(shí)慢一點(diǎn),但卻有面向?qū)ο蟮膭?dòng)態(tài)綁定邦尊、多態(tài)等優(yōu)點(diǎn)背桐。同這些優(yōu)點(diǎn)相比,速度上略微的損失是不值得一提的蝉揍。而其實(shí)消息發(fā)送的速度也非常的快链峭。
通過(guò)使用下面的方法,可以獲得某個(gè)對(duì)象持有的方法的函數(shù)指針又沾,這些方法都被定義在 NSObject 中弊仪。
- (IMP) methodForSelector: (SEL) aSelector
搜索和執(zhí)行選擇器對(duì)應(yīng)的方法,并返回指向該方法實(shí)現(xiàn)的函數(shù)指針杖刷。實(shí)例對(duì)象和類對(duì)象都可以使用這個(gè)方法励饵。對(duì)實(shí)例對(duì)象使用時(shí),會(huì)返回實(shí)例方法對(duì)應(yīng)的函數(shù)滑燃,對(duì)類對(duì)象使用時(shí)役听,會(huì)返回類對(duì)象對(duì)應(yīng)的函數(shù)
+ (IMP) instanceMethodForSelector: (SEL)aSelector;
搜索和制定選擇器相對(duì)應(yīng)的實(shí)例方法,并返回該指向?qū)嵗椒▽?shí)現(xiàn)的函數(shù)指針
IMP 是“implemention”的縮寫(xiě),它是一個(gè)函數(shù)指針典予,指向了方法實(shí)現(xiàn)代碼的入口
IMP 的定義為:
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
這個(gè)被指向的函數(shù)包括 id(self 指針)甜滨、調(diào)用的 SEL(方法名),以及其他參數(shù)
例如:
- (id)setBox:(id)obj1 title:(id)obj2;
foo 是這個(gè)方法所屬類的一個(gè)實(shí)例變量瘤袖。獲取指向 setBox 的函數(shù)指針衣摩,并且通過(guò)該指針進(jìn)行函數(shù)調(diào)用的過(guò)程如下:
IMP funcp;
funcp = [foo methodForSelector:@selector[setBox:title]];
xyz = (*funcp)(foo,@selector[setBox:title:],param1,param2)
類對(duì)象和跟對(duì)象
因?yàn)槔蹖?duì)象也是一個(gè)對(duì)象,所以累對(duì)象可以作為根類 NSObject 的某個(gè)子類的對(duì)象來(lái)使用捂敌。下面這句話看上去好像比較奇怪艾扮,但是實(shí)際上他是正確的,會(huì)返回 YES
[NSString class] isKindOfClass:[NSObject class]]
這就說(shuō)明了相當(dāng)于類對(duì)象的類的對(duì)象是存在的黍匾。而類對(duì)象的類就被叫做元類(metaclass)栏渺。實(shí)例對(duì)象(instance object)所屬的類是 class呛梆,類對(duì)象(class object)所屬的類是 metaclass锐涯。
Objective-C 中的很多概念都來(lái)源于 Smalltalk,元類的概念就是其中之一填物。但現(xiàn)在的 Objective-C中已經(jīng)不存在元類的概念了纹腌,程序中不能操作元類。用于表示對(duì)象的 id 類型和表示類的 Class 類實(shí)際上都是指向結(jié)構(gòu)體的指針滞磺。被詳細(xì)定義在/usr/include/objc 下面的 objc.h 頭文件中升薯。通過(guò)查看 objc.h 中 id和 Class 的定義,就會(huì)發(fā)現(xiàn)類和元類的關(guān)系如圖所示击困。Objective-C2.0更新了基本的數(shù)據(jù)結(jié)構(gòu)涎劈,但是沒(méi)有改變類和元類的關(guān)系。
類 A 是 NSObject 的子類阅茶,類 B是 A 的子類蛛枚。類對(duì)象和實(shí)例對(duì)象都存在一個(gè)成員變量 isa,它是一個(gè) objc_class 類型的指針
圖中帶有 isa 的實(shí)現(xiàn)表明了 isa 指向的對(duì)象脸哀,帶有 super_class 的虛線則表明了父類的關(guān)系蹦浦。
類對(duì)象中保存的是實(shí)例方法,元對(duì)象中保存的是累方法撞蜂。通過(guò)這樣的定義能夠統(tǒng)一實(shí)現(xiàn)實(shí)例方法和類方法的調(diào)用機(jī)制盲镶。
因?yàn)榫幊虝r(shí)不可以直接操作元類,所以并不需要完全了解圖中的細(xì)節(jié)蝌诡。大家只需要記住任何一個(gè)類對(duì)象都是繼承了根類的元對(duì)象的一個(gè)實(shí)例即可溉贿。也就是說(shuō),類對(duì)象可以執(zhí)行根類對(duì)象的實(shí)例方法浦旱。
例如宇色,類對(duì)象可以執(zhí)行 NSObject 的實(shí)例方法 performSelector:和 respondsToSelector:。當(dāng)然提前是沒(méi)有將這些方法作為類方法再次定義。
下面讓我們總結(jié)一下代兵。其中(1)和(2)我們已經(jīng)介紹過(guò)了尼酿。(3)比較不容易理解,
- 所有類的實(shí)例對(duì)象都可以執(zhí)行根類的實(shí)例方法
- 如果在派生類中重新定義了實(shí)例方法植影,新定義的方法會(huì)被執(zhí)行
- 所有類的類對(duì)象都可以執(zhí)行根類的類方法
- 如果在派生類中重新定義了類方法裳擎,新定義的方法會(huì)被執(zhí)行
- 所有類的類對(duì)象都可以執(zhí)行根類的實(shí)例方法
- 即使在派生類中重新定義了實(shí)例方法,根類中的方法也會(huì)被執(zhí)行
- 如果在派生類中將實(shí)例方法作為類方法重新定義了的話思币,新定義的方法會(huì)被執(zhí)行
參考文獻(xiàn)
① Objectivce-C編程全解(第三版) [日]荻原剛志 著 唐璐 翟俊杰 譯
更多內(nèi)容請(qǐng)關(guān)注我的獨(dú)立博客:http://www.aircrayon.xyz