# Object, Class and Meta Class in Objective-C

> 

Published: 2013-12-05
URL: https://kaochenlong.com/object-class-and-meta-class-in-objective-c

---

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


