先看下NSObject繼承圖.
說明:
- 本來想翻譯一下 但是英語(yǔ)水平有限,怕翻譯的會(huì)誤導(dǎo)大家,最終直接原文展示
- 本篇包括2部分,前面是官方文檔,后面是一大神"南峰子"的博客內(nèi)容,個(gè)人感覺寫的很好,看完之后感覺收獲很多.
- 閱讀本文的話,需要了解runtime,否則有些地方可能理解起來費(fèi)勁點(diǎn),但絕對(duì)值得收藏.
一 : 基本概念
NSObject 是 大部分常見OC類的根類.正是由于繼承關(guān)系的存在,objects繼承了一些基本特性和能力,去適應(yīng)運(yùn)行時(shí)系統(tǒng),并且表現(xiàn)出對(duì)象的特征.
二 :Initializing a Class
+ initialize
+ load
三 :Creating, Copying, and Deallocating Objects
+ alloc
+ allocWithZone:
- init //Designated Initializer
- copy
+ copyWithZone:
- mutableCopy
+ mutableCopyWithZone:
- dealloc
+ new
四 :Identifying Classes
+ class
+ superclass
+ isSubclassOfClass:
五 :Testing Class Functionality
+ instancesRespondToSelector:
六 :Testing Protocol Conformance
+ conformsToProtocol:
七 :Obtaining Information About Methods
- methodForSelector:
+ instanceMethodForSelector:
+ instanceMethodSignatureForSelector:
- methodSignatureForSelector:
八 :Describing Objects
+ description
九 :Discardable Content Proxy Support
autoContentAccessingProxy
十 :Sending Messages
- performSelector:withObject:afterDelay:
- performSelector:withObject:afterDelay:inModes:
- performSelectorOnMainThread:withObject:waitUntilDone:
- performSelectorOnMainThread:withObject:waitUntilDone:modes:
- performSelector:onThread:withObject:waitUntilDone:
- performSelector:onThread:withObject:waitUntilDone:modes:
- performSelectorInBackground:withObject:
+ cancelPreviousPerformRequestsWithTarget:
+ cancelPreviousPerformRequestsWithTarget:selector:object:
十一 :Forwarding Messages
- forwardingTargetForSelector:
- forwardInvocation:
十二 :Dynamically Resolving Methods
+ resolveClassMethod:
+ resolveInstanceMethod:
十三 :Error Handling
- doesNotRecognizeSelector:
十四 :Archiving
- awakeAfterUsingCoder:
classForCoder //Property
classForKeyedArchiver //Property
+ classFallbacksForKeyedArchiver
+ classForKeyedUnarchiver
- replacementObjectForCoder:
- replacementObjectForKeyedArchiver:
+ setVersion:
+ version
1
Objective-C中有兩個(gè)NSObject,一個(gè)是NSObject類,另一個(gè)是NSObject協(xié)議。而其中NSObject類采用了NSObject協(xié)議。在本文中,我們主要整理一下NSObject類的使用。
說到NSObject類,寫Objective-C的人都應(yīng)該知道它。它是大部分Objective-C類繼承體系的根類。這個(gè)類提供了一些通用的方法,對(duì)象通過繼承NSObject,可以從其中繼承訪問運(yùn)行時(shí)的接口,并讓對(duì)象具備Objective-C對(duì)象的基本能力。以下我們就來看看NSObejct提供給我們的一些基礎(chǔ)功能。
+load與+initialize
這兩個(gè)方法可能平時(shí)用得比較少,但很有用。在我們的程序編譯后,類相關(guān)的數(shù)據(jù)結(jié)構(gòu)會(huì)保留在目標(biāo)文件中,在程序運(yùn)行后會(huì)被解析和使用,此時(shí)類的信息會(huì)經(jīng)歷加載和初始化兩個(gè)過程。在這兩個(gè)過程中,會(huì)分別調(diào)用類的load方法和initialize方法,在這兩個(gè)方法中,我們可以適當(dāng)?shù)刈鲆恍┒ㄖ铺幚怼2划?dāng)是類本身,類的分類也會(huì)經(jīng)歷這兩個(gè)過程。對(duì)于一個(gè)類,我們可以在類的定義中重寫這兩個(gè)方法,也可以在分類中重寫它們,或者同時(shí)重寫。
load方法
對(duì)于load方法,當(dāng)Objective-C運(yùn)行時(shí)加載類或分類時(shí),會(huì)調(diào)用這個(gè)方法;通常如果我們有一些類級(jí)別的操作需要在加載類時(shí)處理,就可以放在這里面,如為一個(gè)類執(zhí)行Swizzling Method操作。
load消息會(huì)被發(fā)送到動(dòng)態(tài)加載和靜態(tài)鏈接的類和分類里面。不過,只有當(dāng)我們?cè)陬惢蚍诸惱锩鎸?shí)現(xiàn)這個(gè)方法時(shí),類/分類才會(huì)去調(diào)用這個(gè)方法。
在類繼承體系中,load方法的調(diào)用順序如下:
- 一個(gè)類的load方法會(huì)在其所有父類的load方法之后調(diào)用
- 分類的load方法會(huì)在對(duì)應(yīng)類的load方法之后調(diào)用
- 在load的實(shí)現(xiàn)中,如果使用同一庫(kù)中的另外一個(gè)類,則可能是不安全的,因?yàn)榭赡艽嬖诘那闆r是另外一個(gè)類的load方法還沒有運(yùn)行,即另一個(gè)類可能尚未被加載。
- 另外,在load方法里面,我們不需要顯示地去調(diào)用[super load],因?yàn)楦割惖膌oad方法會(huì)自動(dòng)被調(diào)用,且在子類之前。
- 在有依賴關(guān)系的兩個(gè)庫(kù)中,被依賴的庫(kù)中的類其load方法會(huì)優(yōu)先調(diào)用。但在庫(kù)內(nèi)部,各個(gè)類的load方法的調(diào)用順序是不確定的。
initialize方法
- 當(dāng)我們?cè)诔绦蛑邢蝾惢蚱淙魏巫宇惏l(fā)送第一條消息前,runtime會(huì)向該類發(fā)送initialize消息。
- runtime會(huì)以線程安全的方式來向類發(fā)起initialize消息。
- 父類會(huì)在子類之前收到這條消息。
父類的initialize實(shí)現(xiàn)可能在下面兩種情況下被調(diào)用:
- 子類沒有實(shí)現(xiàn)initialize方法,runtime將會(huì)調(diào)用繼承而來的實(shí)現(xiàn)
- 子類的實(shí)現(xiàn)中顯示的調(diào)用了[super initialize]
如果我們不想讓某個(gè)類中的initialize被調(diào)用多次,則可以像如下處理:
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
注意:
- 因?yàn)閕nitialize是以線程安全的方式調(diào)用的,且在不同的類中initialize被調(diào)用的順序是不確定的,所以在initialize方法中,我們應(yīng)該做少量的必須的工作。
- 特別需要注意是,如果我們initialize方法中的代碼使用了鎖,則可能會(huì)導(dǎo)致死鎖。因此,我們不應(yīng)該在initialize方法中實(shí)現(xiàn)復(fù)雜的初始化工作,而應(yīng)該在類的初始化方法(如-init)中來初始化。
- 另外,每個(gè)類的initialize只會(huì)被調(diào)用一次。所以,如果我們想要為類和類的分類實(shí)現(xiàn)單獨(dú)的初始化操作,則應(yīng)該實(shí)現(xiàn)load方法。
如果想詳細(xì)地了解這兩個(gè)方法的使用,可以查看《Effective Objective-C 2.0》的第51條,里面有非常詳細(xì)的說明。如果想更深入地了解這兩個(gè)方法的調(diào)用,則可以參考o(jì)bjc庫(kù)的源碼,另外,NSObject的load和initialize方法一文從源碼層面為我們簡(jiǎn)單介紹了這兩個(gè)方法。
對(duì)象的生命周期
一說到對(duì)象的創(chuàng)建,我們會(huì)立即想到[[NSObject alloc] init]這種經(jīng)典的兩段式構(gòu)造。對(duì)于這種兩段式構(gòu)造,唐巧大神在他的”談ObjC對(duì)象的兩段構(gòu)造模式“一文中作了詳細(xì)描述,大家可以參考一下。
本小節(jié)我們主要介紹一下與對(duì)象生命周期相關(guān)的一些方法。
對(duì)象分配
NSObject提供的對(duì)象分配的方法有alloc和allocWithZone:,它們都是類方法。這兩個(gè)方法負(fù)責(zé)創(chuàng)建對(duì)象并為其分配內(nèi)存空間,返回一個(gè)新的對(duì)象實(shí)例。新的對(duì)象的isa實(shí)例變量使用一個(gè)數(shù)據(jù)結(jié)構(gòu)來初始化,這個(gè)數(shù)據(jù)結(jié)構(gòu)描述了對(duì)象的信息;創(chuàng)建完成后,對(duì)象的其它實(shí)例變量被初始化為0。
alloc方法的定義如下:
+ (instancetype)alloc
而allocWithZone:方法的存在是由歷史原因造成的,它的調(diào)用基本上和alloc是一樣的。既然是歷史原因,我們就不說了,官方文檔只給了一句話:
This method exists for historical reasons; memory zones are no longer used by Objective-C.
我們只需要知道alloc方法的實(shí)現(xiàn)調(diào)用了allocWithZone:方法。
對(duì)象初始化
我們一般不去自己重寫alloc或allocWithZone:方法,不用去關(guān)心對(duì)象是如何創(chuàng)建、如何為其分配內(nèi)存空間的;我們更關(guān)心的是如何去初始化這個(gè)對(duì)象。上面提到了,對(duì)象創(chuàng)建后,isa以外的實(shí)例變量都默認(rèn)初始化為0。通常,我們希望將這些實(shí)例變量初始化為我們期望的值,這就是init方法的工作了。
NSObject類默認(rèn)提供了一個(gè)init方法,其定義如下:
- (instancetype)init
正常情況下,它會(huì)初始化對(duì)象,如果由于某些原因無法完成對(duì)象的創(chuàng)建,則會(huì)返回nil。
注意:
- 對(duì)象在使用之前必須被初始化,否則無法使用。不過,NSObject中定義的init方法不做任何初始化操作,只是簡(jiǎn)單地返回self。
- 當(dāng)然,我們定義自己的類時(shí),可以提供自定義的初始化方法,以滿足我們自己的初始化需求。需要注意的就是子類的初始化方法需要去調(diào)用父類的相應(yīng)的初始化方法,以保證初始化的正確性。
講完兩段式構(gòu)造的兩個(gè)部分,有必要來講講NSObject類的new方法了。
new方法實(shí)際上是集alloc和init于一身,它創(chuàng)建了對(duì)象并初始化了對(duì)象。它的實(shí)現(xiàn)如下:
+ (instancetype)new {
return [[self alloc] init];
}
new方法更多的是一個(gè)歷史遺留產(chǎn)物,它源于NeXT時(shí)代。如果我們的初始化操作只是調(diào)用[[self alloc] init]時(shí),就可以直接用new來代替。不過如果我們需要使用自定義的初始化方法時(shí),通常就使用兩段式構(gòu)造方式。
拷貝
說到拷貝,相信大家都很熟悉。拷貝可以分為“深拷貝”和“淺拷貝”。深拷貝拷貝的是對(duì)象的值,兩個(gè)對(duì)象相互不影響,而淺拷貝拷貝的是對(duì)象的引用,修改一個(gè)對(duì)象時(shí)會(huì)影響到另一個(gè)對(duì)象。
在Objective-C中,如果一個(gè)類想要支持拷貝操作,則需要實(shí)現(xiàn)NSCopying協(xié)議,并實(shí)現(xiàn)copyWithZone:【注意:NSObject類本身并沒有實(shí)現(xiàn)這個(gè)協(xié)議】。如果一個(gè)類不是直接繼承自NSObject,則在實(shí)現(xiàn)copyWithZone:方法時(shí)需要調(diào)用父類的實(shí)現(xiàn)。
雖然NSObject自身沒有實(shí)現(xiàn)拷貝協(xié)議,不過它提供了兩個(gè)拷貝方法,如下:
- (id)copy
這個(gè)是拷貝操作的便捷方法。它的返回值是NSCopying協(xié)議的copyWithZone:方法的返回值。如果我們的類沒有實(shí)現(xiàn)這個(gè)方法,則會(huì)拋出一個(gè)異常。
與copy對(duì)應(yīng)的還有一個(gè)方法,即:
- (id)mutableCopy
從字面意義來講,copy可以理解為不可變拷貝操作,而mutableCopy可以理解為可變操作。這便引出了拷貝的另一個(gè)特性,即可變性。
顧名思義,不可變拷貝即拷貝后的對(duì)象具有不可變屬性,可變拷貝后的對(duì)象具有可變屬性。這對(duì)于數(shù)組、字典、字符串、URL這種分可變和不可變的對(duì)象來說是很有意義的。我們來看如下示例:
NSMutableArray *mutableArray = [NSMutableArray array];
NSMutableArray *array = [mutableArray copy];
[array addObject:@"test1"];
實(shí)際上,這段代碼是會(huì)崩潰的,我們來看看崩潰日志:
-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x100107070
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x100107070'
從中可以看出,經(jīng)過copy操作,我們的array實(shí)際上已經(jīng)變成不可變的了,其底層元類是__NSArrayI。這個(gè)類是不支持addObject:方法的。
偶爾在代碼中,也會(huì)看到類似于下面的情況:
@property (copy) NSMutableArray *array;
這種屬性的聲明方式是有問題的,即上面提到的可變性問題。使用self.array = **賦值后,數(shù)組其實(shí)是不可變的,所以需要特別注意。
mutableCopy的使用也挺有意思的,具體的還請(qǐng)大家自己去試驗(yàn)一下。
釋放
當(dāng)一個(gè)對(duì)象的引用計(jì)數(shù)為0時(shí),系統(tǒng)就會(huì)將這個(gè)對(duì)象釋放。此時(shí)runtime會(huì)自動(dòng)調(diào)用對(duì)象的dealloc方法。在ARC環(huán)境下,我們不再需要在此方法中去調(diào)用[super dealloc]了。我們重寫這個(gè)方法主要是為了釋放對(duì)象中用到的一些資源,如我們通過C方法分配的內(nèi)存空間。dealloc方法的定義如下:
- (void)dealloc
需要注意的是,我們不應(yīng)該直接去調(diào)用這個(gè)方法。這些事都讓runtime去做吧。
消息發(fā)送
Objective-C中對(duì)方法的調(diào)用并不是像C++里面那樣直接調(diào)用,而是通過消息分發(fā)機(jī)制來實(shí)現(xiàn)的。這個(gè)機(jī)制核心的方法是objc_msgSend函數(shù)。消息機(jī)制的具體實(shí)現(xiàn)我們?cè)诖瞬蛔鲇懻摚梢詤⒖?a target="_blank" rel="nofollow">Objective-C Runtime 運(yùn)行時(shí)之三:方法與消息。
對(duì)于消息的發(fā)送,除了使用[obj method]這種機(jī)制之外,NSObject類還提供了一系列的performSelector方法。這些方法可以讓我們更加靈活地控制方法的調(diào)用。接下來我們就來看看這些方法的使用。
在線程中調(diào)用方法
如果我們想在當(dāng)前線程中調(diào)用一個(gè)方法,則可以使用以下兩個(gè)方法:
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes
這兩個(gè)方法會(huì)在當(dāng)前線程的Run loop中設(shè)置一個(gè)定時(shí)器,以在delay指定的時(shí)間之后執(zhí)行aSelector。如果我們希望定時(shí)器運(yùn)行在默認(rèn)模式(NSDefaultRunLoopMode)下,可以使用前一個(gè)方法;如果想自己指定Run loop模式,則可以使用后一個(gè)方法。
當(dāng)定時(shí)器啟動(dòng)時(shí),線程會(huì)從Run loop的隊(duì)列中獲取到消息,并執(zhí)行相應(yīng)的selector。如果Run loop運(yùn)行在指定的模式下,則方法會(huì)成功調(diào)用;否則,定時(shí)器會(huì)處于等待狀態(tài),直到Run loop運(yùn)行在指定模式下。
需要注意的是,調(diào)用這些方法時(shí),Run loop會(huì)保留方法接收者及相關(guān)的參數(shù)的引用(即對(duì)這些對(duì)象做retain操作),這樣在執(zhí)行時(shí)才不至于丟失這些對(duì)象。當(dāng)方法調(diào)用完成后,Run loop會(huì)調(diào)用這些對(duì)象的release方法,減少對(duì)象的引用計(jì)數(shù)。
如果我們想在主線程上執(zhí)行某個(gè)對(duì)象的方法,則可以使用以下兩個(gè)方法:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array
我們都知道,iOS中所有的UI操作都需要在主線程中處理。如果想在某個(gè)二級(jí)線程的操作完成之后做UI操作,就可以使用這兩個(gè)方法。
這兩個(gè)方法會(huì)將消息放到主線程Run loop的隊(duì)列中,前一個(gè)方法使用的是NSRunLoopCommonModes運(yùn)行時(shí)模式;如果想自己指定運(yùn)行模式,則使用后一個(gè)方法。方法的執(zhí)行與之前的兩個(gè)performSelector方法是類似的。當(dāng)在一個(gè)線程中多次調(diào)用這個(gè)方法將不同的消息放入隊(duì)列時(shí),消息的分發(fā)順序與入隊(duì)順序是一致的。
方法中的wait參數(shù)指定當(dāng)前線程在指定的selector在主線程執(zhí)行完成之后,是否被阻塞住。如果設(shè)置為YES,則當(dāng)前線程被阻塞。如果當(dāng)前線程是主線程,而該參數(shù)也被設(shè)置為YES,則消息會(huì)被立即發(fā)送并處理。
另外,這兩個(gè)方法分發(fā)的消息不能被取消。
如果我們想在指定的線程中分發(fā)某個(gè)消息,則可以使用以下兩個(gè)方法:
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array
這兩個(gè)方法基本上與在主線程的方法差不多。在此就不再討論。
如果想在后臺(tái)線程中調(diào)用接收者的方法,可以使用以下方法:
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
這個(gè)方法會(huì)在程序中創(chuàng)建一個(gè)新的線程。由aSelector表示的方法必須像程序中的其它新線程一樣去設(shè)置它的線程環(huán)境。
當(dāng)然,我們經(jīng)常看到的performSelector系列方法中還有幾個(gè)方法,即:
- (id)performSelector:(SEL)aSelector
- (id)performSelector:(SEL)aSelector withObject:(id)anObject
- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject
不過這幾個(gè)方法是在NSObject協(xié)議中定義的,NSObject類實(shí)現(xiàn)了這個(gè)協(xié)議,也就定義了相應(yīng)的實(shí)現(xiàn)。這個(gè)我們將在NSObject協(xié)議中來介紹。
取消方法調(diào)用請(qǐng)求
對(duì)于使用performSelector:withObject:afterDelay:方法(僅限于此方法)注冊(cè)的執(zhí)行請(qǐng)求,在調(diào)用發(fā)生前,我們可以使用以下兩個(gè)方法來取消:
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument
前一個(gè)方法會(huì)取消所以接收者為aTarget的執(zhí)行請(qǐng)求,不過僅限于當(dāng)前run loop,而不是所有的。
后一個(gè)方法則會(huì)取消由aTarget、aSelector和anArgument三個(gè)參數(shù)指定的執(zhí)行請(qǐng)求。同樣僅限于當(dāng)前run loop。
消息轉(zhuǎn)發(fā)及動(dòng)態(tài)解析方法
當(dāng)一個(gè)對(duì)象能接收一個(gè)消息時(shí),會(huì)走正常的方法調(diào)用流程。但如果一個(gè)對(duì)象無法接收一個(gè)消息時(shí),就會(huì)走消息轉(zhuǎn)發(fā)機(jī)制。
消息轉(zhuǎn)發(fā)機(jī)制基本上分為三個(gè)步驟:
- 動(dòng)態(tài)方法解析
- 備用接收者
- 完整轉(zhuǎn)發(fā)
具體流程可參考Objective-C Runtime 運(yùn)行時(shí)之三:方法與消息,《Effective Objective-C 2.0》一書的第12小節(jié)也有詳細(xì)描述。在此我們只介紹一下NSObject類為實(shí)現(xiàn)消息轉(zhuǎn)發(fā)提供的方法。
首先,對(duì)于動(dòng)態(tài)方法解析,NSObject提供了以下兩個(gè)方法來處理:
+ (BOOL)resolveClassMethod:(SEL)name
+ (BOOL)resolveInstanceMethod:(SEL)name
從方法名我們可以看出,
- resolveClassMethod:是用于動(dòng)態(tài)解析一個(gè)類方法;
- 而resolveInstanceMethod:是用于動(dòng)態(tài)解析一個(gè)實(shí)例方法。
我們知道,一個(gè)Objective-C方法是其實(shí)是一個(gè)C函數(shù),它至少帶有兩個(gè)參數(shù),即self和_cmd。我們使用class_addMethod函數(shù),可以給類添加一個(gè)方法。我們以resolveInstanceMethod:為例,如果要給對(duì)象動(dòng)態(tài)添加一個(gè)實(shí)例方法,則可以如下處理:
void dynamicMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically))
{
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}
其次,對(duì)于備用接收者,NSObject提供了以下方法來處理:
- (id)forwardingTargetForSelector:(SEL)aSelector
該方法返回未被接收消息最先被轉(zhuǎn)發(fā)到的對(duì)象。如果一個(gè)對(duì)象實(shí)現(xiàn)了這個(gè)方法,并返回一個(gè)非空的對(duì)象(且非對(duì)象本身),則這個(gè)被返回的對(duì)象成為消息的新接收者。另外如果在非根類里面實(shí)現(xiàn)這個(gè)方法,如果對(duì)于給定的selector,我們沒有可用的對(duì)象可以返回,則應(yīng)該調(diào)用父類的方法實(shí)現(xiàn),并返回其結(jié)果。
最后,對(duì)于完整轉(zhuǎn)發(fā),NSObject提供了以下方法來處理
- (void)forwardInvocation:(NSInvocation *)anInvocation
當(dāng)前面兩步都無法處理消息時(shí),運(yùn)行時(shí)系統(tǒng)便會(huì)給接收者最后一個(gè)機(jī)會(huì),將其轉(zhuǎn)發(fā)給其它代理對(duì)象來處理。這主要是通過創(chuàng)建一個(gè)表示消息的NSInvocation對(duì)象并將這個(gè)對(duì)象當(dāng)作參數(shù)傳遞給forwardInvocation:
方法。我們?cè)?code>forwardInvocation:方法中可以選擇將消息轉(zhuǎn)發(fā)給其它對(duì)象。
在這個(gè)方法中,主要是需要做兩件事:
- 找到一個(gè)能處理anInvocation調(diào)用的對(duì)象。
- 將消息以anInvocation的形式發(fā)送給對(duì)象。anInvocation將維護(hù)調(diào)用的結(jié)果,而運(yùn)行時(shí)則會(huì)將這個(gè)結(jié)果返回給消息的原始發(fā)送者。
這一過程如下所示:
-(void)forwardInvocation:(NSInvocation *)invocation
{
SEL aSelector = [invocation selector];
if ([friend respondsToSelector:aSelector])
[invocation invokeWithTarget:friend];
else
[super forwardInvocation:invocation];
}
當(dāng)然,對(duì)于一個(gè)非根類,如果還是無法處理消息,則應(yīng)該調(diào)用父類的實(shí)現(xiàn)。而NSObject類對(duì)于這個(gè)方法的實(shí)現(xiàn),只是簡(jiǎn)單地調(diào)用了doesNotRecognizeSelector:。它不再轉(zhuǎn)發(fā)任何消息,而是拋出一個(gè)異常。doesNotRecognizeSelector:的聲明如下:
- (void)doesNotRecognizeSelector:(SEL)aSelector
運(yùn)行時(shí)系統(tǒng)在對(duì)象無法處理或轉(zhuǎn)發(fā)一個(gè)消息時(shí)會(huì)調(diào)用這個(gè)方法。這個(gè)方法引發(fā)一個(gè)NSInvalidArgumentException
異常并生成一個(gè)錯(cuò)誤消息。
任何doesNotRecognizeSelector:消息通常都是由運(yùn)行時(shí)系統(tǒng)來發(fā)送的。不過,它們可以用于阻止一個(gè)方法被繼承。例如,一個(gè)NSObject的子類可以按以下方式來重寫copy或init方法以阻止繼承:
- (id)copy
{
[self doesNotRecognizeSelector:_cmd];
}
這段代碼阻止子類的實(shí)例響應(yīng)copy消息或阻止父類轉(zhuǎn)發(fā)copy消息—雖然respondsToSelector:仍然報(bào)告接收者可以訪問copy方法。
當(dāng)然,如果我們要重寫doesNotRecognizeSelector:方法,必須調(diào)用super的實(shí)現(xiàn),或者在實(shí)現(xiàn)的最后引發(fā)一個(gè)NSInvalidArgumentException異常。它代表對(duì)象不能響應(yīng)消息,所以總是應(yīng)該引發(fā)一個(gè)異常。
獲取方法信息
在消息轉(zhuǎn)發(fā)的最后一步中,forwardInvocation:參數(shù)是一個(gè)NSInvocation對(duì)象,這個(gè)對(duì)象需要獲取方法簽名的信息,而這個(gè)簽名信息就是從methodSignatureForSelector:方法中獲取的。
該方法的聲明如下:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
這個(gè)方法返回包含方法描述信息的NSMethodSignature對(duì)象,如果找不到方法,則返回nil。如果我們的對(duì)象包含一個(gè)代理或者對(duì)象能夠處理它沒有直接實(shí)現(xiàn)的消息,則我們需要重寫這個(gè)方法來返回一個(gè)合適的方法簽名。
對(duì)應(yīng)于實(shí)例方法,當(dāng)然還有一個(gè)處理類方法的相應(yīng)方法,其聲明如下:
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
另外,NSObject類提供了兩個(gè)方法來獲取一個(gè)selector對(duì)應(yīng)的方法實(shí)現(xiàn)的地址,如下所示:
- (IMP)methodForSelector:(SEL)aSelector
+ (IMP)instanceMethodForSelector:(SEL)aSelector
獲取到了方法實(shí)現(xiàn)的地址,我們就可以直接將IMP以函數(shù)形式來調(diào)用。
對(duì)于methodForSelector:方法,如果接收者是一個(gè)對(duì)象,則aSelector應(yīng)該是一個(gè)實(shí)例方法;如果接收者是一個(gè)類,則aSelector應(yīng)該是一個(gè)類方法。
對(duì)于instanceMethodForSelector:方法,其只是向類對(duì)象索取實(shí)例方法的實(shí)現(xiàn)。如果接收者的實(shí)例無法響應(yīng)aSelector消息,則產(chǎn)生一個(gè)錯(cuò)誤。
測(cè)試類
對(duì)于類的測(cè)試,在NSObject類中定義了兩個(gè)方法,其中類方法instancesRespondToSelector:用于測(cè)試接收者的實(shí)例是否響應(yīng)指定的消息,其聲明如下:
+ (BOOL)instancesRespondToSelector:(SEL)aSelector
如果aSelector消息被轉(zhuǎn)發(fā)到其它對(duì)象,則類的實(shí)例可以接收這個(gè)消息而不會(huì)引發(fā)錯(cuò)誤,即使該方法返回NO。
為了詢問類是否能響應(yīng)特定消息(注意:不是類的實(shí)例),則使用這個(gè)方法,而不使用NSObject協(xié)議的實(shí)例方法respondsToSelector:。
NSObject還提供了一個(gè)方法來查看類是否采用了某個(gè)協(xié)議,其聲明如下:
+ (BOOL)conformsToProtocol:(Protocol *)aProtocol
如果一個(gè)類直接或間接地采用了一個(gè)協(xié)議,則我們可以說這個(gè)類實(shí)現(xiàn)了該協(xié)議。我們可以看看以下這個(gè)例子:
@protocol AffiliationRequests <Joining>
@interface MyClass : NSObject <AffiliationRequests, Normalization>
BOOL canJoin = [MyClass conformsToProtocol:@protocol(Joining)];
通過繼承體系,MyClass類實(shí)現(xiàn)了Joining協(xié)議。
不過,這個(gè)方法并不檢查類是否實(shí)現(xiàn)了協(xié)議的方法,這應(yīng)該是程序員自己的職責(zé)了。
識(shí)別類
NSObject類提供了幾個(gè)類方法來識(shí)別一個(gè)類,首先是我們常用的class類方法,該方法聲明如下:
+ (Class)class
該方法返回類對(duì)象。當(dāng)類是消息的接收者時(shí),我們只通過類的名稱來引用一個(gè)類。在其它情況下,類的對(duì)象必須通過這個(gè)方法類似的方法(-class實(shí)例方法)來獲取。如下所示:
BOOL test = [self isKindOfClass:[SomeClass class]];
NSObject還提供了superclass類方法來獲取接收者的父類,其聲明如下:
+ (Class)superclass
另外,我們還可以使用isSubclassOfClass:類方法查看一個(gè)類是否是另一個(gè)類的子類,其聲明如下:
+ (BOOL)isSubclassOfClass:(Class)aClass
描述類
描述類是使用description方法,它返回一個(gè)表示類的內(nèi)容的字符串。其聲明如下:
+ (NSString *)description
我們?cè)贚LDB調(diào)試器中打印類的信息時(shí),使用的就是這個(gè)方法。
當(dāng)然,如果想打印類的實(shí)例的描述時(shí),使用的是NSObject協(xié)議中的實(shí)例方法description,我們?cè)诖瞬欢嗝枋觥?/p>
歸檔操作
一說到歸檔操作,你會(huì)首先想到什么呢?我想到的是NSCoding協(xié)議以及它的兩個(gè)方法:
initWithCoder:和encodeWithCoder:。如果我們的對(duì)象需要支持歸檔操作,則應(yīng)該采用這個(gè)協(xié)議并提供兩個(gè)方法的具體實(shí)現(xiàn)。
在編碼與解碼的過程中,一個(gè)編碼器會(huì)調(diào)用一些方法,這些方法允許將對(duì)象編碼以替代一個(gè)更換類或?qū)嵗旧怼_@樣,就可以使得歸檔在不同類層次結(jié)構(gòu)或類的不同版本的實(shí)現(xiàn)中被共享。例如,類簇能有效地利用這一特性。這一特性也允許每個(gè)類在解碼時(shí)應(yīng)該只維護(hù)單一的實(shí)例來執(zhí)行這一策略。
NSObject類雖然沒有采用NSCoding協(xié)議,但卻提供了一些替代方法,以支持上述策略。這些方法分為兩類,即通用和專用的。
通用方法由NSCoder對(duì)象調(diào)用,主要有如下幾個(gè)方法和屬性:
@property(readonly) Class classForCoder
- (id)replacementObjectForCoder:(NSCoder *)aCoder
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
專用的方法主要是針對(duì)NSKeyedArchiver對(duì)象的,主要有如下幾個(gè)方法和屬性:
@property(readonly) Class classForKeyedArchiver
+ (NSArray *)classFallbacksForKeyedArchiver
+ (Class)classForKeyedUnarchiver
- (id)replacementObjectForKeyedArchiver:(NSKeyedArchiver *)archiver
子類在歸檔的過程中如果有特殊的需求,可以重寫這些方法。這些方法的具體描述,可以參考官方文檔。
在解碼或解檔過程中,有一點(diǎn)需要考慮的就是對(duì)象所屬類的版本號(hào),這樣能確保老版本的對(duì)象能被正確地解析。NSObject類對(duì)此提供了兩個(gè)方法,如下所示:
+ (void)setVersion:(NSInteger)aVersion
+ (NSInteger)version
它們都是類方法。默認(rèn)情況下,如果沒有設(shè)置版本號(hào),則默認(rèn)是0.
總結(jié)
NSObject類是Objective-C中大部分類層次結(jié)構(gòu)中的根類,并為我們提供了很多功能。了解這些功能更讓我們更好地發(fā)揮Objective-C的特性。
參考:
南峰子的技術(shù)博客