Objective-Cのivarについて


Objective-C 2.0のオブジェクトとivarが謎だったので少し調べた。(この前は[弱参照について調べた](http://qiita.com/hotpepsi/items/b2f5d917d82d02828ab8))
iOSのARM64環境を前提としている。

Objective-Cのオブジェクトは以下のようなものである。

+ NSObjectを継承している
+ メソッドに応答する
+ インスタンス変数を持つ

### objc_object

NSObjectの元となるobjc_objectは、isaという要素を持った構造体である。isaはクラスオブジェクトのポインタで、そのオブジェクトのクラスを示す。
idは基本的にはobjc_objectのポインタだが、タグなどの情報がミックスされている。(最上位ビットが0のとき、下位3ビットを0にしたものがアドレス)
https://github.com/opensource-apple/objc4/blob/master/runtime/objc-private.h#L127

```cpp:objc_object
struct objc_class;
struct objc_object;
typedef struct objc_class *Class;
typedef struct objc_object *id;
union isa_t {
  Class cls;
};

struct objc_object {
  isa_t isa;
};
```

### objc_class

クラスオブジェクトもまたオブジェクトだが、もう少し情報を持っているobjc_classという構造体である。objc_objectを継承していて、先頭のメンバは(objc_objectの)isaである。
Objective-C 1.0と異なり、ivarのリストなどの情報は持っていない。

https://github.com/opensource-apple/objc4/blob/master/runtime/objc-runtime-new.h#L1012


```cpp:objc_class
typedef uint32_t mask_t;
struct cache_t {
  struct bucket_t *_buckets;
  mask_t _mask;
  mask_t _occupied;
};
struct class_data_bits_t {
  uintptr_t bits;
};

struct objc_class : objc_object {
  Class superclass;
  cache_t cache;
  class_data_bits_t bits;
};
```

### 継承関係

継承関係を調べるため、Cのポインタに変換して表示してみる。同名だと怒られるので別名で構造体を定義する。
※ ARC下で生ポインタを得るにはbridge castが必要である。

```objectivec:obj
typedef struct objc_klass *Klass;
typedef uint32_t mask_t;

struct objc_objekt {
    Klass isa;
};

struct objc_klass {
    Klass isa;
    Klass superclass;
    struct cache_t {
        struct bucket_t *_buckets;
        mask_t _mask;
        mask_t _occupied;
    } cache;
    struct class_data_bits_t {
        uintptr_t bits;
    } bits;
};

...

NSObject *obj = [NSObject new];
struct objc_objekt *p = (__bridge struct objc_objekt *) obj;
NSLog(@"obj: %p", p);
NSLog(@"class of obj (NSObject): %p, %p, %p", p->isa, [obj class], objc_getClass("NSObject"));
NSLog(@"class of NSObject: %p, super class of NSObject: %p", p->isa->isa, p->isa->superclass);
NSLog(@"class of class of NSObject: %p, super class of class of NSObject: %p", p->isa->isa->isa, p->isa->isa->superclass);
```

上の実行結果は以下のような感じになる。(アドレスは毎回変わる)

```
obj: 0x60000000a050
class of obj (NSObject): 0x103adbe88, 0x103adbe88, 0x103adbe88
class of NSObject: 0x103adbe38, super class of NSObject: 0x0
class of class of NSObject: 0x103adbe38, super class of class of NSObject: 0x103adbe88
```

先頭に入っている値isaが[obj class]やobjc_getClass("NSObject")と同じもの(NSObjectのクラスオブジェクト)を指していることがわかる。
NSObjectを継承したオブジェクトHogeについて試してみると、

```objectivec:hoge
@interface Hoge : NSObject
@property NSString *x;
@property NSString *y;
@end

@implementation Hoge
@end

...

hoge = [Hoge new];
struct objc_objekt *p = (__bridge struct objc_objekt *) hoge;
NSLog(@"hoge: %p", p);
NSLog(@"class of hoge (Hoge): %p, %p, %p", p->isa, [hoge class], objc_getClass("Hoge"));
NSLog(@"class of Hoge: %p, super class of Hoge: %p", p->isa->isa, p->isa->superclass);
NSLog(@"class of class of Hoge: %p, super class of class of Hoge: %p", p->isa->isa->isa, p->isa->isa->superclass);
```

以下のようなものが得られる。

```
hoge: 0x608000257d90
class of hoge (Hoge): 0x1031407e8, 0x1031407e8, 0x1031407e8
class of Hoge: 0x1031407c0, super class of Hoge: 0x103adbe88
class of class of Hoge: 0x103adbe38, super class of class of Hoge: 0x103adbe38
```

### 継承関係図

継承関係を図にすると以下のようになる。Objective-C 1.0と同様の結果となった。
(参考: http://news.mynavi.jp/column/objc/016/)

![hogehoge.png](https://qiita-image-store.s3.amazonaws.com/0/20902/af702c52-ee30-c8d3-1453-9f79452bc145.png "hogehoge.png")

なお上図は[MarkDownDiagram](https://github.com/wakufactory/MarkDownDiagram)で生成した。ソースは以下の通り。

```
[b1] <5,5>
#obj 0x60000000a050
isa 0x103adbe88
==>[b2]

[b2] <20,5>
#NSObject 0x103adbe88
isa 0x103adbe38
==>[b3]
---
superclass 0x0

[b3] <37,5>
#meta NSObject 0x103adbe38
---
isa 0x103adbe38
l==>l1[b3]
---
superclass 0x103adbe88
l==>l1[b2]

[b4] <5,15>
#hoge 0x608000257d90
isa 0x1031407e8
==>[b5]

[b5] <20,15>
#Hoge 0x1031407e8
isa 0x1031407c0
==>[b6]
---
superclass 0x103adbe88
l==>l1[b2]

[b6] <37,15>
#meta Hoge 0x1031407c0
---
isa 0x103adbe38
l==>l1[b3]
---
superclass 0x103adbe38
l==>l1[b3]
```

# ivar

オブジェクトが構造体のようなものであれば、isaに続けて格納されているのではと予想できる。
Objective-Cのインスタンス変数はivarと呼ばれている。ivarは以下のような定義で、名前と型と、オブジェクトのどこに格納されるかというオフセットを持つ。

```cpp:objc_ivar
struct objc_ivar {
    char *ivar_name;
    char *ivar_type;
    int ivar_offset;
};
```

前掲のHogeについて試してみると、

```objectivec:Hoge
@interface Hoge : NSObject
@property NSString *x;
@property NSString *y;
@end
@implementation Hoge
@end

...

unsigned int count;
Ivar *ivars = class_copyIvarList([Hoge class], &count);
for (int i = 0; i < count; ++i) {
  Ivar ivar = ivars[i];
  NSLog(@"name: %s, offset: %lu", ivar_getName(ivar), ivar_getOffset(ivar));
}
free(ivars);
```

以下のような結果が得られた。

```
name: _x, offset: 8
name: _y, offset: 16
```

64bit環境ではisaのサイズは8なので、連続していそうである。
試しに構造体にキャストして内容を表示してみると、isaの次に格納されているようである。

```objectivec:hogehoge
@interface Hoge : NSObject
@property NSString *x;
@property NSString *y;
@end
@implementation Hoge
@end

struct HogeHoge {
  void *isa;
  void *x;
  void *y;
};

...

hoge = [Hoge new];
hoge.x = @"hoge";
hoge.y = @"piyo";

struct HogeHoge *hogehoge = (__bridge struct HogeHoge *) hoge;
NSLog(@"x: %@, y: %@", hogehoge->x, hogehoge->y);
```

実行結果

```
x: hoge, y: piyo
```

### クラスの変更

どうやらObjective-Cは動的型付け言語らしく、実行時にクラスを定義したり、オブジェクトのクラスを変更したりできるらしいので、クラスを変更してみる。
クラスはobject_setClassで変更できる。クラスFugaを定義して、HogeクラスのインスタンスhogeをFugaクラスにしてみる。
Fugaは、メンバxとyの順番と型をHogeとは変えてみた。

```objectivec:hoge
@interface Hoge : NSObject
@property NSString *x;
@property NSString *y;
@end
@implementation Hoge
@end

@interface Piyo : NSObject
@property NSString *piyopiyo;
@end
@implementation Piyo
@end

@interface Fuga : NSObject
@property NSString *y;
@property Piyo *x;
@property NSString *v;
@property NSString *w;
@end
@implementation Fuga
@end

...
NSLog(@"size of %@: %zd", NSStringFromClass([Hoge class]), malloc_size((__bridge const void *) [Hoge new]));
NSLog(@"size of %@: %zd", NSStringFromClass([Fuga class]), malloc_size((__bridge const void *) [Fuga new]));

// dump ivars
unsigned int count;
Ivar *ivars = class_copyIvarList([Fuga class], &count);
for (int i = 0; i < count; ++i) {
  Ivar ivar = ivars[i];
  NSLog(@"name: %s, offset: %lu", ivar_getName(ivar), ivar_getOffset(ivar));
}
free(ivars);

// generate hoge
Hoge *hoge = [Hoge new];
hoge.x = @"hoge";
hoge.y = @"piyo";
NSLog(@"hoge: %zd, %@, %@, %@", malloc_size((__bridge const void *) hoge), hoge, hoge.x, hoge.y);

// change class
object_setClass(hoge, [Fuga class]);
Fuga *fuga = (Fuga *)hoge;
fuga.y = @"fuga";
NSLog(@"fuga: %zd, %@, %@, %@", malloc_size((__bridge const void *) fuga), fuga, fuga.x, fuga.y);
```

以下のような出力が得られた。

```
size of Hoge: 32
size of Fuga: 48
name: _y, offset: 8
name: _x, offset: 16
name: _v, offset: 24
name: _w, offset: 32
hoge: 32, <Hoge: 0x608000228d60>, hoge, piyo
fuga: 32, <Fuga: 0x608000228d60>, piyo, fuga
```

hogeはFugaクラスに変わったが(isaがFugaを指す)、それ以外の中身は変わっていない。Fugaクラスのオブジェクトは通常48バイトだが、クラスを変更しても32バイトのままである。
Fugaのivarはy,xの順番のため、Fugaクラスに変更後にyに ```"fuga"``` を代入すると、先頭が ```"fuga"``` に書き換わる。
fuga.xとfuga.yを表示すると、Hogeにおける2番目、1番目を表示することになるので、 ```piyo, fuga``` になる。

というわけでオブジェクトには余分な情報が含まれておらず、割と効率的に格納されていることがわかった。

### 参考

- [こたつつきみかん &raquo; id型](http://www.lifeaether.com/overtaker/blog/?p=246)
- [インスタンス変数 - HMDT](http://hmdt.jp/cocoaProg/ObjC/Ivar/Ivar.html)
- [Objective-C の季節 - メタクラス、インスタンス変数リスト - ろじかるんるんものがたり](http://d.hatena.ne.jp/lyrical_logical/20110306/1299403381)
- [iOS / Objective-C - struct objc_class * and struct objc_object * - Stack Overflow](http://stackoverflow.com/questions/20874458/ios-objective-c-struct-objc-class-and-struct-objc-object)
- [How are instance variables and methods stored in an Objective-C 2.0 object? - Stack Overflow](http://stackoverflow.com/questions/11837589/how-are-instance-variables-and-methods-stored-in-an-objective-c-2-0-object)