Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Why not to use ARC

If you have done any development for iOS in the past few years you have at least some familiarity with ARC. The overall response to ARC since Apple released it with iOS 5 has been little short of orgasmic. You can’t swing a dead internet cat without hitting a blog post from someone explaining how ARC saved his/her dying grandmother and if you’re not using it on every project you touch then you’re helping the Commies win.

I’ve seen some projects do perfectly well with ARC, but at the same time I feel it provides its own set of challenges which we should not overlook. Here are some reasons why you might want to consider not using ARC on your next Objective C project.

ARC probably isn’t solving the problem you need it to

Memory management. Programmers whisper these words in fearful tones, or brazenly avow the impossibility of doing it correctly. ARC takes care of memory management, thus solving one of the great problems of our generation, right?

As it turns out, memory management is actually quite simple; /relationship/ management presents the challenge. Memory problems are usually a result of poor relationship management. Object A leaks because objects B and C both have an ownership relationship to A, A has an ownership relationship to C, etc.

In simple cases ARC will clean up the unused memory for you, but you’re still left with a poorly designed object graph, and all its associated problems. In more complex cases you can have strong circular references — a common result of a messy object graph — and then even ARC can’t prevent the leaks.

ARC makes easy things easier, but difficult things more difficult

Here’s a simple example of a class that has a simple relationship to another class:

Class declaration without ARC:

@interface Person : NSObject
@property (nonatomic, retain) Wallet *wallet;
@end

Class declaration with ARC:

@interface Person : NSObject
@property (strong, nonatomic) Wallet *wallet;
@end

Not much difference here. Here’s a bit of the implementation:

Initializer without ARC:

- (id)init {
    if (self = [super init];) {
        self.wallet = [[[Wallet alloc] init] autorelease];
    }
    return self;
}

- (void)dealloc {
    self.wallet = nil;
    [super dealloc];
}

Initializer with ARC:

- (id)init {
    if (self = [super init];) {
        self.wallet = [[Wallet alloc] init];
    }
    return self;
}

In this simple case ARC makes life a little easier; you don’t have to type autorelease, and you don’t have to write a dealloc method to release owned objects. With a well designed object graph, these two things are the vast majority of memory management you need to do. They’re more tedious than difficult, but ARC makes them go away. That’s pretty handy, right?

Now let’s look at an example of retrieving a value from the iOS Keychain, which is a bit more complicated:

Without ARC:

NSMutableDictionary *query = [NSMutableDictionary dictionary];
[query setObject:kSecClassGenericPassword forKey:kSecClass];
[query setObject:[NSData dataWithBytes:SOME_ID length:ID_LENGTH] forKey:kSecAttrGeneric];
[query setObject:(id)kCFBooleanTrue forKey:kSecReturnData];

NSData *result = nil;
NSString *value = nil
if (errSecSuccess == SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result)) {
    value = [[[NSString alloc] initWithData:[result autorelease] encoding:NSUTF8StringEncoding] autorelease];
}

With ARC:

NSMutableDictionary *query = [NSMutableDictionary dictionary];
[query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[query setObject:[NSData dataWithBytes:SOME_ID length:ID_LENGTH] forKey:(__bridge id)kSecAttrGeneric];
[query setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];

CFDataRef result = NULL;
NSString *value = nil;
if (errSecSuccess == SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result)) {
    value = [[NSString alloc] initWithData:(__bridge_transfer NSData *)result encoding:NSUTF8StringEncoding];
}

ARC saves us from two autorelease calls, but at the cost of five __bridge casts and a __bridge_transfer cast. ARC won’t let you declare the result variable as an NSData, since ARC disallows casting indirect pointers entirely. Thus, when you receive the result in a CFDataRef you’re still responsible for releasing that memory. You could use CFRelease() on it, or, as shown here, __bridge_transfer the CFDataRef into an NSData * temporary that ARC cleans up at the end of the execution of the statement.

Not exactly simpler, is it?

Sometimes ARC doesn’t help at all

Do you see the memory leak in this code?

self.thing = [Thing thing];
self.thing.completeNotification = ^{
    [self spreadTheWord];
};
[self.thing startTask];

By default, Objective C blocks retain everything they refer to from the local scope, including the self pointer. In this case, self retains the Thing, the Thing retains the block, and the block retains self. This creates a circular reference and a memory leak, with or without ARC.

Here’s how you fix it without ARC:

__block ThisClass *this = self;
self.thing.completeNotification = ^{
    [this spreadTheWord];
};

and with ARC:

__weak ThisClass *this = self;
self.thing.completeNotification = ^{
    [this spreadTheWord];
};

Even with ARC you still have to think about managing memory in some cases. If you rely on ARC to handle all of your memory this type of leak is probably more likely to happen.

The ARC compiler is broken

So ARC may not be helping as much as you thought, but at least it’s not really hurting anything, right? Unfortunately, turning on ARC injects bugs into the compiler. Until LLVM 4.1 (in XCode 4.5) code containing C++ templates wouldn’t even compile with ARC enabled. While LLVM 4.1 is a significant improvement over its predecessors, now the code compiles but fails to work correctly. Here’s an example of a simple function template with a specialization:

class Thing {
public:
    template<typename T>
    void do_something(const T &);
};

template<typename T>
void Thing::do_something(const T &) {
    NSLog(@"Do something with generic type");
}

template<>
void Thing::do_something(UIView * const &) {
    NSLog(@"Do something with UIView *");
}
</typename></typename>

When invoked:

Thing thing;
thing.do_something(someViewController.view);

here is the output without ARC:

2012-10-07 20:17:57.387 Project[65248:c07] Do something with UIView *

and with ARC:

2012-10-07 20:18:54.400 Project[65984:c07] Do something with generic type

What does C++ template type resolution have to do with Objective C reference counting? Nothing, as far as I can tell. It seems the ARC compiler is working off a different branch than the non-ARC compiler; a branch with significant unrelated defects. Who knows what other problems lie in wait?

Sometimes ARC is really, really broken

If you try digging around in the more esoteric capabilities of Objective C, ARC will sometimes get confused and do the wrong thing without warning you. For instance, if you’d like to change the class of an object at runtime, for example to a proxy class, with object_setClass, ARC won’t make a peep. However, it can get confused and release the object when its class changes, leading to overrelease and EXC_BAD_ACCESS. Worst of all, since ARC is completely out of your control, there’s nothing you can do about it!

Weirdly, for something that is meant to operate 100% at compile time, this behavior changes depending on what platform you run on. It happens in some instances on the simulator for older iOS versions, in different instances on the iOS 6 simulator, and (mercifully) never on a device.

ARC doesn’t really improve performance

This isn’t really a problem with ARC, but I’ve seen a number of blog posts about how great ARC is because it improves performance. These blog posts are usually filled with breathless tales of tail call optimization and assembly language listings. Now, there’s nothing wrong with Objective C from a performance perspective, but if you want to get down to questions of how many processor cycles your code takes then Objective C is pretty slow. Removing a few processor cycles from your iOS app is not going to have a noticeable effect.

Comments
  1. Some of the bugs you describe are certainly of concern. But what you are calling a “messy” object graph to me seems like good design. This suggests you might view it as “messy” because with manual memory management trying to make sure things are retained and released at the right times gets messy, rather than it being “messy” because the object graph is a poor representation of the problem being modeled. If so, that’s a bit of a circular argument (“It’s a messy design because the memory management for it gets messy.”).

  2. Adam Milligan says:

    Can you give an example of an undirected or cyclic object graph that you’d consider good design?

    A well structured object graph makes a number of things easier; it just so happens that memory management falls into this category.

  3. refCounts will never make a good memory management system.

    Let’s pray that Apple see the light one day and provides us a good incremental garbage collector.

  4. Adam Milligan says:

    Pascal, I don’t think you quite got the message of the article. Early on I made the point that ARC solves the problem of memory management, but not the much more interesting and difficult problem of relationship management. Garbage collectors have the same deficiency.

    Simply put, memory management — even with basic reference counting — is not difficult with a well structured relationship model.

    Considering the relatively low power and low memory of iOS devices, and having seen the frustration the GC has caused on Android projects, I fully agree with Apple’s decision to disallow use of the GC for iOS.

  5. John Q Passerby says:

    Agree 100%.

    ARC also requires you to static reference anything you want to keep; so having a delegate object that releases itself after it gets a callback will get released early, because the delegate relation is weak. In some cases I find that to be a better solution — but with ARC I would have to add an Ivar in somewhere, which adds unneeded complexity to some class.

  6. Great post.

    With manual memory management, I feel the main benefit is having tighter control over your program’s operation. It’s especially critical in performance-driven apps where you may need to allocate and release memory often and at specific times. I also believe there’s a certain nostalgic factor involved.

    One nice benefit of ARC is that observers use weak references. With manual memory management, we’d need to remove the observers beforehand in a separate teardown method in order for the object to dealloc, quite cumbersome. With ARC, we can do all that right in dealloc.

    And for you new people, even with ARC available, it’s still a good idea to learn the basics of manual memory management first. It’s good to have an idea of what’s going on under the hood.

  7. I completely agree with Panupan, also great article.

  8. Matt H says:

    This seems like a poor tradeoff. You’re arguing that since ARC isn’t perfect for the rare, more-difficult cases that it’s not worth the upside in memory management for the 90% case.

  9. chartus says:

    great article – i want to add the comment that ARC makes debugging far more complicated or at least more ‘poking in the dark’

    if you make mistakes in MRC your app will crash and the debugger will get your nose on the point whats wrong – but ARC dosn’t let your App die – all this wrong references will be hold forever and all what you gets are strange error reports from users about strange behavior…

  10. Me ARC user says:

    What is the point? No technology is perfect so it is a matter of preference right? Some people have a hard time debugging non-arc while I find it easy to debug with arc.

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *