Ivars are not quite as efficient to access as C or C++ struct members. Recall that struct member access is as simple as loading from an offset into the struct:

struct SomeStruct { double dbl ; // Added to make it more clear how offsets are handled by // giving x a nonzero offset. int x ; int y ; }; int accessMember ( struct SomeStruct * o ) { return o->x + o->y; } int accessArray ( int o [4]) { return o[2] + o[3]; }

.globl _accessMember .p2align 2 _accessMember : ; @accessMember ; BB#0: ldp w8, w9, [x0, #8] add w0, w9, w8 ret .globl _accessArray .p2align 2 _accessArray : ; @accessArray ; BB#0: ldp w8, w9, [x0, #8] add w0, w9, w8 ret

To read x and y , accessStruct just loads from offsets 8 and 12 at the location pointed to by o . (As it happens, the ldp ("Load Pair") instruction lets us load two ints at once in this case.) This case is exactly the same as if we were to pass an array of ints, as in accessArray .

Compare this to ivar access:

#import <Foundation/Foundation.h> @interface SomeClass : NSObject { int x ; int y ; } @end @implementation SomeClass - ( int ) accessIvar { return x + y; } @end

"-[SomeClass accessIvar]" : ; @"\01-[SomeClass accessIvar]" ; BB#0: Lloh0 : adrp x8, _OBJC_IVAR_$_SomeClass.x@PAGE Lloh1 : ldrsw x8, [x8, _OBJC_IVAR_$_SomeClass.x@PAGEOFF] ldr w8, [x0, x8] Lloh2 : adrp x9, _OBJC_IVAR_$_SomeClass.y@PAGE Lloh3 : ldrsw x9, [x9, _OBJC_IVAR_$_SomeClass.y@PAGEOFF] ldr w9, [x0, x9] add w0, w9, w8 ret

Instead of one load per struct member access (previously combined into ldp ), we are now looking at a load of a PC-relative pointer offset ( adrp (PC-relative address of 4KB page) and ldrsw (load register signed word)), and a second load ( ldr ) using that offset into self (which is in x0 ).

In pseudo-C, this is something like

ptrdiff_t xOffset = SomeClass_x_ivar->offset; int x = *( int *)(( char *)self + xOffset); ptrdiff_t yOffset = SomeClass_y_ivar->offset int y = *( int *)(( char *)self + yOffset); return x + y;

For C++ experts, the code generated for ivar access is virtually identical to the code generated for the following contrived example of access via pointer-to-member:

struct SomeStruct { double dbl ; int x ; int y ; }; int SomeStruct ::* xPtr = & SomeStruct ::x; int SomeStruct ::* yPtr = & SomeStruct ::y; int accessViaMemPtr ( SomeStruct * o ) { return o->*xPtr + o->*yPtr; }

Assembly:

__Z15accessViaMemPtrP10SomeStruct : ; @_Z15accessViaMemPtrP10SomeStruct ; BB#0: Lloh0 : adrp x8, _xPtr@PAGE Lloh1 : ldr x8, [x8, _xPtr@PAGEOFF] ldr w8, [x0, x8] Lloh2 : adrp x9, _yPtr@PAGE Lloh3 : ldr x9, [x9, _yPtr@PAGEOFF] ldr w9, [x0, x9] add w0, w9, w8 ret .loh AdrpLdr Lloh0, Lloh1 .loh AdrpLdr Lloh2, Lloh3 .section __DATA,__data .globl _xPtr ; @xPtr .p2align 3 _xPtr : .quad 8 ; 0x8 .globl _yPtr ; @yPtr .p2align 3 _yPtr : .quad 12 ; 0xc

This article explains that the reason for the extra indirection in ivar access is to solve the fragile base class problem: unlike in C++, if a superclass has more ivars added, subclasses don't have to be recompiled to adjust their ivar offsets. Instead, the Objective-C runtime can just make this adjustment at load time.