views:

57

answers:

1

I have found a crash in an iPhone application with target iOS 4 that changes depending on the type of build.

The debugger is giving me nothing much to go on, it stops at

 UIViewController *result = [self factory](self);

with EXC_BAD_ACCESS. self is a class inheriting from NSObject (shown below as NSObjectInheritor). Zombies are enabled. I've tried changing method factory three ways with the following results.

This crashes in both debug and ad hoc builds...

- (FactoryMethod) factory;
{
    return [^ UIViewController * (NSObjectInheritor *newThing)
    {
      return [[ViewControllerClass alloc] initWithStuff:(boolValue ? foo : bar)];
    } autorelease];
}

This works in debug builds but crashes in ad hoc...

- (FactoryMethod) factory;
{
  return [^ UIViewController * (NSObjectInheritor *newThing)
  {
    if(boolValue)
    {
      return [[ViewControllerClass alloc] initWithStuff:foo];
    }
    else
    {
      return [[ViewControllerClass alloc] initWithStuff:bar];
    }
  } autorelease];
}

This works in both debug and ad hoc but is very ugly and redundant:

- (FactoryMethod) factory;
{
  if(boolValue)
  {
    return [^ UIViewController * (NSObjectInheritor *newThing)
    {
      return [[ViewControllerClass alloc] initWithStuff:foo];
    } autorelease];
  }
  else
  {
    return [^ UIViewController * (NSObjectInheritor *newThing)
    {
      return [[[ViewControllerClass alloc] initWithStuff:bar];
    } autorelease];
  }
}

My theory is that boolValue becomes inaccessible at the time the returned block is executed. It is

@interface SubclassOfNSObjectInheritor : NSObjectInheritor
{
  BOOL boolValue;
}

@property (readonly) BOOL boolValue;

(YES or NO assigned in SubclassOfNSObjectInheritor's init of course) and

@synthesize boolValue;

in SubclassOfNSObjectInheritor's implementation.

The final questions are - is my theory about what is wrong correct? Is the third way of doing it - noted as working in ad hoc and debug builds - safe? What is the best way to do this?

+4  A: 

Your problem is twofold, first you are overreleasing your block and second, blocks are created on the stack, which is going away when your method returns (you're basically returning a pointer into the previously destroyed stack frame).

Instead, copy the block and auto release it before returning. You can do this in the objective-c way with a copy message, your you can call the Block_copy() function.

Your crashing in various configurations is pure happenstance. So, to fix one of your implementations:

- (FactoryMethod) factory;
{
  return [[^(NSObjectInheritor *newThing)
  {
    if(boolValue)
    {
      return [[ViewControllerClass alloc] initWithStuff:foo];
    }
    else
    {
      return [[ViewControllerClass alloc] initWithStuff:bar];
    }
  } copy] autorelease];
}
Jason Coco
Nice answer. Note also that "Build and Analyze" should have caught this particular error. If it did not, please file a bug.
bbum
Thank you - the analyzer does not complain, so will log the bug. Adding copy resolves the problem.
Adam Eberbach