先看下NSObject繼承圖.

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