Tuesday, 21 February 2012

Isn't Java a Civilised Language?

A few weeks ago I was given the opportunity to learn iOS programming. My boss decided that I was more of a ‘computer scientist’ rather than a developer, and this meant that I could apply my knowledge to developing an iPad application or two - all I’d need to do was to learn Objective-C and the iOS SDK: after all, how hard can it be?

Although I’ve done lots of C/C++ in the past, I’ve been doing Java since 2001, so I’d virtually forgotten much of what I knew, and besides, Objective-C isn’t much like C++ anyway. The result is that you feel like you’ve washed up on a foreign shore unable to speak the language and you’re armed with nothing more than a sun tan and a dictionary.

In learning all this new stuff, I came to realise that Java is a VERY good language and the reason is, firstly and foremost, because you don’t have to think about memory allocation and deallocation, you can just get on with writing your application. In writing iOS applications in Objective-C, you have to both allocate memory for your objects and then clear up after yourself, returning your object's memory back to the free-pool. This seems very old school for the second decade of the 21st century. Furthermore, having allocated memory for an object, you have to keep track of the number of references there are to it - a practice known as reference counting, something I first came across in my previous life as a C++ programmer. The fundamental idea here is that every time you create a new reference to an object, you increment the reference counter and when you’ve finished with the object reference you decrement its reference counter. When the counter reaches zero, the the system releases the memory back to the free pool. In Objective-C, the function to decrement the reference count is called release; hence if Java had reference counting and if I wanted to, for example, ask an AddressService to find me an address, I’d need to do something like this:

    // create an object and set the reference count to 1
   
AddressService addressService = new AddressService()
   
// Use the object
   
Address address = addressService.findAddress(id);
   
// pass it to a method
   
model.addAttribute("address", address);
   
// free the memory
   
addressService.release();

That’s the simple scenario, but what happens if some method or other passes you an object reference as a method argument? In that case you have to decide how you’re going to hang on to it and there seem to be two ways of doing this. The first is to make a copy of the object and the second is to increment its reference count. Making a copy is probably the safest idea, but consumes more memory and will be slower, whilst incrementing the reference count is quick but not as safe as some other part of the program may get the reference counting wrong and decrement the count one too many times, which means that you’ve got a reference to some memory that may have been released, and when you try to access your released object your program will crash.... Again, if Java had reference counting then a Spring Controller request handler method would look something like this:

  @RequestMapping(value = "/find", method = RequestMethod.GET)
 
public String findAddress(@RequestParam("id") int id, Model model) {
   
   
// increment the reference count - 'retain' is the Objective-C method for doing this.
   
model.retain();   

   
// create a new string object - with reference count of one
   
String msg = new String().withFormat("Processing an address page request for address with id: " + id);
   
// pass the string to the logger
   
logger.info(msg);
   
// release the string's memory
   
msg.release();

    AddressService addressService =
new AddressService();
    Address address = addressService.findAddress
(id);
    model.addAttribute
("address", address);
    addressService.release
();

   
// finished with the model
   
model.release();
   
return "address-display";
 
}

There are a couple of points to note here - firstly, the handler method is passed a model object, so I increment the reference count by calling retain and then call release on the model when I’m finished with it at the end of the method. Secondly, even when creating trivial objects, like a message string that’s passed to a logger, you have to apply the reference counting rules and free the memory once you’ve finished with it.

The example below demonstrates the alternative to incrementing the reference count: making your own copy of an object...

  @RequestMapping(value = "/find", method = RequestMethod.GET)
 
public String findAddress(@RequestParam("id") int id, Model model) {
   
   
// increment the reference count - 'retain' is the Objective-C method for doing this.
   
Model myModel = model.copy();   

   
// As previous example
   
    // finished with the model
   
myModel.release();
   
return "address-display";
 
}

These examples are only trivial, there’s a whole bunch of reference counting rules that you need to apply and if you get it wrong then Ka-Bam your program crashes and getting it wrong means either you try to access memory that’s already been freed, or slowly die due to a memory leak because you've forgotten to release some memory. Although I guess that the key point here is ownership: if you own an object, increment it's reference count, when you're finished with an object then decrement its reference count.

The code above is just a bit of scribble written to illustrate the point - it won’t compile. The code was actually taken from my Address sample on GitHub and in real life looks like this:

  @RequestMapping(value = "/find", method = RequestMethod.GET)
 
public String findAddress(@RequestParam("id") int id, Model model) {

   
logger.info("Processing an address page request for address with id: " + id);

    Address address = addressService.findAddress
(id);
    model.addAttribute
("address", address);

   
return "address-display";
 
}

...much smaller neater and more understandable. One last point, some of you will have noticed that in the reference counting example, I allocate (Objective-C word) a new AddressService - this is because iOS programming has nothing like dependency injection or a Spring framework, so you’re back to creating objects for yourself. (Note to the Guys at Spring: ‘Spring iOS’ - sounds like an idea to me...)

Those of you in the know will highlight the fact that Apple recently introduced something called ‘Automatic Reference Counting’. Whilst it isn’t garbage collection, it’s simplifies things in that iOS will now automatically keep track of your pointer references and free memory for you when the count reaches zero, which means that you don’t have to bother calling the retain and release methods.

Also, this blog isn’t trying to denigrate Objective-C - I quite like Objective-C. Its syntax seems rather arcane and feels verbose when compared to Java, but it forces you to be more disciplined in your programming technique, so if like me you believe that good programming is down to ‘clarity of thought’, then when writing an Objective-C program you have to think a little bit more clearly, and that I really like.

I also guess that a lot of readers might come up with a whole bunch of reasons as to why they think Java is a terrible language, and why their preferred language 'does it better', whatever it might be, so I'm looking forward to your comments...

Finally, I’m just a ‘newbie’ when it comes to iOS programming, so if any iOS/Objective-C masters come across this blog - please let me know where I’m wrong.

6 comments:

Blacktiger said...

Automatic Reference Counting (ARC) is a new feature that Apple added to Objective-C which is supposed to generate the reference counting code for you. This is supposed to allow you to write simpler code without the slowdowns of a garbage collector.

Anonymous said...

This...

String msg = new String().withFormat("Processing an address page request for address with id: " + id);
logger.info(msg);
msg.release();

should be...

NSLog(@"blah %@",id);

Which logs to console and syslog. Also note that NSString can return autoreleased strings, and you don't retain parameters on sync code.

Here are the manual memory management rules of objc:

1. If you new, alloc init, retain, or copy (NARC) an object, you have to release it.
2. If a method doesn't start with any of these words it returns an autoreleased object.

You have to practice a bit, but it's just that.

Tiran Kenja said...

Apple actually added garbage collection to Objective-C before it added automatic reference counting. It never made it to iOS tho. And it has been abandoned again now.

Automatic reference counting is not really doing anything automatic at runtime tho. It simply adds the retain and release calls in the per-processor. So it can still be messed up by bad code.

Anonymous said...

What about circular references? A pointing to B and B pointing to A. Or a more complex path, A -> B -> C -> D -> B...

Does automatic compiler based retain/release calls just aggravate it?

I remember from way back when that COM and DCOM fell flat on its face because of circular references. It made any complicated application a complete nightmare, completely unacceptable to work with. You always get leaks eventually.

~Mike

Roger Hughes said...

I can see you point on circular references, which I guess is something that Apple have thought about. Without going into detail, when using automatic reference counting, you can tell the compiler that a reference to an object maybe 'unsafe' and not reference count it; hence if A->B->A, object B knows that it's reference is unsafe, and it doesn't reference count it, it just has a copy of a pointer back to A.

In general terms - if you know a reference is unsafe, then your setter may look like this:

-(void) mySetter:(A *)myA {
_myA = myA;
}

If you know that the pointer is safe, then you do something like this:


-(void) mySetter:(A *)myA {
[_myA release];
_myA = myA;
[myA retain];
}

...where _myA is you instance variable and 'retain' gives you ownership.

Nina said...

I made exactely the same experiences. I am a java developer since 2005 and wrote an iPad-App at the end of the last year. The memory management was quite confusing for me but in this time ARC was even more confusing because on some parts I had do call retain/release-methods anyway (when using Core Graphics). And this inconsequence made me mad.
It seems to me that ARC is not completed jet.