Matt Galloway

My home on the 'net.

A look under ARC's hood - Episode 1

After a conversation on Twitter with @jacobrelkin I decided to write a little post about how ARC works under the hood and how you can go about seeing what it’s doing. In this post I’ll explain about how ARC adds in retain, release and autorelease calls accordingly.

We shall start by defining a class like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#import <Foundation/Foundation.h>

@interface ClassA : NSObject
@property (nonatomic, retain) NSNumber *foo;
@end

@implementation ClassA

@synthesize foo;

- (void)changeFooDirect:(NSNumber*)inFoo {
    foo = inFoo;
}

- (void)changeFooSetter:(NSNumber*)inFoo {
    self.foo = inFoo;
}

- (NSNumber*)newNumber {
    return [[NSNumber alloc] initWithInt:10];
}

- (NSNumber*)getNumber {
    return [[NSNumber alloc] initWithInt:10];
}

@end

This outlines a few important aspects of ARC including direct access to ivars versus using a setter and how ARC will add autorelease calls when returning an object from a method based on the name of the method.

Let’s first look at the direct access to ivars versus using a setter. If we compile that code and look at the assembly then we’ll get an insight into what’s going on. I decided to use ARMv7 because it’s easier to understand what’s going on than x86 (in my opinion anyway!). We can turn ARC on and off with the -fobjc-arc and -fno-objc-arc compiler options. In these examples I’ve used optimisation level 3 which will mean the compiler will also remove redundant code which we’re not really interested in and would clog up the understanding (an exercise for the reader is to try without any optimisations and see what it looks like).

So to compile without ARC I used the following command:

1
$ /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/clang -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -arch armv7 -fno-objc-arc -O3 -S -o - test-arc.m

So, let’s look at changeFooDirect: and changeFooSetter::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    .align  2
    .code   16
    .thumb_func     "-[ClassA changeFooDirect:]"
"-[ClassA changeFooDirect:]":
    movw    r1, :lower16:(_OBJC_IVAR_$_ClassA.foo-(LPC0_0+4))
    movt    r1, :upper16:(_OBJC_IVAR_$_ClassA.foo-(LPC0_0+4))
LPC0_0:
    add     r1, pc
    ldr     r1, [r1]
    str     r2, [r0, r1]
    bx      lr

    .align  2
    .code   16
    .thumb_func     "-[ClassA changeFooSetter:]"
"-[ClassA changeFooSetter:]":
    push    {r7, lr}
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
    mov     r7, sp
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
LPC1_0:
    add     r1, pc
    ldr     r1, [r1]
    blx     _objc_msgSend
    pop     {r7, pc}

And following straight on, let’s look at what it looks like with ARC enabled. To do this I used the following command:

1
$ /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/clang -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -arch armv7 -fobjc-arc -O3 -S -o - test-arc.m

Again, we’re interested at the moment in changeFooDirect: and changeFooSetter::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    .align  2
    .code   16
    .thumb_func     "-[ClassA changeFooDirect:]"
"-[ClassA changeFooDirect:]":
    push    {r7, lr}
    movw    r1, :lower16:(_OBJC_IVAR_$_ClassA.foo-(LPC0_0+4))
    mov     r7, sp
    movt    r1, :upper16:(_OBJC_IVAR_$_ClassA.foo-(LPC0_0+4))
LPC0_0:
    add     r1, pc
    ldr     r1, [r1]
    add     r0, r1
    mov     r1, r2
    blx     _objc_storeStrong
    pop     {r7, pc}

    .align  2
    .code   16
    .thumb_func     "-[ClassA changeFooSetter:]"
"-[ClassA changeFooSetter:]":
    push    {r7, lr}
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
    mov     r7, sp
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
LPC1_0:
    add     r1, pc
    ldr     r1, [r1]
    blx     _objc_msgSend
    pop     {r7, pc}

We can instantly see the difference here. The changeFooSetter: is exactly the same whereas changeFooDirect: has changed with a single call to objc_storeStrong. That’s the interesting bit. If we looks at the LLVM documentation for this then we see that it’s doing a standard swap of the variable by releasing the old value and retain the new value. Whereas in the non-ARC version the ivar is just swapped without any retain or release. That’s just what we’d expect! Thanks ARC!

Now for the more interesting bit, the newNumber versus getNumber. Those methods in non-ARC land are both returning NSNumber objects which have a retain count of 1, i.e. the caller owns them. That sounds right for newNumber but not for getNumber according to Cocoa’s naming conventions. We’d expect to see an autorelease call when returning from getNumber. So let’s see what the code looks like without ARC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    .align  2
    .code   16
    .thumb_func     "-[ClassA newNumber]"
"-[ClassA newNumber]":
    push    {r7, lr}
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC2_0+4))
    mov     r7, sp
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC2_0+4))
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC2_1+4))
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC2_1+4))
LPC2_0:
    add     r1, pc
LPC2_1:
    add     r0, pc
    ldr     r1, [r1]
    ldr     r0, [r0]
    blx     _objc_msgSend
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC2_2+4))
    movs    r2, #10
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC2_2+4))
LPC2_2:
    add     r1, pc
    ldr     r1, [r1]
    blx     _objc_msgSend
    pop     {r7, pc}

    .align  2
    .code   16
    .thumb_func     "-[ClassA getNumber]"
"-[ClassA getNumber]":
    push    {r7, lr}
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC3_0+4))
    mov     r7, sp
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC3_0+4))
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC3_1+4))
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC3_1+4))
LPC3_0:
    add     r1, pc
LPC3_1:
    add     r0, pc
    ldr     r1, [r1]
    ldr     r0, [r0]
    blx     _objc_msgSend
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC3_2+4))
    movs    r2, #10
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC3_2+4))
LPC3_2:
    add     r1, pc
    ldr     r1, [r1]
    blx     _objc_msgSend
    pop     {r7, pc}

And now with ARC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
    .align  2
    .code   16
    .thumb_func     "-[ClassA newNumber]"
"-[ClassA newNumber]":
    push    {r7, lr}
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC2_0+4))
    mov     r7, sp
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC2_0+4))
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC2_1+4))
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC2_1+4))
LPC2_0:
    add     r1, pc
LPC2_1:
    add     r0, pc
    ldr     r1, [r1]
    ldr     r0, [r0]
    blx     _objc_msgSend
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC2_2+4))
    movs    r2, #10
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC2_2+4))
LPC2_2:
    add     r1, pc
    ldr     r1, [r1]
    blx     _objc_msgSend
    pop     {r7, pc}

    .align  2
    .code   16
    .thumb_func     "-[ClassA getNumber]"
"-[ClassA getNumber]":
    push    {r7, lr}
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC3_0+4))
    mov     r7, sp
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC3_0+4))
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC3_1+4))
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC3_1+4))
LPC3_0:
    add     r1, pc
LPC3_1:
    add     r0, pc
    ldr     r1, [r1]
    ldr     r0, [r0]
    blx     _objc_msgSend
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC3_2+4))
    movs    r2, #10
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC3_2+4))
LPC3_2:
    add     r1, pc
    ldr     r1, [r1]
    blx     _objc_msgSend
    blx     _objc_autorelease
    pop     {r7, pc}

And look at the single difference – a blx (this is a method call) to objc_autorelease in getNumber:. That’s just what we’d expect from ARC because it’s noticed that the method name doesn’t start with new or copy and it knows that at the point of return the NSNumber has a retain count of 1 so it adds in an autorelease call. Excellent!

This has just shown a little insight into how ARC works in two circumstances and I hope it inspires the reader to go away and look for themselves into how ARC works rather than just taking it for granted. It’s important as a programmer to understand how your tools work.

Comments