Object, Class and Meta Class in Objective-C
寫了一陣子的 Objective-C/iOS app,這次讓我們回頭來看點基礎的東西 :)
什麼是 id?
在 Objective-C 裡,變數宣告通常得告知編譯器這個變數的型別,例如這樣:
NSArray* myArray = @[@"eddie", @"kao"];
而在 Objective-C 裡有個特別的型別叫做 id
,它可以指向任何物件,像這樣:
id myObject = @[@"eddie", @"kao"];
那這個 id
是什麼? 我們直接來從原始程式碼來找這個 id
的定義。大家可以打開 XCode,然後選擇「File」→「Open Quickly..」,應該可以找到 objc.h
這個檔案,往下捲一點,應該可以找到像下面的這幾行程式碼:
/// 檔案名稱:objc.h
/// A pointer to an instance of a class.
typedef struct objc_object *id;
就照上面這段註解說的,所謂的 id
就是一個「指向一個實例 (instance) 的指針」,而且 id
這個 Struct 的定義本身就有帶一個 *
,這也是為什麼你在宣告其它一般物件型別的變數需要加註一個 *
來表示這是一個指標變數,而使用 id
卻不需要的原因。
什麼是一個物件(Object)?
讓我們再從剛剛那個檔案,順蔓摸瓜的繼續看下去..
/// 檔案名稱:objc.h
/// Represents an instance of a class.
struct objc_object {
Class isa;
};
從這段程式碼可以知道,其實所謂的「物件」,說穿了就只是一個 C 的結構(C Struct),而在這個 Struct 裡面只有一個 isa
的指針,指向自己所屬的類別。
再來我們來看看在 Objective-C 裡,到底什麼是一個類別(Class)
什麼是類別(Class)?
繼續來看剛剛的那個檔案:
/// 檔案:objc.h
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
看得出來其實 Class
本身也是一個 C Struct,再往下看一下這個 objc_class
的定義 (runtime.h):
/// 檔案:runtime.h
struct objc_class {
Class isa;
#if !__OBJC2__
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
#endif
};
從上面這段程式碼可以看到,所謂的類別也是一個 C Struct,裡面第一個指針 isa
指向它的所屬類別,第二個指針 super_class
則是指向它的父類別。除此之外還有一些其它的指針,指向例如 instance variables、method list、cache、protocol 等 Struct。
訊息傳遞(Message Passing)
大家在其它 Objective-C 的書上應該常看到,在 Objective-C 的世界裡,方法不像其它程式語言一樣的被直接呼叫,而是透過「訊息傳遞(Message Passing)」方式,像是這樣:
[someObject saySomething:@"hello"]
這裡的 someObject
稱作 receiver
,就是接收「訊息」的傢伙,而 saySomething:
就是「訊息」,@"hello"
則是參數。
事實上,在訊息傳遞的過程,當 someObject
這傢伙收到訊息之後,會順著這個物件的 isa
指針找到自己的類別,然後再依照收到的「訊息」去類別的 method list 找出對應的方法。如果在這個類別裡面找到了,就會直接執行它,如果找不到,會再往上一層類別(super class)找。以上這個流程都是在程式執行過程中(Rumtime)才動態決定的,而不是在編譯(Compile)時期就決定好的。
而這個訊息傳遞的過程畢竟是多做了幾件事,相對感覺會比較耗時,但實際上在程式的執行過程中,一但執行過的方法就會被暫存(cache)下來,下次再收到一樣的訊息的時候就會快得多了。
其實,在 Objective-C 的世界裡,類別本身也是物件,所以你也可以對它「發送訊息」,像是這樣:
[NSArray arrayWithObjects:@"eddie", @"kao", nil];
看起來就跟在其它程式語言的「類別方法」差不多,但事實上它就是對 NSArray
這個類別發送了 arrayWithObjects:
這個訊息。
接下來我們直接寫一小段程式會比較容易想像:
// Animal Class
@interface Animal : NSObject
@end
@implementation Animal
@end
// Fox Class
@interface Fox : Animal
- (void) say;
@end
@implementation Fox
- (void) say
{
NSLog(@"What does the fox say!?");
}
@end
上面這段程式碼建立了兩個類別,分別是 Animal
及 Fox
,而且 Fox
繼承自 Animal
,而 Animal
類別則是繼承自 NSObject
類別。
不管是「物件」或是「類別」,因為一樣都是物件,所以他們在收到訊息時的反應流程也是一樣的:
- 當對「物件」發送訊息的時候,它會順著這個物件的
isa
指針找到它的所屬類別,然後翻看看這個類別的 method list 有沒有符合的方法可以執行; - 當對「類別」發送訊息的時候,它會順著這個類別的
isa
指針找到它的所屬類別(也就是我們待會要說明的 Meta Class),然後翻看看這個類別的 method list 有沒有符合的方法可以執行。
什麼是 Meta Class?
Meta
這個字我不太懂中文該怎麼翻譯比較好,有人翻譯成「元」,有人翻譯成「後設」,但我個人還是喜歡直接使用 Meta
這個詞就好。
剛剛在看類別的 objc_class
的定義的時候有提到類別的 Struct 裡也有一個 isa
指針,指向它所屬的類別,你可以想像成是它是「類別的類別」,也就是所謂的 Meta Class
。
而 Meta Class 其實也是一種類別,所以也跟一般的類別一樣有 isa
跟 super_class
指針... 所以就可以把這整個的關係用一張圖表來解釋:
看圖說故事:
- 每個 Class (Fox, Animal, NSObject) 的
isa
指針都指向一個唯一的 Class,這個 Class 稱之為Meta Class
。 - 每個 Meta Class 的
isa
指針都是指向最上層的 Meta Class (在我們上面那段範例裡,就是 NSObject 的 Meta Class),而最上層的 Meta Class 的isa
則是指向自己,形成一個迴路。 - 每個 Meta Class 的
super_class
指針都是指向它原本類別的 Super Class 的 Meta Class,但最上層的 Meta Class 的super_class
則是指向 NSObject 類別本身。 - 最上層的 Class (NSObject),它的 Super Class 指向
nil
。
PS:在 Objective-C 裡有兩種 Root Class(NSObject 跟 NSProxy),但因為在 Objective-C 裡「大部份」的類別都是 NSObject
的子類別,所以舉例常會說最上層的類別就是 NSObject
。
一堆 Class、Super Class、Meta Class 的,有種快打結的感覺了嗎? 其實只要理解上面那張圖片,基本上就不用記這些繞舌的規則了。
但口說無憑,讓我們來寫幾行程式碼驗證一下:
Fox* lucky = [[Fox alloc] init];
[lucky say];
// about fox instance
NSLog(@"the class of lucky is %@, address = %p", [lucky class], [lucky class]);
// about Fox class
NSLog(@"Fox = %@, address = %p", [Fox class], [Fox class]);
NSLog(@"Fox's Super Class = %@, address = %p", [Fox superclass], [Fox superclass]);
Class metaClassOfFox = object_getClass([Fox class]);
NSLog(@"Fox's Meta Class = %p", metaClassOfFox);
NSLog(@"Fox's Meta Class's Super Class = %p", [metaClassOfFox superclass]);
Class metaMetaClassOfFox = object_getClass(metaClassOfFox);
NSLog(@"Fox's Meta Class's Meta Class = %p", metaMetaClassOfFox);
// about Animal class
NSLog(@"Animal = %@, address = %p", [Animal class], [Animal class]);
NSLog(@"Animal's Super Class = %@, address = %p", [Animal superclass], [Animal superclass]);
Class metaClassOfAnimal = object_getClass([Animal class]);
NSLog(@"Animal's Meta Class = %p", metaClassOfAnimal);
NSLog(@"Animal's Meta Class's Super Class = %p", [metaClassOfAnimal superclass]);
Class metaMetaClassOfAnimal = object_getClass(metaClassOfAnimal);
NSLog(@"Animal's Meta Class's Meta Class = %p", metaMetaClassOfAnimal);
// about NSObject class
NSLog(@"NSObject = %@, address = %p", [NSObject class], [NSObject class]);
NSLog(@"NSObject's Super Class = %@, address = %p", [NSObject superclass], [NSObject superclass]);
Class metaClassOfNSObject = object_getClass([NSObject class]);
NSLog(@"NSObject's Meta Class = %p", metaClassOfNSObject);
NSLog(@"NSObject's Meta Class's Super Class = %p", [metaClassOfNSObject superclass]);
Class metaMetaClassOfNSObject = object_getClass(metaClassOfNSObject);
NSLog(@"NSObject's Meta Class's Meta Class = %p", metaMetaClassOfNSObject);
輸出結果如下:
What does the fox say!?
the class of lucky is Fox, address = 0x100002260
Fox = Fox, address = 0x100002260
Fox's Super Class = Animal, address = 0x100002210
Fox's Meta Class = 0x100002238
Fox's Meta Class's Super Class = 0x1000021e8
Fox's Meta Class's Meta Class = 0x7fff77cce838
Animal = Animal, address = 0x100002210
Animal's Super Class = NSObject, address = 0x7fff77cce810
Animal's Meta Class = 0x1000021e8
Animal's Meta Class's Super Class = 0x7fff77cce838
Animal's Meta Class's Meta Class = 0x7fff77cce838
NSObject = NSObject, address = 0x7fff77cce810
NSObject's Super Class = (null), address = 0x0
NSObject's Meta Class = 0x7fff77cce838
NSObject's Meta Class's Super Class = 0x7fff77cce810
NSObject's Meta Class's Meta Class = 0x7fff77cce838
輸出結果可能在每個人的電腦上都不一樣,但從輸出的結果就能證明上面那張圖片的每個類別之間的關係。
要特別注意的是,你可能會覺得直接對類別發送 class
訊息就可以得到該類別的 Meta Class,事實上直接對類別發送 class
訊息只會得到該類別自己。要取得類別的 Meta Class,可以透過 object_getClass
來取得。
以上,希望這篇文章能讓大家對 Objective-C 基本的物件跟類別有更進一步的了解。
個人認為,雖然了解這些知識不見得對 iOS app 的開發有直接的幫助,但至少當在寫一個類別或使用一個物件的時候,會更清楚到底是怎麼一回事。如果有哪邊寫錯,還請前輩先進不吝指點。