[譯]Objective-C中的meta-class是什麼。

獨樂樂發表於2018-03-12

原文地址: http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html

在這篇文章中,我看到了Objective-C中的一個陌生概念——元類。Objective-C中的每個類都有它自己的相關元類,但是由於您很少直接使用元類,所以它們仍然是謎一樣的。我將從如何在執行時建立類開始。通過檢查這個建立的“類對”,我將解釋元類是什麼,並且還涵蓋了Objective-C中常見的類與資料。

用runtime建立一個類

下面的程式碼用執行時建立了NSError的一個新的子類,並新增了一個方法:

    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]));
}
複製程式碼

表面上看,這一切都很簡單。在執行時建立類僅僅是三個簡單的步驟:

為“class pair”分配儲存(使用objc_allocateClassPair)。 根據需要向類新增方法和ivars(我已經新增了一個使用class_addMethod的方法)。 註冊類以便它可以被使用(使用objc_registerClassPair)。 然而,最直接的問題是:什麼是“class pair”? 函式objc_allocateClassPair只返回一個值:類。另一半在哪裡?

我確信你已經猜到了,另一半是元類(這是這個帖子的標題),但是為了解釋這是什麼以及為什麼需要它,我將介紹Objective-C中的物件和類的一些背景知識。

資料結構需要什麼才能成為物件?

每個物件都有一個類。這是一個基本的物件導向概念,但在Objective-C中,它也是資料的基礎部分。任何具有指向正確位置上的類的指標的資料結構都可以被視為物件。

在Objective-C中,物件的類由它的isa指標決定。isa指標指向物件的類。

實際上,Objective-C中物件的基本定義是這樣的:

typedef struct objc_object {
    Class isa;
} *id;
複製程式碼

這就是說:任何從指向類結構的指標開始的結構都可以被視為objc_object。

Objective-C中物件最重要的特性是可以向它們傳送訊息:

[@"stringValue"
    writeToFile:@"/file.txt" atomically:YES encoding:NSUTF8StringEncoding error:NULL];
複製程式碼

這是有效的,因為當您向Objective-C物件(比如這裡的NSCFString)傳送訊息時,執行時將尋找物件的isa指標到達物件的類(在本例中為NSCFString類)。然後,該類包含一個方法列表,該列表適用於該類的所有物件,以及一個指向超類的指標,以查詢繼承的方法。執行時通過的關於類和超類的方法來找到一個匹配的訊息選擇器(在本例中,writeToFile:atomically:encoding:error on NSString)。然後執行時呼叫該方法的函式(IMP)。

重要的一點是,類定義了可以傳送到物件的訊息。

什麼是meta-class?

你們可能已經知道,Objective-C中的一個類也是一個物件。這意味著您可以向類傳送訊息。

NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
複製程式碼

在這種情況下,defaultStringEncoding被髮送到NSString類。

這是因為Objective-C中的每個類都是一個物件。這意味著類結構必須從一個isa指標開始,這樣它就可以與我上面顯示的objc_object結構相相容,並且結構中的下一個欄位必須是一個指向超類的指標(或基類的nil)。

有幾種不同的方法可以定義一個類,這取決於您執行的執行時的版本,但是,它們都是從一個isa欄位開始,然後是一個超類欄位。

typedef struct objc_class *Class;
struct objc_class {
    Class isa;
    Class super_class;
    /* followed by runtime specific details... */
};
複製程式碼

然而,為了讓我們在類上呼叫方法,類的isa指標必須指向類結構,類結構必須包含我們可以在類上呼叫的方法的列表。

這匯出了元類的定義:元類是類物件的類。

簡單地說:

當向物件傳送訊息時,該訊息將在物件類的方法列表中查詢。 當您向一個類傳送訊息時,該訊息將在類的元類的方法列表中查詢。 元類非常重要,因為它儲存類的類方法。每個類都必須有一個唯一的元類,因為每個類都有一個可能唯一的類方法列表。

元類的類是什麼

元類,就像前面的類一樣,也是一個物件。這意味著您也可以在它上呼叫方法。當然,這意味著它也必須有一個類。

所有元類都使用基類的元類(繼承層次上的頂級類的元類)作為類。這意味著對於從NSObject(大多數類)層級的所有類,元類都是NSObject元類作為它的類。

遵循所有元類都使用基類的元類作為類的規則,任何基礎元類都將是它自己的類(它們的isa指標指向它們自己)。這意味著在NSObject元類上的isa指標指向它自己(它本身就是一個例項)。

在此附上一張圖幫助理解

[譯]Objective-C中的meta-class是什麼。

類和元類的繼承

就像類用它的超類指標指向超類一樣,元類用它自己的超類指標指向類的超類的元類。

基類的元類將它的super_class設定為基類本身。

這種繼承體系就是,體系中的所有例項、類和元類都繼承了體系的基類。

對於NSObject層級中的所有例項、類和元類,這意味著所有NSObject例項方法都是有效的。對於類和元類,所有NSObject類方法都是有效的。

實驗證實這一猜想

為了確認所有這些,讓我們看一下我在本文開頭給出的ReportFunction的輸出。這個函式的目的是跟蹤isa指標並記錄它找到的內容。

要執行ReportFunction,我們需要動態建立類的例項並在其上轉發方法。

id instanceOfNewClass =
    [[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
[instanceOfNewClass performSelector:@selector(report)];
[instanceOfNewClass release];
複製程式碼

由於沒有報告方法的宣告,所以我使用performSelector來呼叫它:所以編譯器不會發出警告。

ReportFunction現在將遍歷isa指標並告訴我們什麼物件被用作類、元類和元類的類。

獲取一個物件的類:ReportFunction使用object_getClass來跟蹤isa指標,因為isa指標是類的受保護成員(您不能直接訪問其他物件的isa指標)。ReportFunction不使用類方法來執行此操作,因為在類物件上呼叫類方法不會返回元類,而是返回類(因此[NSString類]將返回NSString類而不是NSString元類)。 當程式執行之後,以下是輸出


This object is 0x10010c810.

Class is RuntimeErrorSubclass, and super is NSError.

Following the isa pointer 1 times gives 0x10010c600

Following the isa pointer 2 times gives 0x10010c630

Following the isa pointer 3 times gives 0x7fff71038480

Following the isa pointer 4 times gives 0x7fff71038480

NSObject's class is 0x7fff710384a8

NSObject's meta class is 0x7fff71038480

通過反覆檢視“isa”值達成的地址:

物件是地址0x10010c810。 這個類的地址是0x10010c600。 元類是地址0x10010c630。 元類的類(即NSObject元類)是地址0x7fff71038480。 NSObject的元類是它自己。 地址的值並不重要,只是它顯示了從類到元類到NSObject元類的。

結論

元類是一個類的類物件。每個類都有自己的獨特的元類(因為每一個類可以有它自己的獨特的方法列表)。(This means that all Class objects are not themselves all of the same clas不會。。歡迎在討論區補充)

類物件的元類總是確保所有基類的例項和類方法的體系,加上中間所有的類方法。NSObject的子類,這個子類含有所有NSObject例項和協議方法,這些是從元類被定義好了的。

所有的元類本身,使用基類的元類( NSObject元類)作為他們的類,包括基礎的元類在執行時中都是唯一類。

全文終 感謝閱讀 不準確的地方歡迎指正。

相關文章