LoginSignup
9
5

More than 5 years have passed since last update.

Objective-Cのivarについて

Posted at

Objective-C 2.0のオブジェクトとivarが謎だったので少し調べた。(この前は弱参照について調べた)
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

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のリストなどの情報は持っていない。

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が必要である。

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について試してみると、

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

なお上図は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は以下のような定義で、名前と型と、オブジェクトのどこに格納されるかというオフセットを持つ。

objc_ivar
struct objc_ivar {
    char *ivar_name;
    char *ivar_type;
    int ivar_offset;
};

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

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の次に格納されているようである。

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とは変えてみた。

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 になる。

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

参考

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5