Implementing the ‘defer’ statement from Go in Objective-C?
Asked Answered
H

2

6

Today I read about the defer statement in the Go language:

A defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns. Defer is commonly used to simplify functions that perform various clean-up actions.

I thought it would be fun to implement something like this in Objective-C. Do you have some idea how to do it? I thought about dispatch finalizers, autoreleased objects and C++ destructors.

Autoreleased objects:

@interface Defer : NSObject {}
+ (id) withCode: (dispatch_block_t) block;
@end

@implementation Defer
- (void) dealloc {
    block();
    [super dealloc];
}
@end

#define defer(__x) [Defer withCode:^{__x}]

- (void) function
{
    defer(NSLog(@"Done"));
    …
}

Autoreleased objects seem like the only solution that would last at least to the end of the function, as the other solutions would trigger when the current scope ends. On the other hand they could stay in the memory much longer, which would be asking for trouble.

Dispatch finalizers were my first thought, because blocks live on the stack and therefore I could easily make something execute when the stack unrolls. But after a peek in the documentation it doesn’t look like I can attach a simple “destructor” function to a block, can I?

C++ destructors are about the same thing, I would create a stack-based object with a block to be executed when the destructor runs. This would have the ugly disadvantage of turning the plain .m files into Objective-C++?

I don’t really think about using this stuff in production, I’m just interested in various solutions. Can you come up with something working, without obvious disadvantages? Both scope-based and function-based solutions would be interesting.

Hexastyle answered 3/2, 2011 at 8:17 Comment(1)
There is nothing wrong with your solution. In fact I came up with something very similar on my blog. I also have unit tests for it.Nickelic
M
2

If you could use C++, check Boost's Scope Exit library.


If you don't mind having to type 2 extra words in the beginning and the end of the function, you could use the @finally block.

#define SCOPE               {id _defered_actions__=[[NSMutableArray alloc]init];@try{
#define END_SCOPE           }@finally{for(void(^action)()in[_defered_actions__ reverseObjectEnumerator])action();[_defered_actions__ release];}}
#define DEFER_COPY(_code__) {id _blk__=[^{_code__;}copy];[_defered_actions__ addObject:_blk__];[_blk__ release];}
#define DEFER(_code__)      ([_defered_actions__ addObject:(^{_code__;})])

Example use:

@interface XXObject : NSObject {
}
-(int)factorial:(int)x;
@end

@implementation XXObject
-(int)factorial:(int)x { SCOPE

    printf("begin foo:%d\n", x);
    DEFER( printf("end foo:%d\n", x) );

    if (x > 0)
        return x * [self factorial:x-1];
    else if (x == 0)
        return 1;
    else {
        @throw [NSException exceptionWithName:@"NegativeFactorialException"
                                       reason:@"Cannot call factorial on negative numbers"
                                     userInfo:nil];
        return 0;
    }

END_SCOPE }

-(void)dealloc {
    printf("%p has been released.\n", self);
    [super dealloc];
}
@end




void do_stuff() { SCOPE

    __block XXObject* x = [[XXObject alloc] init];
    DEFER({
        printf("releasing %p.\n", x);
        [x release];
    });


    int i;
    for (i = 2; i >= -1; -- i) {
        // use DEFER_COPY to retain the local variable 'i' and 'fact'
        int fact = [x factorial:i];
        DEFER_COPY( printf("%d! == %d\n", i, fact) );
    }

END_SCOPE }




int main () {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

    @try {
        do_stuff();
    } @catch(NSException* e) {
        // note that the @finally statements might not be called in 64-bit if we
        // left the exception uncaught.
        NSLog(@"%@", e);
    }
    [pool drain];
    return 0;
}

Which should print:

begin foo:2
begin foo:1
begin foo:0
end foo:0
end foo:1
end foo:2
begin foo:1
begin foo:0
end foo:0
end foo:1
begin foo:0
end foo:0
begin foo:-1
end foo:-1
0! == 1
1! == 1
2! == 2
releasing 0x100116500.
0x100116500 has been released.
2011-02-05 23:06:21.192 a.out[51141:903] Cannot call factorial on negative numbers
Mcafee answered 5/2, 2011 at 15:20 Comment(0)
C
1

Read Mike Ash's post on generator's in Objective-C.

Confluence answered 3/2, 2011 at 8:57 Comment(2)
That’s quite a piece of code to digest :) If I understand things correctly, though, this is precisely the autoreleased solution?Hexastyle
Sorta; it provides a foundation upon which you could implement something like your defer. You could tack it onto an autorelease pool, yes, but there is some risk in that autorelease pools may be nested, etc... You could also mix in C++ and use a destructor to trigger the block.Confluence

© 2022 - 2024 — McMap. All rights reserved.