2019獨角獸企業重金招聘Python工程師標準>>>
比較簡單的一篇英文,重點是講解meta-class。翻譯下,加深理解。
原文標題:What is a meta-class in Objective-C?
原文地址:http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html
?
本篇將會探討一個在Objective-C中相對陌生的概念 -- meta-class。OC中的每一個類都會有一個與之相關聯的meta class,但是你卻幾乎永遠也不會直接使用到,它們始終籠罩著一層神秘的面紗。筆者將以運行時動態創建一個class為引,通過剖析創建的class pair來弄明白到底meta-class是什么以及更深入的了解它對于OC中對象、類的意義。
?
在運行時創建類
以下代碼演示運行時創建一個NSError的子類,同時添加一個實例方法給它:
Class newClass = objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
objc_registerClassPair(newClass);
函數ReportFunction就是添加的實例方法的具體實現,如下:
void?ReportFunction(id?self,?SEL?_cmd)??
{??NSLog(@"This?object?is?%p.",self);??NSLog(@"Class?is?%@,?and?super?is?%@.",[self?class],[self?superclass]);??Class?currentClass?=?[self?class];??for(?int?i?=?1;?i?<?5;?++i?)??{??NSLog(@"Following?the?isa?pointer?%d?times?gives?%p",i,currentClass);??currentClass?=?object_getClass(currentClass);??}??NSLog(@"NSObject's?class?is?%p",?[NSObject?class]);??NSLog(@"NSObject's?meta?class?is?%p",object_getClass([NSObject?class]));??
}??
看起來一切都很簡單,運行時創建類只需要三步:
1、為"class pair"分配空間(使用objc_allocateClassPair).
2、為創建的類添加方法和成員(上例使用class_addMethod添加了一個方法)。
3、注冊你創建的這個類,使其可用(使用objc_registerClassPair)。
?
估計讀者馬上就要問:什么是“class pair"? objc_allocateClassPair只返回一個值:Class。那么pair的另一半在哪里呢?
是的,估計你已經猜到了這個另一半就是meta-class,也就是這篇短文的標題,但是要解釋清楚它是什么,為什么需要它,還需要交代下OC的對象與類的相關背景。
?
一個數據結構何以成為一個對象?
每個對象都會有一個它所屬的類。這是面向對象的基本概念,但是在OC中,這對所有數據結構有效。任何數據結構,只要在恰當的位置具有一個指針指向一個class,那么,它都可以被認為是一個對象。
在OC中,一個對象所屬于哪個類,是由它的isa指針指向的。這個isa指針指向這個對象所屬的class。
實際上,OC中對象的定義是如下的樣子:
typedef?struct?objc_object?{??Class?isa;??
}*id;?
?
這個定義表明:任何以一個指向Class的指針作為首個成員的數據結構都可以被認為是一個objc_object.
最重要的特性就是,你可以向OC中的任何對象發送消息,如下這樣:
【@”stringValue"?writeToFile:@"/file.txt?atomically:YES?
encoding:?NSUTF8StringEncoding?error:NULL];??
?
運行原理就是,當你向一個OC對象發送消息時(上文的@“stringValue”),運行時庫會根據對象的isa指針找到這個對象所屬的類(上文為例,會找到NSCFString類).這個類會包含一個所有實例方法的列表及一個指向superclass的指針以便可以找到父類的實例方法。運行時庫會在類的方法列表以及父類(們)的方法列表中尋找符合這個selector(上文為例,這個selector是"writeToFile:atomically:encoding:error")的方法。找到后即運行這個方法。關鍵點就是類要定義這個你發送給對象的消息。
什么是meta-class?
至此,你可能已經知道,一個OC的類其實也是一個對象,意思就是你可以向一個類發送消息。
NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
在這個例子中,defaultStringEncoding 被發送給了NSString類。因為每一個OC的類本身也是一個對象。也就是說Class的數據結構必然也是以isa指針開始的在二進制級別上與objc_object是完全兼容的。然后一個類結構的下一個字段一定是一個指向super class的指針(或者指向nil,對于基類而言)。
一個類如何定義有很多方法,依賴于你的運行時庫版本,但是不管哪種方法,他們都是以一個isa作為第一個字段,接著是superclass字段。
typedef?struct?objc_class?*Class;??
struct?objc_class{??Class?isa;??Class?super_class;??/*followed?by?runtime?specific?details...*/??
};??
?
為了可以調用類方法,這個類的isa指針必須指向一個包含這些類方法的類結構體。
這樣就引出了meta-class的概念:meta-class是一個類對象的類。
簡單解釋下:
? ? ? ?當你向一個對象發送消息時,runtime會在這個對象所屬的那個類的方法列表中查找。
? ? ? ?當你向一個類發送消息時,runtime會在這個類的meta-class的方法列表中查找。
meta-class之所以重要,是因為它存儲著一個類的所有類方法。每個類都會有一個單獨的meta-class,因為每個類的類方法基本不可能完全相同。
meta-class的類又是什么呢?
meta-class,就像Class一樣,也是一個對象。你依舊可以向它發送消息調用函數,自然的,meta-class也會有一個isa指針指向其所屬類。所有的meta-class使用基類的meta-class作為他們的所屬類。具體而言,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己所屬的類。
根據這個規則,所有的meta-class使用基類的meta-class作為它們的類,而基類的meta-class也是屬于它自己,也就是說基類的meta-class的isa指針指向它自己。(譯:完美的閉環)
類和meta-class的繼承
就像一個類使用super_class指針指向自己的父類一樣,meta-class的super_class會指向類的super_class的meta-class。一直追溯到基類的meta-class,它的super_class會指向基類自身。(譯:萬物歸根)
這樣一來,整個繼承體系中的實例、類和meta-class都派生自繼承體系中的基類。對于NSObject繼承體系來說,NSObject的實例方法對體系中所有的實例、類和meta-class都是有效的;NSObject的類方法對于體系中所有的類和meta-class都是有效的。
用文字描述總會讓人迷糊,Greg Parker給出了一份精彩的圖譜來展示這些關系:
點擊打開鏈接
實驗證明:
為了證實以上的論述,讓我們查看下開篇代碼中ReportFunction的輸出。這個函數的目的就是沿著isa指針進行打印。
為了運行ErportFunction,我們需要創建一個實例,并調用report方法。
id?instanceOfNewClass?=?[[newClass?alloc]initWithDomain:@"some?Domain"?code:0?userInfo:nil];??
[instanceOfNewClass?performSelector:@"report)];??
[instanceOfNewClass?release];??
因為我們并沒有對report方法進行聲明,所以我們使用performSelector進行調用,這樣避免編譯器警告。
然后ReportFunction函數會沿著isa進行檢索,來告訴我們class,meta-class以及meta-class的class是什么樣的情況:
?
【注:ReportFunction使用object_getClass來獲取isa指針指向的類,因為isa指針是一個受保護成員,你不能直接訪問其他對象的isa指針。ReportFunction沒有使用class方法是因為在一個類對象上調用這個方法是無法獲得meta-class的,它只是返回這個類而已。(所以[NSString class]只是返回NSString類,而不是NSString的meta-class]
以下是程序的輸出:
This?object?is?0x10010c810.??
Class?is?RuntimeErrorSubclass,?and?super?is?NSError.??
Followingthe?isa?pointer?1times?gives?0x10010c600??
Followingthe?isa?pointer?2times?gives?0x10010c630??
Followingthe?isa?pointer?3times?gives?0x7fff71038480??
Followingthe?isa?pointer?4times?gives?0x7fff71038480??
NSObject's?class?is?0x7fff710384a8??
NSObject's?meta?class?is?0x7fff71038480??
?
觀察通過isa獲得的地址:
對象的地址是 ? ? ?0x10010c810.
類的地址是 ? ? ? ? 0x10010c600.
類的meta-class地址是 ?0x10010c630.
類的meta-class的類地址是 ? ? ? ? ? ? ? ? 0x7fff71038480.(即NSOjbect的meta-class)
NSObject的meta-class的類地址是它自身。
這些地址的值并不重要,重要的是它們說明了文中討論的從類到meta-class到NSObject的meta-class的整個流程。
結論:
meta-class是類對象的類,每個類都有自己單獨的meta-class。所有的類對象并不會屬于同一個meta-class。
meta-class要保證類對象具有繼承體系中基類的所有實例和類方法,以及繼承體系中的所有中間類方法。對于所有NSObject繼承體系下的類,NSObject的實例方法和協議方法對他們和他們meta-class的對象都要有效。
所有的meta-class使用基類的meta-class作為自己的基類,對于頂層基類的meta-class也是一樣,只是它指向自己而已