Monday, April 04, 2011

Easier Memory Release In Objective C with Blocks

I've been getting my head around Objective C recently and remembering all that is great about about manual memory management. I found myself repeating code that either a) autoreleased or b) released with try finally loops. The autorelease puts a drag on your pool so you want to use it sparingly (with APIs where you don't know who will be calling it) and it's mentioned in several places in the Apple documentation to favor "release" over "autorelease". So, that leaves me with option b in most of my code with a lot of boilerplate code. And I hate boilerplate code. Well, did some more reading and found out that blocks are a new thing in Objective C and I quickly whipped this up:
typedef void (^Monadic)(id value);
typedef id (^Niladic)();

-(void)use: (Niladic)calc during:(Monadic)monadic {
    id toUse = calc();
    @try {
        monadic(toUse);
    }
    @finally {
        [toUse release];
    }
}
Basically, it allows you to send in one block to get the allocated object and then use it in a try finally. It automatically cleans up after itself! I then decided to try it out on some code:
-(IBAction)calculateClicked:(id) sender {
    [self 
     use: ^() { return [[Cents alloc] initWithString: [startView text]]; } 
     during: ^(id startAmount) {
         [self 
          use: ^() { return [[Cents alloc] initWithString: [eachYearAmountView text]]; }
          during: ^(id eachYearAmount) {
              [self
               use: ^() { return [[NSDecimalNumber alloc] initWithString: [interestView text]]; }
               during: ^(id years) {
                   Cents *answer = [startAmount
                          atRetirementAdd: eachYearAmount 
                          earning: years 
                          for: [[yearsView text] intValue]];
                   [resultView setText: [answer description]];
               } ];
          } ];
     } ];
}
I think I made it too generic in that I now have to type "^() { return [blah blah blah]; } every time. I realized I could get rid of those blocks and just pass in the object I want to clean up. Remove Niladic block and take two looks like this:
typedef void (^Monadic)(id value);
-(void)use:(id)toUse during:(Monadic)monadic {
    @try {
        monadic(toUse);
    }
    @finally {
        [toUse release];
    }
}
And this is what it looks like when you use it:
-(IBAction)calculateClicked:(id) sender {
    [self 
     use: [[Cents alloc] initWithString: [startView text]]
     during: ^(id startAmount) {
         [self 
          use: [[Cents alloc] initWithString: [eachYearAmountView text]]
          during: ^(id eachYearAmount) {
              [self
               use: [[NSDecimalNumber alloc] initWithString: [interestView text]]
               during: ^(id years) {
                   Cents *answer = [startAmount
                          atRetirementAdd: eachYearAmount 
                          earning: years 
                          for: [[yearsView text] intValue]];
                   [resultView setText: [answer description]];
               } ];
          } ];
     } ];
}
As long as the Monadic blocks stay on the stack we don't need to do the copy autorelease business on block objects. I like this code better than nested try finally code because I'm not repeating the name of my variable to release in the clean up code. I'm still not 100% happy with the code, but I'll keep playing around with other things until then. I do like the fact that it is simple. Just something I wanted to share as I learn Objective C.

No comments: