« Welcome to the new decade | Main | We Don't Need No Stinking Garbage Collection »
Friday
Aug132010

OCPtr - a Smart Pointer for Objective C

In my last post I covered why we might want Garbage Collection on the iPhone, some reasons why we don't have it, and how the same problem is solved in C++. I then hinted that we might be able to bring the same C++ goodness to Objective-C - if we allow ourselves to use Objective-C++.

In this post I'm going to introduce my own solution.

Say hello to OCPtr

Rather than dive into the implementation, let's look at usage - and how it addresses our memory management needs. Let's start with our first example - allocating an NSString. Here's how it looks using OCPtr:

OCPtr<NSString> str = [[NSString alloc] initWithFormat: @"One: %d", 1];

// ...

The first thing to notice here is that instead of declaring our type as NSString* we declare it as OCPtr<NSString>. This is how smart pointers work. C++'s power is with types - especially when mixed with templates. OCPtr<NSString> is a complete type. Furthermore it is a value type. At the end of the scope it will be destroyed. In its destructor we call release on its NSString* member - which brings us to the second thing to notice - we have omitted the [str release] step!

Let's think about that for a moment. On the face of it we have saved ourselves 14 characters - but at the cost of six characters in the declaration - a net saving of eight characters. Woohoo! Obviously if this was all there was to it I wouldn't be writing this blog post. I'm not that obsessed with removing redundancy!

What is more significant is the reduction in the mental overhead of tracking when you need to call release - and the debugging overhead when it goes wrong - not to mention the potential for financial overhead if it only goes wrong when your users are using it.

In this simple example it doesn't seem to have given us much - but think about this again next time you're trying to track down a particularly elusive leak or over-release.

Of course real code is more complex than this, and OCPtr will need to be more than this to function as a stand-in for raw pointers. Years of C++ smart pointer experience tells us we need to overload the assignment operator, provide copy constructors (allowing us to create one OCPtr from another), and ideally overload a few other operators too. Technically a true smart pointer would overload the -> and & operators to behave like raw pointers - but these are not used with Objective-C types, so I haven't provided them. Other than that OCPtr provides all of this, so simple assignments result in retain counts being updated appropriately, and objects are released as their OCPtr wrappers leave the scope.

Some smart pointers also overload the conversion operator (this is invoked when you try to cast an object - whether explicitly or implicitly) to convert to the underlying raw point. This is a controversial practice, and can lead to ambiguities. However in the constrained environment of Objective-C code it seems safe enough - and gives us one key advantage: it allows us to send messages to the underlying object without additional syntax. Here's some more code illustrating this as well as some of the other preceding points:

{
  OCPtr<NSString> str = [[NSString alloc] initWithFormat: @"One: %d", 1];

  OCPtr<NSString> str2;
  {
    OCPtr<NSString> str3 = str; // retain count == 2
    str2 = str3; // retain count == 3

  } // retain count == 2

  // ...

} // retain count == 0 - dealloc called

Not just Smart - it smells nice too

So what else can we do with OCPtr?

Well it wouldn't be so useful if it couldn't also manage instance variables. OCPtr does that too - as long as you have the project setting enabled for: "Call C++ Default Ctors/Dtors in Objective-C". This setting seems to be enabled by default now, but it's worth checking. Now if all your ivars are held as OCPtrs you don't even need to write a dealloc method. The compiler will effectively write it for you - and all your OCPtr destructors will be called - releasing all their managed objects.

What about properties?

To some extent there is an overlap between the functionality of synthesised properties and what we are doing here. That is, if you declare a property for an Objective-C object with the 'retain' attribute then the synthesised code will include the retain-release code necessary to do the right thing. This does relieve the programmer of some work - so is OCPtr buying us anything?

Well, using properties still places two responsibilities on the caller: First they must remember to use the property syntax (whether the dot-syntax or the direct messaging passing) everywhere (including within the implementing class - except at the point of allocation). Secondly they must still remember to set the properties to nil in dealloc - which is really no improvement over just releasing them.

So retain properties do help with change of ownership - but at the cost of having two ways to do so - one which is automatic - the other still manual.

But can we declare properties for OCPtr ivars - and what happens with the retain counts?

Well recall that you can write a property for any Objective-C type - which includes all C types - primitives and structs. In the latter case you will use the assign attribute instead of retain (and assign is the default - so even easier). In this case no retain-release code will be generated - values will just be assigned directly. But OCPtr overloads assignment to provide retained change of ownership semantics. This gives us exactly what we want!

@property OCPtr<NSString> str;

So, in short, use assign properties to provide external access to OCPtr ivars. Internally you can use either form of access but they work the same way. In either case assignment works as it should and references will be cleaned up in dealloc. This is surely an improvement.

Transfer of ownership

How does OCPtr work with autoreleased objects? If you initialise an OCPtr with an autoreleased object you do still have to tell it to take ownership. This is no different to a raw Objective-C pointer. In both cases to take ownership you must call retain. E.g:

OCPtr<NSString> str = [[NSString stringWithFormat: @"One: %d", 1] retain];

It niggles me a bit that we still have to make a distinction between autoreleased and non-autoreleased objects - but that's a property of the autorelease mechanism itself, rather than a limitation of OCPtr (I could imagine a scheme where OCPtr detected that the pointer being passed was in the autorelease pool - but there would be no way to know for sure if that related to this assignment - and would probably be taking things to far anyway).

But take a step back for a moment. What is the problem that autorelease solves in the first place? autorelease's raison d'ĂȘtre is to provide transfer of ownership semantics. If a method creates an object but does not own it (typically a factory method) - it just returns it. In order to return it with a valid retain count, without requiring that the caller release it, it adds it to the autorelease pool to be released some time later. This works, but adds extra rules and syntax, and can result in objects living longer than they need to.

But smart pointers already solve this problem. By returning an OCPtr, the retain count will remain valid until after the assignment operator of the caller's OCPtr has retained it. The returned OCPtr then immediately goes out of scope (it is a temporary object). The net result is that you can freely create and return OCPtrs just as you can primitive types - no need to worry about retain counts or autorelease pools.

-(OCPtr<NSString>) someMethod
{
    OCPtr str = [[NSString alloc] initWithFormat: @"created here"];
    return str;

} // str will be released here but caller already has it

// ...

-(void) someOtherMethod
{
    OCPtr<NSString> str = [self someMethod];

} // str will be released here, retain will go to 0 and dealloc called

Of course if you're working with third-party APIs (including the SDK frameworks) you will still need to work with autoreleased objects at times, so it's worth remembering that you still need to retain them before putting them in an OCPtr.

But wait - there's more

What we have discussed so far covers our memory management needs. But if we've accepted a general purpose object wrapper into our code we have opportunity for further, aspect-oriented, benefits:

id type checking

One of the great things about Objective-C is that it is a dynamic language (albeit with static underpinnings).

One of the biggest problems with Objective-C is that it is a dynamic language (fortunately with static underpinnings).

While it is nice that we can choose to use typed, untyped (id) or partially typed (NSObject) objects, sometimes we are forced to go untyped when we'd like the benefit of type checking. The consequences of getting the types wrong are usually crashes, with not so helpful error messages - and at a different point in the code. We can check types at runtime, of course, with the isKindOfClass: method, to which you have to pass a class object - obtained by passing the class message. This can clutter the code up with mechanics.

OCPtr provides a conversion constructor from both id and NSObject, which will test the types (using the class and isKindOfClass: methods) before casting internally. As a result if we do this:

OCPtr<NSArray> str = [[NSString alloc] initWithFormat: @"hello"];

... we will get an exception that tells us exactly what happened.

If we had used a raw NSArray* pointer here the assignment would have worked - but we'd get errors further down the line if we tried to call array methods on it. These can be difficult to track down.

Don't want to pay for the check? Just cast to the target type before you assign (but lose the benefit of the type checking - so the principle is "correct by default, fast where necessary").

Release early

By eliminating your memory management bugs you will be able to release your apps earlier - but actually I was referring to releasing objects early.

Sometimes you're done with an object in the middle of a scope and you want to release it there and then. If you do this you are strongly advised to then set it to nil - to avoid the chance of anyone trying to use no longer valid memory. With an OCPtr you need only set it to nil and you get both advantages. You've been able to do this for a while with properties, but now you can do it directly with ivars, and even with local variables:

{
	OCPtr<NSString> str = [[NSString alloc] initWithFormat: @"One: %d", 1];

	// ...

	str = nil; // release is called here, and the underlying pointer set to nil

	// ...
}

Logging hooks

A powerful, but easily abused, feature of C++ templates is a concept known as specialisation (unfortunately a rather overloaded term in OO languages). A template is specialised when you write specific code for the case where a template's type argument(s) are of specific types. If that doesn't make things any clearer I'll explain how this relates to OCPtr and logging and hopefully it will click.

OCPtr comes with another template class: OCPtrHooks. OCPtrHooks declares a set of empty methods and nothing else. Each method represents a state change in OCPtr (e.g. onAssign) and OCPtr uses an OCPtrHooks class, parameterised with the same Objective-C type, calling the appropriate hook method as things happen. Because all the methods are empty the compiler is able to optimise these calls away completely.

So if the methods do nothing and they are not even compiled in what use is this? Well, due to the magic of template specialisation we can write a version of OCPtrHooks specialised for a particular type - or even partially specialised for a base type. Then, for those specialised types only, your custom versions will be called.

You can implement your specialisations to do anything - but a useful implementation for us is to log the events. Enabling logging for a particular type is as easy as declaring a specialisation that derives from a logging base class, like this:

template<> class OCPtrHooks<NSString> : public OCPtrLogHooks{};

Don't worry about trying to follow that if you're not a C++ programmer. The important bits are the template type parameter (NSString, here) and the base class (OCPtrLogHooks). Just substitute the NSString for any type you want to log and it will start working - with no overhead (not even an if) in all other cases.

While this is powerful, and useful, it does make use of, and expose, some tricky template syntax - If you're not already familiar enough with C++ to know how this works you may choose not to take advantage of this facility (I might try and make it friendlier in the future - even if that involves the use of a wrapper macro).

The dark side

So we've eliminated the mental overhead of manual retain counts, without introducing any runtime overhead, added transparent runtime type checking to dynamic types, along with several other benefits. With all this goodness everyone will want to use OCPtr, right? They'd be mad not to?

Well, that's generally true of C++ smart pointers in the C++ world. But because we're intruding the world of C++ into the world of Objective-C, and using a hybrid language to do so, there are some drawbacks to consider. These are the ones I think are relevant:

  1. OCPtr is a C++ template. This means it must be #included or #imported - so all your implementation files will need to be .mm files (or you can set the filetype in the file info dialog).
  2. C++ syntax intrudes into your application code in the form of the OCPtr<MyClass> syntax.
  3. The idea of custom value types may be unfamiliar to people reading the code. The fact that assignments handle retain counts for you may be a surprise, for example.
  4. If you use an OCPtr in an untyped context, e.g. as an argument to NSLog, the compiler cannot deduce that it needs to return the raw pointer out. So you'll need to explicitly access it - either calling a C++ method, such as get(), or casting to the underlying type.

Issues 1 & 2 are the most likely to put someone off - and they become especially significant if you are writing code for a client, or as part of a team in a larger company - especially if you are not already using any Objective-C++ on the project.

So I wouldn't necessarily recommend that everyone just start using OCPtr everywhere - but if you are just writing for yourself - or as a small, open-minded, team - I'd encourage you to at least give it a try and see if it can make your life easier.

But at the end of the day, even if you decide the trade-offs are not worth it for you, you can at least rest easy knowing that manual referencing counting is a choice. And you can tell all your gloating friends who use other languages, "I don't need no stinking garbage collection!"

Give me the codez

So where can you get OCPtr. I'll shortly be putting it up on GitHub. When I do so I'll update here with a link.

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments (13)

Hi! I just started off with Objective-C and came across your article. I am used to C++ and *love* smart pointers. Can't believe that nobody implemented something like OCPtr before. Greetz from Germany.

August 17, 2010 | Unregistered CommenterFrank Friemel

Hi,

Thanks for the article. Nice to have a cool alternative. I just wanted to add a few pros:

1. if you have a code where there are several exit points or exceptions thrown, the smart pointer takes care of freeing the memory

2. your code does not need to autorelease every single pointer


My concern is that if you create a class that is an Objective-C but placed in a .mm file because you want to use smart pointers, does this class become a Objective-C++ class? That would be a drag because you can no longer subclass it by a Object-C class (.m).

Cheers.

October 13, 2010 | Unregistered Commenterramin

1. Absolutely. I mentioned this aspect of smart pointers in the previous article (referenced at the beginning of this one): "it allows for early returns or exceptions". I didn't make a big thing of it because exception handling is not as big a concern in the OC world as in the C++ world.

2. There should be no need for your code to autorelease, except where interacting with other code that expects it.

As to your concern - yes all OC files will have to be .mm. However there is no such thing as an Objective-C++ class. You just get an Objective-C class that can use C++ code within it, or a C++ class that can use Objective-C code within it. What you are thinking of is that, even with Objective-C++, you cannot derive an Objective-C class from a C++ class or vice-versa. There is no danger of that here.

October 13, 2010 | Registered CommenterPhil Nash

Yes! i was beginning to roll my own and found this.

Humm, you (philsquared, correct?) haven't posted it on github :( I'd loooove to use OCPtr. If it's not done yet, why not throw it up there and I'll help you finish it.

Anyway, some thoughts on the dark side:

1. that's just life in the city.
2,3. Perhaps a typedef OCPtr<NSObject> sid; (or smartid) could make it more palatable to those objective c coders that are not into static typing
4. ptr->get() or ptr->raw() seems reasonable to me.

November 19, 2010 | Unregistered Commenterjbenet

@jbenet Thanks for your comments and interest.
I discovered a hole in my tests which meant I'd misinterpreted what happens when a value is assigned as part of a synthesized property set. It seems that the generated code does a memcpy on the object, bypassing the copy-ctor.
I parked the whole thing for a while - at least until somebody came asking for the source code ;-)
If you can think of any way around that issue I'd be very interested to hear!
It might be better to move the conversation into email. I have the philnash.me domain - mail me with ocptr@

November 19, 2010 | Unregistered CommenterPhil Nash

This is great, will check it out. Thanks.

Does it handle CoreFoundation? CGPathRef, CGContextRef, etc.?

How much is involved in adding special cases for those types?

November 22, 2010 | Unregistered CommenterAwesome

Really nice. Can you use this in an .h file to create smart pointer instance variables inside objective C classes?

March 13, 2011 | Unregistered CommenterTD

@TD Yes. The caveat is that the Objective-C class will have to be in a .mm file (to make it Objective-C++)

March 14, 2011 | Unregistered CommenterPhil Nash

... and, in fact, anything that uses the header for your class. It's a bit viral - so you really have to make the whole project Objective-C++.

March 14, 2011 | Unregistered CommenterPhil Nash

This is basically SharedPointer what about WeakPointer ? I think It might be quite usefull.

April 4, 2011 | Unregistered CommenterOlek

@Olek. Yes a weak ref version could be useful - but one step at a time :-)
Most of the time I find raw (uncounted) pointers to be "good enough" as weak pointers. For the few remain cases there is usually some point at which I know a whole graph objects is "done" and I might special case the code to release them all in one go.

Supporting weak pointers in the context of OCPtr would require an extra object to be allocated (that could live beyond the end of the main object and signal whether it has been deleted or not). It can be done but I'm not sure I want to pay the price.

April 12, 2011 | Registered CommenterPhil Nash

Great post! I am trying to use the OCPtr with an Objective-C class that has properties but I am unable to use "dot syntax" to access the properties if I have an OCPtr of the class. For example:

@interface Foo : NSObject
{

}

@property (nonatomic, readwrite) int Bar;

@end

@implementation Foo

@synthesize Bar;

@end

OCPtr<Foo> foo = [[Foo alloc] init];

int i = foo.Bar; //This fails to compile since its trying to access the Bar member of OCPtr<T>, I can get it to
//work if I use foo.get().Bar but that is kind of annoying to have to type each time
//especially if you chain property calls.

I have been trying to get this to work, any advice you can offer would be greatly appreciated.

May 19, 2011 | Unregistered CommenterJon R

Waoo,
very awesome article.

Phil can you please send me source code of OCPtr so that i can use it in my application.
My email id is kashif.pucitian@gmail.com

Waiting for your email.

Thanks,
Kashif

April 13, 2012 | Unregistered Commenterkashif

PostPost a New Comment

Enter your information below to add a new comment.
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>