PDA

View Full Version : Crash storing block in mutable array.




tronthomas
Feb 18, 2012, 08:18 PM
What can people tell me about why this program is crashing:


#import <Foundation/Foundation.h>

@interface Action : NSObject
+ (void)execute;
@end

@implementation Action
+ (void)execute
{
NSLog(@"The action was executed.");
}
@end

@interface ActionPerformer : NSObject {
@private
NSMutableDictionary* _namedActions;
}

+ (ActionPerformer*) performer;
- (void)addAction:(NSString*)action withClass:(Class)class andSelector:(SEL)selector;
- (void)performAction:(NSString*)action;
@end

@implementation ActionPerformer

typedef void(^ActionBlock)();

+ (ActionPerformer*) performer
{
ActionPerformer* performer = [[ActionPerformer alloc] init];
performer->_namedActions = [NSMutableDictionary dictionary];
return performer;
}

- (void)addAction:(NSString*)actionName withClass:(Class)class andSelector:(SEL)selector
{
ActionBlock block = ^(){ [class performSelector:selector]; };
// Verify things work
block();
[_namedActions setObject:block forKey:actionName];
}

- (void)performAction:(NSString*)actionName
{
ActionBlock block = [_namedActions objectForKey:actionName];
// Crash happens here!!!
block();
}

@end

int main()
{
@autoreleasepool {
ActionPerformer* performer = [ActionPerformer performer];

[performer addAction:@"action" withClass:[Action class]
andSelector:@selector(execute)];

[performer performAction:@"action"];
}
return 0;
}


I am building it on Mac OS X 10.6.8 using Xcode 4.2 with Apple LLVM compiler 3.0



chown33
Feb 18, 2012, 08:42 PM
Since you didn't post the crash log or stack trace, I'm not going to bother guessing.

Instead, I'm just going to say "Use NSInvocation". Seriously, trying to do what you're doing with blocks is silly. Especially when NSInvocation already exists and is well-tested (hint: it's instrumental in Distributed Objects).

And I'm also going to suggest NOT using class-methods. Use instance methods, even if it means targeting an instance of the Action class instead of targeting the Action class itself. So:
@interface Action : NSObject
- (void)execute;
@end

@implementation Action
- (void)execute
{
NSLog(@"The action was executed.");
}
@end

kpua
Feb 18, 2012, 10:39 PM
You need to explicitly copy the block to allow it to live beyond the scope it's declared in.

Blocks live on the stack by default, unlike every other object that starts off in the heap. Adding a stack-based block to an array will call -retain on it, sure, but that doesn't actually have any effect on its lifetime. The only way to get the block to the heap is to -copy it. Once there, it will respond properly to -retain and -release. (don't forget to balance the -copy too!)