views:

743

answers:

1

It sure looks innocuous enough. In my App Delegate, I check NSUserDefaults for a flag to show tips at startup. If it’s set then, at the end of applicationDidFinishLaunching:, I do this:

TipsViewController *vc = [[TipsViewController alloc]
  initWithNibName:@“TipsView" bundle:nil];
[window addSubview:vc.view];
[vc release];

The idea is to show this view temporarily. (Note that it’s not a modal VC. No Navigation Controller at this point, plus there’s no navigation bar in this view anyway.)

Once this view is dismissed, we’ll add our pre-empted UITabBarController’s view to the window and transition over to it, then remove the tip view from the main window. I haven’t made it to the view dismissal point yet because, well, keep reading.

My TipsView’s VC is wired up more or less like so:

UIView -> view -> File’s Owner (TipsViewController)
  UIImageView -> background image
  UIView -> tipView -> File’s Owner
    UIImageView -> background image
    UIScrollView
      UILabel (tip text)
    UIButton -> touch-up-inside -> -(IBAction)button1:
    UIButton -> touch-up-inside -> -(IBAction)button2:
    UIButton -> touch-up-inside -> -(IBAction)button3:

The source contains declarations and definitions for all three IBAction calls. Right now two of them do nothing. The third one changes the tip text, resizes it to fit, and adjusts the scroll view’s contentSize to match.

When I run the app, the TipsViewController view shows up just fine. I can even scroll through the tip text. However, when I trigger a touch-up-inside on any UIButton, Xcode starts to plant me in the source (where I’ve placed a breakpoint at each IBAction) … and then bails out with either a EXC_BAD_ACCESS or obj_stack_overflow.

I have compared this with other parts of the app where I have a VC, a view, and buttons. It is identical in every way except that, in this case, I’ve added a VC’s view as a subview of the app’s window instead of pushing the VC onto a navigation controller. Moreover, the View Controller Programming Guide for iPhone OS docs say this is fair game:

If you use a single view controller in your application, you add its view to a window instead of adding the view controller to a tab bar or navigation controller.

True, I do have a UITabBarController waiting in the wings, and it has tabs with UINavigationControllers (and other VCs) in it. However, if the tip view is being shown, the tab bar controller’s view has not yet been added to the window. The intent is to swap it in after we’re done showing tips. In other words, for all intents and purposes, we’re temporarily acting like we have a single VC in play. Afterward, we switch to the tab bar and tear down the tip VC.

Perhaps I’m doing something subtly wrong? Is there a better way? (“There’s got to be a better way!”)

Sample stack trace:

#0  0x992b6f52 in objc_exception_throw
#1  0x302d6ffb in -[NSObject doesNotRecognizeSelector:]
#2  0x3026e056 in ___forwarding___
#3  0x3024a0a2 in __forwarding_prep_0___
#4  0x308f79d1 in -[UIApplication sendAction:to:from:forEvent:]
#5  0x309598b1 in -[UIControl sendAction:to:forEvent:]
#6  0x3095bad2 in -[UIControl(Internal) _sendActionsForEvents:withEvent:]
#7  0x3095a81e in -[UIControl touchesEnded:withEvent:]
#8  0x30910fdf in -[UIWindow _sendTouchesForEvent:]
#9  0x308faecb in -[UIApplication sendEvent:]
#10 0x309013e1 in _UIApplicationHandleEvent
#11 0x32046375 in PurpleEventCallback
#12 0x30245560 in CFRunLoopRunSpecific
#13 0x30244628 in CFRunLoopRunInMode
#14 0x32044c31 in GSEventRunModal
#15 0x32044cf6 in GSEventRun
#16 0x309021ee in UIApplicationMain
#17 0x00002888 in main at main.m:14

As you can see from the trace, we end up at doesNotRecognizeSelector: … except I can clearly see the methods in my tip VC source. Plus, they’re all wired up. (No multiple wirings or anything like that in IB. Everything looks good there, down to the File’s Owner relationships.)

Clues welcome/appreciated!

A: 

The problem is right there in your first code snippet: you’re creating an instance of TipsViewController, retaining its view, then releasing the view controller--which will cause it to be deallocated. So now the target of the buttons is a pointer to the deallocated view controller.

A view does not retain its view controller, nor do they retain their delegates or targets.

You'll have to keep the view controller instance retained—perhaps in a retain property—for as long as you want the view to be displayed. When you're done with the view, you can remove it from its parent view, and release the view controller.

adurdin
Ahh, great eye!I’m so used to creating stuff with alloc, adding it to something else (which retains it), and then releasing `my` local alloc to balance things out … I can’t believe I missed that. :)I can retain the VC in the app delegate, since that’s where I’m going to transition back to the tab bar controller.
Joe D'Andrea
What I originally did was add the vc as the subview - which of course is completely wrong, but it’s also why I tossed in the release afterward. When I fixed it to use vc.view, I missed that disconnect.Lesson: It’s OK to run static analysis (which would have caught this, I’ll be) *more* often than usual! :)Thanks again! Yes I feel silly, but I suppose this will stand as a good object lesson for others.
Joe D'Andrea
We all make silly mistakes like this. Sometimes all it takes is another set of eyes to notice them!
adurdin