iOS-NSObject官方API大總結(jié)

先看下NSObject繼承圖.

NSObject繼承圖.png

說明:

  • 本來想翻譯一下 但是英語(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ù)博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,456評(píng)論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,608評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,802評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,048評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評(píng)論 1 281
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,424評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,762評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容