Objective-C
Objective-C是對C的擴充,它最初的設(shè)計目的是給C語言一個通過簡單且直觀的方式而實現(xiàn)面向?qū)ο缶幊痰哪芰?。本系列用于自我查疑?/p>
面向?qū)ο蟮某绦蚨际菄@對象而構(gòu)建,一個對象把數(shù)據(jù) 和一些對這些數(shù)據(jù)的操作(即Methods)捆綁在一起,打包成一個獨立的編程單元。
例如你正在寫一個畫圖程序,這個程序允許用戶創(chuàng)建諸如線、弧線、矩形、文本、位圖等,那么你就可以創(chuàng)建關(guān)于這些基本圖形的很多類。例如一個矩形對象,應(yīng)該有標識它的位置的實例變量和寬與高的信息,可能還會有別的實例變量來定義它的顏色、矩形是否被填充、直線樣式等。這個Rectangle類會有一些方法用來設(shè)置一個實例的位置、大小、顏色、填充狀態(tài)、直線樣式,還應(yīng)該提供一個方法用來繪制自己。
在Objective-C中,Object的實例變量屬于Object的內(nèi)部數(shù)據(jù),通常要訪問這些數(shù)據(jù)只能通過對象的方法,你還可以通過作用域指示符(scope directives)為一個類的子類或別的對象指定訪問實例變量的權(quán)限。要獲取對象別的信息,必須提供一個相應(yīng)的方法,例如,Rectangle類應(yīng)該有專用的方法來獲取它的大小和位置。而且,對象只能看到為它自己而設(shè)計的方法,它不會把別的對象的方法運用到自己身上。就像一個函數(shù)隱藏局部變量一樣,對象不但隱藏實例變量,還會隱藏方法的實現(xiàn)。
一、OC中的類
編譯器為每個類定義一個類對象(Class_object)。Class_object 是Class的編譯版本,而它所構(gòu)建的對象被稱為類的實例。在你的程序中真正做工作的是類在運行時對象創(chuàng)建的那些實例。一個類的所有實例有同一套方法,而且有相同一套實例變量。每個對象都有自己的實例變量,但他們卻共享這套方法。根據(jù)約定,類名以大寫字母開頭,如Rectangle,而實例變量通常以小寫字母開頭,如myRect。
- 繼承(Inheritance)
類的定義通常是添加式的,一個新的類往往都基于另外一個類,而這個新類繼承了原來類的方法和實例變量。新類通常簡單地添加實例變量或者修改它所繼承的方法,它不需要復(fù)制繼承的代碼。繼承將這些類連接成一個只有一個根繼承關(guān)系樹。在OC中,寫基于功能框架的代碼時,這個根類通常是NSObject。每個類(除了根類)都有一個父類,而每個類,包括根類都可以成為任何數(shù)量子類的父類。- 抽象類
有些類被設(shè)計為不能實例化,他們只能作為super類被其他類所繼承。這些類往往自身是不完整的,而只是包含了一些有用的代碼,這些代碼能夠被子類所繼承,從而減少子類的代碼量。NSObject類就是一個重要的抽象類。程序中經(jīng)常會定義NSObject的子類并使用這些子類的實例,但從來沒有直接使用這個類的實例的。抽象類通常包含一些幫助定義應(yīng)用程序架構(gòu)的代碼,當你定義這些類的子類時,這些子類的實例能很好地適應(yīng)這種應(yīng)用程序架構(gòu),并能夠自動地和別的對象協(xié)作。由于抽象類只有定義了子類才能成為一個有用的類,因此它們常常被稱為抽象超類。- 類類型
類實際上是一類對象的詳細說明,定義了一個數(shù)據(jù)類型。這個類型不但基于它所定義的數(shù)據(jù)結(jié)構(gòu),還取決于它所定義的一些行為,這些行為就是方法。因此,類名可以出現(xiàn)在任何C語言允許的類型說明符出現(xiàn)的地方,例如作為sizeof操作符的參數(shù):Int i = sizeof(Rectangle);- 靜態(tài)類型匹配
Rectangle *myRect;
這種聲明對象的方式為編譯器提供了對象種類的信息,所以被稱為靜態(tài)類型匹配,靜態(tài)類型匹配使編譯器具備了一些類型檢查功能,例如如果一個對象接收一個沒有定義的消息時,可以發(fā)出警告,而如果把對象定義為id,就會放松這種限制。盡管這樣,它還是無法替代動態(tài)綁定的位置。一個對象可以被靜態(tài)地匹配為它自己的類或者它所繼承的類。例如因為繼承使得Rectangle成為一種Graphic,所以Rectangle實例可以靜態(tài)匹配為Graphic類:
Graphic* myRect;
這樣做之所以合法,是因為Rectangle本身就是一種Graphic,但它由于繼承了Shape和Rectangle的特性,所以又不止是一個Graphic。為了類型檢查,編譯器會把myRect當作Graphic,但在運行時環(huán)境中,它會被當作Rectangle對待。- 動態(tài)類型匹配
id myRect;
對象通常都是被定義成指針,上面的靜態(tài)匹配使得指針的含義更加明確,而id卻隱藏了這些信息。
id類型是一種靈活的數(shù)據(jù)類型, 只表示它是一個對象,不能為編譯器提供例如實例變量,可執(zhí)行操作等信息。所以每個對象在運行時必須提供這些信息。 而之所以能做到這點, 是因為每個對象都有一個isa 實例變量來標示這個對象所屬的類每個Rectangle對象都能告訴運行時系統(tǒng)它是一個矩形類,因此,動態(tài)類型匹配實際上發(fā)生在程序被執(zhí)行時。
不論何時,只要需要,運行時系統(tǒng)就能夠查明一個對象到底屬于哪個類,而這只需要查詢對象的isa實例變量就可以了。這個isa指針還為對象提供了一種稱為“自省”(introspection)的功能。編譯器會在數(shù)據(jù)機構(gòu)中記錄關(guān)于類定義的信息為運行時環(huán)境所用。通過這種機制,你可以判斷一個對象是否提供了一個特定的方法,也可以獲取這個對象的超類(supperclass)的名字。當然也可以通過靜態(tài)類型匹配為編譯器提供有關(guān)對象所屬類的信息,這也是為什么類名可以作為類型名而使用了。- 類型自省
實例在運行時可以獲取自己的類。例如,NSObject類中定義的isMemberOfClass(或isKindOfClass)方法可以檢查接收者是不是特定類的實例(或繼承特定類的實例)。
- 消息語法
ObjC中使用[Receiver message]方式來進行方法調(diào)用,本質(zhì)其實就是向Receiver發(fā)送message消息.而message告訴這個Receiver要做什么。
- 給nil發(fā)送消息
在ObjC中,給nil發(fā)送消息是合法的,但在運行時什么都不做,而發(fā)送給nil的消息帶有返回值也是合法的。
■如果一個方法返回一個對象、任何類型的指針、任何size小于或等于sizeof(void*)的類型,如float、 double、 long double、或者long long,那么給nil發(fā)送消息將返回0。
■如果一個方法返回一個數(shù)據(jù)結(jié)構(gòu),那么將這個消息傳遞給nil將為這個數(shù)據(jù)結(jié)構(gòu)的每個成員都返回0.0。
■如果一個方法返回上述類型外的其它類型,那么將這個消息傳遞給nil,返回值為定義。
- 多態(tài)和動態(tài)綁定
函數(shù)調(diào)用和消息的一個關(guān)鍵區(qū)別是,函數(shù)和它的參數(shù)是在編譯時綁定在一起的,而消息和接收者直到程序運行時,消息被發(fā)送才實現(xiàn)這種綁定。因此,響應(yīng)一個消息的具體方法是在運行時才決定的,而不是在代碼被編譯的時候。Message啟動哪個Method取決于Message的Receiver,不同的Receiver可能有同名的不同Method實體,這就是多態(tài)。
而編譯器要為一個Message找到正確的Method實體,它必須知道這個Receiver屬于什么對象,而這是一個對象只有在運行時接收到Message后才能夠真正獲取的信息. 因此Method實體的選擇是發(fā)生在運行時的。
當Message發(fā)出之后,運行時環(huán)境會查看Receiver以及它與消息同名的Method,它通過名字匹配找到這個Receiver的Method實體,并調(diào)用,同時傳遞一個指向Receiver實例變量的指針。Message中的方法名是用來選擇 Receiver的Method實體,因此,Message中的方法名也被稱為(selector)選擇器.
Method和Message的動態(tài)綁定,以及多態(tài)之間的協(xié)調(diào)合作,給了面向?qū)ο缶幊特S富的靈活性和強大的功能,因為每個對象都可以有一個方法的自己的版本,但僅僅是接收相同消息的對象不同,而這些都可以在程序運行時完成,即不同的對象以各自的方式響應(yīng)相同的消息.
可以簡化編程接口,容許在類與類之間重用一些習慣性的命名.
這樣你可以編寫應(yīng)用于任何不同種類對象的代碼,而不用去考慮到底是應(yīng)用到什么樣的對象上.
這些對象甚至可以是尚未開發(fā)的,或者是由別的項目的程序員開發(fā)的.(ps.如果你寫了一個向id類型變量發(fā)送display消息的代碼,任何擁有display方法的對象都可能成為這個消息的接收者。)
- 方法和選取器:
選取器確定的是方法名,不是方法的實現(xiàn), 這是多態(tài)和動態(tài)綁定的基礎(chǔ).它使得向不同對象發(fā)送相同的消息成為現(xiàn)實.- 方法返回值和參數(shù)類型:
消息機制是通過選取器找到方法的返回值類型和參數(shù)類型. 因此: 動態(tài)綁定需要同名方法的實現(xiàn) 擁有相同返回值類型和相同的參數(shù)類型;否則,運行時可能出現(xiàn)找不到對應(yīng)方法的錯誤.(有一個例外,雖然同名靜態(tài)方法和實例方法擁有相同的選取器,但是它們可以有不同的參數(shù)類型和返回值類型。)
// SEL和@selector區(qū)別:選擇器的類型是SEL.而 @selector指示符是用來引用選擇器的, 它返回類型是SEL.
SEL response;
response = @selector(load:)
// 1. 通過字符串來得到選取器:
responseSEL = NSSelectorFromString(@"loadDataForTableView:");
// 2 . 通過選擇器轉(zhuǎn)換來得到方法名:
NSString *methodName = NSStringFromSelector(responseSEL);
1.1 類對象
一個類的定義會包含豐富的信息(詳見附錄),但大部分都是關(guān)于這個類的實例的,如:
■類名及超類
■關(guān)于實例變量的完整描述
■關(guān)于方法名及其參數(shù)的說明
■方法的實現(xiàn)
所有這些信息都被編譯被保存在一個運行時系統(tǒng)能夠訪問的數(shù)據(jù)結(jié)構(gòu)中。編譯器創(chuàng)建一個而且只創(chuàng)建一個對象來表達這些信息,那就是類對象,因此,在OC中,所有的類自身也是一個對象。
雖然類對象保存了類的屬性,但它本身并不是一個類的實例。它沒有自己的實例變量,而且它也不能執(zhí)行為類實例設(shè)計的方法。不過,類定義了可以包含只為類對象使用的方法,這就是類方法(靜態(tài)方法),這些方法不同于實例方法(動態(tài)方法)。另外,類對象會繼承所有超對象的類方法,就像類實例可以繼承所有超類實例的方法一樣。
在實際代碼中,類名就代表類對象,下面的例子中,Rectangle類使用繼承自NSObject的方法返回類的版本號:
Int versionNumber = [Rectangle version];
但只有在作為接收者接收一個消息時,類名才能代表類對象,在別的地方,你必須通過給類實例或給類發(fā)送class消息來獲得一個類id,如:
Id aClass = [anObject class];
Id rectClass = [Rectangle class];
如上例所述,和所有其它的對象一樣,類對象可以被轉(zhuǎn)化為id類型,而且類對象還可以更精確地轉(zhuǎn)化為類類型,如:
Class aClass = [anObject class];
Class rectClass = [Rectangle class];
所有的類都屬于類對象,使用Class類型和使用類名進行靜態(tài)類型匹配是等效的。因此,類對象也像類實例那樣,可以進行動態(tài)類型匹配、接收消息以及從別的類繼承方法。不同之處在于它們是由編譯器產(chǎn)生的,沒有自己的數(shù)據(jù)結(jié)構(gòu),它們是用于運行時系統(tǒng)產(chǎn)生類實例的代理。
- 創(chuàng)建實例
類對象的主要用途是用于創(chuàng)建新的實例,下面這個例子告訴Rectangle類創(chuàng)建一個Rectangle實例,并將其賦值給myRect變量:
id myRect;
myRect = [Rectangle alloc];
alloc方法會為這個實例的每個實例變量動態(tài)分配內(nèi)存,并將除連接該實例和它的類的 isa變量外的所有實例變量全部清零,一個對象要能真正派上用場,它還必須完整地初始化,這就是init方法的作用,通常init方法都緊挨著alloc方法,如:
myRect = [[Rectangle alloc] init];
在一個實例能夠接收消息前,對實例進行這樣的初始化是必須的。Alloc方法返回一個新的實例,然后init方法對這個事例進行初始化。每個類對象至少擁有一個方法(如alloc)才能讓它具備創(chuàng)建新對象的能力。而每個實例至少一個方法(如init)讓它能夠有用。
而初始化方法通常帶有一些參數(shù)用來傳遞特定的值,而且用標簽來標示這些參數(shù),例如initWithPositon: size:,但不管怎樣,它都是以init開頭。
- 變量和類對象
定義一個類,你可以為它定義實例變量,這個類的每個實例都保留一份這些實例變量的拷貝——每個對象管理自己的數(shù)據(jù)。
但是你發(fā)現(xiàn)沒有,OC中并沒有類似實例變量那樣的所謂的“類變量”。而且類對象無法訪問任何實例的實例變量,它不能初始化、讀寫這些變量。為了讓類的所有實例共享數(shù)據(jù),你必須定于外部變量(不是在類中定義的變量)。最簡單的辦法是像下面的代碼片斷那樣在類的實現(xiàn)文件中聲明一個變量:
int MCLSGlobalVariable;
@implementation MyClass
...
@end
更巧妙的做法是,你可以把變量聲明為靜態(tài)的,并提供類方法來管理它。靜態(tài)變量會把它的作用域限定在類范圍,而且僅限于當前文件的類的實現(xiàn)部分。并不像實例變量那樣,靜態(tài)變量不能被繼承,不能被子類直接操作。這種特性常常用于定于類的共享實例(如單一模式,參考“創(chuàng)建單例”部分)。靜態(tài)變量能夠幫助類對象提供一種比“工廠模式”創(chuàng)建實例更多的功能,而且更接近一個完整和通用的對象。
static MyClass *MCLSSharedInstance;
@implementation MyClass
+ (MyClass *)sharedInstance
{
// check for existence of shared instance
// create if necessary
return MCLSSharedInstance;
}
// implementation continues
- 初始化類對象
如果類對象不分配實例,它就可以向類的實例那樣初始化,盡管程序并不為類對象分配內(nèi)存,但Objective-C確實也提供了一個初始化類對象的途徑,如果類使用靜態(tài)或全局變量,那么initialize是一個初始化這些變量的好地方。
在類接受任何其它消息前,運行時環(huán)境會給類對象發(fā)送一個initialize消息,當然這發(fā)生在它的超類收到initialize消息之后。這就給這個類一個在被使用前建立運行時環(huán)境的機會。
如果沒有初始化工作要做,你就沒有必要實現(xiàn)一個initialize方法來響應(yīng)這個消息。因為繼承關(guān)系的存在,給一個沒有實現(xiàn)initialize的類發(fā)送initialize消息,這個消息將會被傳遞到它的超類,即使超類已經(jīng)受到這個消息也是如此。
例如,A類實現(xiàn)了initialize方法,而類B繼承自類A,但類B沒有實現(xiàn)initialize方法,在類B接收它的第一個消息之前,運行時環(huán)境會為它發(fā)送一個initialize消息。但由于類B沒有實現(xiàn)initialize方法,類A的initialize方法會被執(zhí)行,因此,類A需要確保它的初始化邏輯只被執(zhí)行一次。為了確保初始化邏輯只執(zhí)行一次,請使用如下的模板實現(xiàn)initialize方法:
+ (void)initialize
{
static BOOL initialized = NO;
if (!initialized) {
// Perform initialization here.
...
initialized = YES;
}
}
- 根類方法
所有的類和實例, 都需要有一個和runtime環(huán)境交互的接口。 類對象和實例都必須對自己有能力完成自省,并能夠匯報自己在繼承關(guān)系中所處的位置,所以要實現(xiàn)兩次:一次為實例提供可以和運行時環(huán)境交互的接口,另一次在類對象中復(fù)制這些接口。 而NSObject會提供這些接口,所以NSObject不需要實現(xiàn)兩次。類對象扮演另外一個角色,就是執(zhí)行根類中定義的實例方法。當類對象接收一個不能響應(yīng)消息時,運行時環(huán)境會判斷是否有根實例方法可以響應(yīng)這個消息。類對象能夠執(zhí)行的唯一的實例方法就是那些在根類中定義的方法,而且還必須是沒有類方法能完成這項工作時才會由根類方法來完成。有關(guān)類方法執(zhí)行實例方法的特殊能力,請參考有關(guān)NSObject類的說明。
- 代碼中的類名
在代碼中,類名職能在兩種不同的上下文環(huán)境中出現(xiàn),這兩種情況也反映了類名作為數(shù)據(jù)類型和對象的雙重角色:
Rectangle * anObject;
這里anObject被明確地定義為指向Rectangle的一個指針。編譯器會認為它具有Rectangle的數(shù)據(jù)結(jié)構(gòu)和Rectangle的實例方法以及Rectangle所繼承的方法。靜態(tài)類型匹配可以讓編譯器做到更好的類型檢查并使得代碼更加清晰。但只有實例才能靜態(tài)地進行類型匹配,類對象不能,因為它們不屬于某個類的一種。
而在發(fā)送消息的表達式中,作為消息的接收者,類名代表的是類對象,這種用法在前面的例子中已經(jīng)多次展示過。只有作為消息接收者,類名才能代表一個類對象。在任何別的上下文環(huán)境中,必須要先通過發(fā)送一個class消息,獲取類對象的id,例如:
if ( [anObject isKindOfClass:[Rectangle class]] )
如果你不知道在編譯時類名稱,僅僅是用“ Rectangle”這個名稱作為參數(shù),NSClassFromString將返回類對象:
NSString *className;
if ( [anObject isKindOfClass:NSClassFromString(className)] )
如果className字符串不是一個有效的類名,返回nil。類名和全局變量存在同一個nameSpace,具有唯一性。
附錄1:
類的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu):
typedef struct objc_class *Class;
struct objc_class { // 類的數(shù)據(jù)結(jié)構(gòu)
Class isa OBJC_ISA_AVAILABILITY; //
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy),則super_class為NULL。
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認為0
long info OBJC2_UNAVAILABLE; // 類信息,供運行期使用的一些位標識
long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
類實例數(shù)據(jù)結(jié)構(gòu):
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
可以看到,這個結(jié)構(gòu)體只有一個字體,即指向其類的isa指針。因此id其實就是指向Class類型的指針。這樣,當我們向一個Objective-C對象發(fā)送消息時,運行時庫會根據(jù)實例對象的isa指針找到這個實例對象所屬的類。Runtime庫會在類的方法列表及父類的方法列表中去尋找與消息對應(yīng)的selector指向的方法。找到后即運行這個方法。
當創(chuàng)建一個特定類的實例對象時,分配的內(nèi)存包含一個objc_object數(shù)據(jù)結(jié)構(gòu),然后是類的實例變量的數(shù)據(jù)。NSObject類的alloc和allocWithZone:方法使用函數(shù)class_createInstance來創(chuàng)建objc_object數(shù)據(jù)結(jié)構(gòu)。
另外還有我們常見的id,它是一個objc_object結(jié)構(gòu)類型的指針。它的存在可以讓我們實現(xiàn)類似于C++中泛型的一些操作。該類型的對象可以轉(zhuǎn)換為任何一種對象,有點類似于C語言中void *指針類型的作用。
- isa:
isa指針指向其類.
NSArray *array = [NSArray array];
顯而易見, array的數(shù)據(jù)結(jié)構(gòu)中isa指針指向NSArray。
而其實在這個例子中,+array消息發(fā)送給了NSArray類,這個NSArray也是一個對象,類對象。它也包含一個指向其類的一個isa指針。那么這些就有一個問題了,這個isa指針指向什么呢?即metaClass元類。 meta-class是一個類對象的類。當我們向一個對象發(fā)送消息時,runtime會在這個對象所屬的這個類的方法列表中查找方法;而向一個類發(fā)送消息時,會在這個類的meta-class的方法列表中查找。
meta-class之所以重要,是因為它存儲著一個類的所有類方法。每個類都會有一個單獨的meta-class,因為每個類的類方法基本不可能完全相同。
如果你有想法,再深入一下,meta-class也是一個類,也可以向它發(fā)送一個消息,那么它的isa又是指向什么呢?為了不讓這種結(jié)構(gòu)無限延伸下去,Objective-C的設(shè)計者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類。 - super_class:
指向該類的父類,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy),則super_class為NULL。 - cache:
用于緩存最近使用的方法。一個接收者對象接收到一個消息時,它會根據(jù)isa指針去查找能夠響應(yīng)這個消息的對象。在實際使用中,這個對象只有一部分方法是常用的,很多方法其實很少用或者根本用不上。這種情況下,如果每次消息來時,我們都是methodLists中遍歷一遍,性能勢必很差。這時,cache就派上用場了。在我們每次調(diào)用過一個方法后,這個方法就會被緩存到cache列表中,下次調(diào)用的時候runtime就會優(yōu)先去cache中查找,如果cache沒有,才去 methodLists中查找方法。這樣,對于那些經(jīng)常用到的方法的調(diào)用,但提高了調(diào)用的效率。結(jié)構(gòu)如下:
struct objc_cache {
/* mask :一個整數(shù),指定分配的緩存bucket的總數(shù)。在方法查找過程中,
Objective-C runtime使用這個字段來確定開始線性查找數(shù)組的索引位
置。指向方法selector的指針與該字段做一個AND位操作(index =
(mask & selector))。這可以作為一個簡單的hash散列算法。*/
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
/*occupied:一個整數(shù),指定實際占用的緩存bucket的總數(shù)。*/
unsigned int occupied OBJC2_UNAVAILABLE;
/*buckets:指向Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組。這個數(shù)組可能包含不
超過mask+1個元素。需要注意的是,指針可能是NULL,表示這個緩
存bucket沒有被占用,另外被占用的bucket可能是不連續(xù)的。這個數(shù)
組可能會隨著時間而增長。*/
Method buckets[1] OBJC2_UNAVAILABLE;
};
- version:
我們可以使用這個字段來提供類的版本信息。
附錄2
類與對象操作函數(shù)
runtime提供了大量的函數(shù)來直接操作類與對象數(shù)據(jù)結(jié)構(gòu),對照附錄1。類的操作方法大部分是以class為前綴的,而對象的操作方法大部分是以objc或object_為前綴。下面我們將根據(jù)這些方法的用途來分類討論這些方法的使用。
1. 類相關(guān)操作函數(shù)
我們可以回過頭去看看objc_class的定義,runtime提供的操作類的方法主要就是針對這個結(jié)構(gòu)體中的各個字段的。
類名(name)
類名操作的函數(shù)主要有:
// 獲取類的類名
const char * class_getName ( Class cls );
- 對于class_getName函數(shù),如果傳入的cls為Nil,則返回一個char字符串。
父類(super_class)和元類(meta-class)
父類和元類操作的函數(shù)主要有:
// 獲取類的父類
Class class_getSuperclass ( Class cls );
// 判斷給定的Class是否是一個元類
BOOL class_isMetaClass ( Class cls );
- class_getSuperclass函數(shù),當cls為Nil或者cls為根類時,返回Nil。不過通常我們可以使用NSObject類的superclass方法來達到同樣的目的。
- class_isMetaClass函數(shù),如果是cls是元類,則返回YES;如果否或者傳入的cls為Nil,則返回NO。
實例變量大小(instance_size)
實例變量大小操作的函數(shù)有:
// 獲取實例大小
size_t class_getInstanceSize ( Class cls );
成員變量(ivars)及屬性
在objc_class中,所有的成員變量、屬性的信息是放在鏈表ivars中的。ivars是一個數(shù)組,數(shù)組中每個元素是指向Ivar(變量信息)的指針。runtime提供了豐富的函數(shù)來操作這一字段。大體上可以分為以下幾類:
- 成員變量操作函數(shù),主要包含以下函數(shù):
// 獲取類中指定名稱實例成員變量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 獲取類成員變量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 添加成員變量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
// 獲取整個成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
- class_getInstanceVariable函數(shù),它返回一個指向包含name指定的成員變量信息的objc_ivar結(jié)構(gòu)體的指針(Ivar)。
- class_getClassVariable函數(shù),目前沒有找到關(guān)于Objective-C中類變量的信息,一般認為Objective-C不支持類變量。注意,返回的列表不包含父類的成員變量和屬性。
- Objective-C不支持往已存在的類中添加實例變量,因此不管是系統(tǒng)庫提供的提供的類,還是我們自定義的類,都無法動態(tài)添加成員變量。但如果我們通過運行時來創(chuàng)建一個類的話,又應(yīng)該如何給它添加成員變量呢?這時我們就可以使用class_addIvar函數(shù)了。不過需要注意的是,這個方法只能在objc_allocateClassPair函數(shù)與objc_registerClassPair之間調(diào)用。另外,這個類也不能是元類。成員變量的按字節(jié)最小對齊量是1<<alignment。這取決于ivar的類型和機器的架構(gòu)。如果變量的類型是指針類型,則傳遞log2(sizeof(pointer_type))。
- class_copyIvarList函數(shù),它返回一個指向成員變量信息的數(shù)組,數(shù)組中每個元素是指向該成員變量信息的objc_ivar結(jié)構(gòu)體的指針。這個數(shù)組不包含在父類中聲明的變量。outCount指針返回數(shù)組的大小。需要注意的是,我們必須使用free()來釋放這個數(shù)組。
屬性操作函數(shù),主要包含以下函數(shù):
// 獲取指定的屬性
objc_property_t class_getProperty ( Class cls, const char *name );
// 獲取屬性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
// 為類添加屬性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 替換類的屬性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
這一種方法也是針對ivars來操作,不過只操作那些是屬性的值。我們在后面介紹屬性時會再遇到這些函數(shù)。在MAC OS X系統(tǒng)中,我們可以使用垃圾回收器。runtime提供了幾個函數(shù)來確定一個對象的內(nèi)存區(qū)域是否可以被垃圾回收器掃描,以處理strong/weak引用。這幾個函數(shù)定義如下:
const uint8_t * class_getIvarLayout ( Class cls );
void class_setIvarLayout ( Class cls, const uint8_t *layout );
const uint8_t * class_getWeakIvarLayout ( Class cls );
void class_setWeakIvarLayout ( Class cls, const uint8_t *layout );
但通常情況下,我們不需要去主動調(diào)用這些方法;在調(diào)用objc_registerClassPair時,會生成合理的布局。在此不詳細介紹這些函數(shù)。
方法(methodLists)
方法操作主要有以下函數(shù):
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 替代方法的實現(xiàn)
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// 返回方法的具體實現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 類實例是否響應(yīng)指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
- class_addMethod的實現(xiàn)會覆蓋父類的方法實現(xiàn),但不會取代本類中已存在的實現(xiàn),如果本類中包含一個同名的實現(xiàn),則函數(shù)會返回NO。如果要修改已存在實現(xiàn),可以使用method_setImplementation。一個Objective-C方法是一個簡單的C函數(shù),它至少包含兩個參數(shù)—self和_cmd。所以,我們的實現(xiàn)函數(shù)(IMP參數(shù)指向的函數(shù))至少需要兩個參數(shù),如下所示:
void myMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
與成員變量不同的是,我們可以為類動態(tài)添加方法,不管這個類是否已存在。
另外,參數(shù)types是一個描述傳遞給方法的參數(shù)類型的字符數(shù)組,這就涉及到類型編碼,我們將在后面介紹。
- class_getInstanceMethod、class_getClassMethod函數(shù),與class_copyMethodList不同的是,這兩個函數(shù)都會去搜索父類的實現(xiàn)。
- class_copyMethodList函數(shù),返回包含所有實例方法的數(shù)組,如果需要獲取類方法,則可以使用class_copyMethodList(object_getClass(cls), &count)(一個類的實例方法是定義在元類里面)。該列表不包含父類實現(xiàn)的方法。outCount參數(shù)返回方法的個數(shù)。在獲取到列表后,我們需要使用free()方法來釋放它。
- class_replaceMethod函數(shù),該函數(shù)的行為可以分為兩種:如果類中不存在name指定的方法,則類似于class_addMethod函數(shù)一樣會添加方法;如果類中已存在name指定的方法,則類似于method_setImplementation一樣替代原方法的實現(xiàn)。
- class_getMethodImplementation函數(shù),該函數(shù)在向類實例發(fā)送消息時會被調(diào)用,并返回一個指向方法實現(xiàn)函數(shù)的指針。這個函數(shù)會比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函數(shù)指針可能是一個指向runtime內(nèi)部的函數(shù),而不一定是方法的實際實現(xiàn)。例如,如果類實例無法響應(yīng)selector,則返回的函數(shù)指針將是運行時消息轉(zhuǎn)發(fā)機制的一部分。
- class_respondsToSelector函數(shù),我們通常使用NSObject類的respondsToSelector:或instancesRespondToSelector:方法來達到相同目的。
協(xié)議(objc_protocol_list)
協(xié)議相關(guān)的操作包含以下函數(shù):
// 添加協(xié)議
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// 返回類是否實現(xiàn)指定的協(xié)議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回類實現(xiàn)的協(xié)議列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
- class_conformsToProtocol函數(shù)可以使用NSObject類的conformsToProtocol:方法來替代。
- class_copyProtocolList函數(shù)返回的是一個數(shù)組,在使用后我們需要使用free()手動釋放。
版本(version)
版本相關(guān)的操作包含以下函數(shù):
// 獲取版本號
int class_getVersion ( Class cls );
// 設(shè)置版本號
void class_setVersion ( Class cls, int version );
其它
runtime還提供了兩個函數(shù)來供CoreFoundation的tool-free bridging使用,即:
Class objc_getFutureClass ( const char *name );
void objc_setFutureClass ( Class cls, const char *name );
通常我們不直接使用這兩個函數(shù)。
2. 實例相關(guān)操作函數(shù)
實例操作函數(shù)主要是針對我們創(chuàng)建的實例對象的一系列操作函數(shù),我們可以使用這組函數(shù)來從實例對象中獲取我們想要的一些信息,如實例對象中變量的值。這組函數(shù)可以分為三小類:
針對整個對象進行操作的函數(shù),這類函數(shù)包含
// 返回指定對象的一份拷貝
id object_copy ( id obj, size_t size );
// 釋放指定對象占用的內(nèi)存
id object_dispose ( id obj );
有這樣一種場景,假設(shè)我們有類A和類B,且類B是類A的子類。類B通過添加一些額外的屬性來擴展類A?,F(xiàn)在我們創(chuàng)建了一個A類的實例對象,并希望在運行時將這個對象轉(zhuǎn)換為B類的實例對象,這樣可以添加數(shù)據(jù)到B類的屬性中。這種情況下,我們沒有辦法直接轉(zhuǎn)換,因為B類的實例會比A類的實例更大,沒有足夠的空間來放置對象。此時,我們就要以使用以上幾個函數(shù)來處理這種情況,如下代碼所示:
NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
object_setClass(newB, MyClass.class);
object_dispose(a);針對對象實例變量進行操作的函數(shù),這類函數(shù)包含:
// 修改類實例的實例變量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
// 獲取對象實例變量的值
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
// 返回指向給定對象分配的任何額外字節(jié)的指針
void * object_getIndexedIvars ( id obj );
// 返回對象中實例變量的值
id object_getIvar ( id obj, Ivar ivar );
// 設(shè)置對象中實例變量的值
void object_setIvar ( id obj, Ivar ivar, id value );
如果實例變量的Ivar已經(jīng)知道,那么調(diào)用object_getIvar會比object_getInstanceVariable函數(shù)快,相同情況下,object_setIvar也比object_setInstanceVariable快。針對對象的類進行操作的函數(shù),這類函數(shù)包含:
// 返回給定對象的類名
const char * object_getClassName ( id obj );
// 返回對象的類
Class object_getClass ( id obj );
// 設(shè)置對象的類
Class object_setClass ( id obj, Class cls );
獲取類定義
Objective-C動態(tài)運行庫會自動注冊我們代碼中定義的所有的類。我們也可以在運行時創(chuàng)建類定義并使用objc_addClass函數(shù)來注冊它們。runtime提供了一系列函數(shù)來獲取類定義相關(guān)的信息,這些函數(shù)主要包括:
// 獲取已注冊的類定義的列表
int objc_getClassList ( Class *buffer, int bufferCount );
// 創(chuàng)建并返回一個指向所有已注冊類的指針列表
Class * objc_copyClassList ( unsigned int *outCount );
// 返回指定類的類定義
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
Class objc_getRequiredClass ( const char *name );
// 返回指定類的元類
Class objc_getMetaClass ( const char *name );
- objc_getClassList函數(shù):獲取已注冊的類定義的列表。我們不能假設(shè)從該函數(shù)中獲取的類對象是繼承自NSObject體系的,所以在這些類上調(diào)用方法是,都應(yīng)該先檢測一下這個方法是否在這個類中實現(xiàn)。
下面代碼演示了該函數(shù)的用法:
int numClasses;
Class * classes = NULL;
numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
classes = malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
NSLog(@"number of classes: %d", numClasses);
for (int i = 0; i < numClasses; i++) {
Class cls = classes[i];
NSLog(@"class name: %s", class_getName(cls));
}
free(classes);
}
輸出結(jié)果如下:
2014-10-23 16:20:52.589 RuntimeTest[8437:188589] number of classes: 1282
2014-10-23 16:20:52.589 RuntimeTest[8437:188589] class name: DDTokenRegexp
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: _NSMostCommonKoreanCharsKeySet
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: OS_xpc_dictionary
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: NSFileCoordinator
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: NSAssertionHandler
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: PFUbiquityTransactionLogMigrator
2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: NSNotification
2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: NSKeyValueNilSetEnumerator
2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: OS_tcp_connection_tls_session
2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: _PFRoutines
......還有大量輸出
獲取類定義的方法有三個:objc_lookUpClass, objc_getClass和objc_getRequiredClass。如果類在運行時未注冊,則objc_lookUpClass會返回nil,而objc_getClass會調(diào)用類處理回調(diào),并再次確認類是否注冊,如果確認未注冊,再返回nil。而objc_getRequiredClass函數(shù)的操作與objc_getClass相同,只不過如果沒有找到類,則會殺死進程。
- objc_getMetaClass函數(shù):如果指定的類沒有注冊,則該函數(shù)會調(diào)用類處理回調(diào),并再次確認類是否注冊,如果確認未注冊,再返回nil。不過,每個類定義都必須有一個有效的元類定義,所以這個函數(shù)總是會返回一個元類定義,不管它是否有效。