views:

579

answers:

4

This is my first time using IB, but after spending a one or two intimate days with it I believe I'm beginning to understand it. That's just my way of saying I might be overlooking something simple here:

I've set up a UIPickerView and joined it to its DataSource and Delegate object in IB (both different Classes in my case). This allows the picker to show up when I run the app, which is very encouraging when it hasn't been showing up in any previous test runs. ;) However, when I scroll the UIPickerView, the program crashes, and I can't find any of my code referenced in the backtrace. After quite a bit of troubleshooting, I think I've narrowed down the crash to two distinct cases, as far as the backtrace is concerned:

the return value of -pickerView:numberOfRowsInComponent: > the number of rows displayed

  • The app crashes as soon as a motion is begun to select a new row
  • The app crashes if I try to use -selectRow:inComponent:animated:

backtrace (ignoring main):

#0  0x955e8688 in objc_msgSend ()
#1  0x0167bea8 in -[UIPickerView table:cellForRow:column:reusing:] ()
#2  0x016773c1 in -[UIPickerView table:cellForRow:column:] ()
#3  0x017fef53 in -[UITable createPreparedCellForRow:column:] ()
#4  0x018077c8 in -[UITable _updateVisibleCellsNow] ()
#5  0x018027cf in -[UITable layoutSubviews] ()
#6  0x03ac42b0 in -[CALayer layoutSublayers] ()
#7  0x03ac406f in CALayerLayoutIfNeeded ()
#8  0x03ac38c6 in CA::Context::commit_transaction ()
#9  0x03ac353a in CA::Transaction::commit ()
#10 0x03acb838 in CA::Transaction::observer_callback ()
#11 0x007b8252 in __CFRunLoopDoObservers ()
#12 0x007b765f in CFRunLoopRunSpecific ()
#13 0x007b6c48 in CFRunLoopRunInMode ()
#14 0x000147ad in GSEventRunModal ()
#15 0x00014872 in GSEventRun ()
#16 0x0168a003 in UIApplicationMain ()

the return value of -pickerView:numberOfRowsInComponent: < the number of rows displayed

  • The app crashes after the motion ceases and the row is selected
  • The app does not crash if I try to use -selectRow:inComponent:animated:

backtrace (ignoring main):

#0  0x955e8688 in objc_msgSend ()
#1  0x0167700d in -[UIPickerView _sendSelectionChangedForComponent:] ()
#2  0x017f4187 in -[UIScroller _scrollAnimationEnded] ()
#3  0x016f732c in -[UIAnimator stopAnimation:] ()
#4  0x016f7154 in -[UIAnimator(Static) _advance:] ()
#5  0x00017739 in HeartbeatTimerCallback ()
#6  0x007b7ac0 in CFRunLoopRunSpecific ()
#7  0x007b6c48 in CFRunLoopRunInMode ()
#8  0x000147ad in GSEventRunModal ()
#9  0x00014872 in GSEventRun ()
#10 0x0168a003 in UIApplicationMain ()

My delegate and datasource implementations follow:

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    return (NSInteger)3;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
    return (NSInteger)4;
}

- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
  //it will probably be better to use the method following when creating the rows, so I can better customize it 
    return @"strings";
}

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
    NSLog(@"selected a row");
}
A: 

From your last comment may follow that the delegate for your UIPickerView is being deleted and after that your picker.delegate references to an invalid memory...
Possible solutions:

  1. Ensure that your delegate object will be valid while you're using your picker - retain it somewhere and release when your picker is being destroyed (e.g. in picker's parent view controller dealloc method)
  2. In your dealloc method set picker's delegate property to nil - it must remove the crash, but it will also stop handling picker events.
Vladimir
do you mean something like [self retain]; in -init for your first example? It would be helpful to know why the delegate object is getting released, since it doesn't make sense at all (to me) why the picker's delegate would be released before the Picker was when using IB.
JoBu1324
It is just my guess and I may be wrong here. How do you create your delegate object?
Vladimir
Actually, I didn't even have -init at the time of this writing. It consisted of the last two methods I listed in the question exclusively. I didn't reference the delegate in my code at all, either - IB did all the work. So, what you see is what I've got. I was thinking that I might be missing something I needed.
JoBu1324
+1  A: 

Investigated Apple documentation a bit and it proved my previous guess. From Resource Programming Guide:

Objects in the nib file are created with a retain count of 1 and then autoreleased. As it rebuilds the object hierarchy, however, UIKit reestablishes connections between the objects using the setValue:forKey: method, which uses the available setter method or retains the object by default if no setter method is available. If you define outlets for nib-file objects, you should also define a setter method for accessing that outlet. Setter methods for outlets should retain their values, and setter methods for outlets containing top-level objects must retain their values to prevent them from being deallocated. If you do not store the top-level objects in outlets, you must retain either the array returned by the loadNibNamed:owner:options: method or the objects inside the array to prevent those objects from being released prematurely.

So the top level objects are created autoreleased and you must retain them in your code. There's also described recommended way to handle that:

For both Mac OS X and UIKit, the recommended way to manage the top-level objects in a nib file is to create outlets for them in the File’s Owner object and then define setter methods to retain and release those objects as needed. Setter methods give you an appropriate place to include your memory-management code, even in situations where your application uses garbage collection. One easy way to implement your setter methods is to use the @property syntax and let the compiler create them for you.

I've tested this approach in a sample code - defined outlets for delegate and data source objects in file owner class and connected them in IB. And in file owner class defined a property for those outlets:

@property (nonatomic, retain) NSObject<UIPickerViewDelegate>* myDelegate;
@property (nonatomic, retain) NSObject<UIPickerViewDataSource>* mySource;

Worked fine.

Vladimir
Your answer looks thorough, but I'm having trouble making it work for some reason. Do I need more code than just the @property... lines? e.g., do I need to have [myDelegate retain]; somewhere, and if so where? It doesn't seem to work if I put it in -viewDidLoad of the File's Owner.
JoBu1324
Have you connected your delegate and data source to the file owner outlets?
Vladimir
Ok, I needed to put this down for a few days so I could come back to it with a clear head. I wasn't able to connect the delegate
JoBu1324
Dang, I'm sorry! I missed the bounty deadline so I can't select your answer as the correct one :/ I expect you still got some points though... Thanks for your help.
JoBu1324
Huh, now I can. I hope you got some points.
JoBu1324
A: 

You said your pickerview's delegate and datasource are different classes. Where are you setting these up? In your xib or setting the connections programatically? Is it possible that the objects you have created for the delegate and datasource are not retained?

So the next time when they need to be referenced, they have been released and you get an exception.

Why do you want to use different objects as delegate and datasource? Why not implement them in your viewcontroller itself?

lostInTransit
For various reasons I want to keep them out of my viewController. IB doesn't require that I implement the delegate and datasource in my viewcontroller, does it? (That would seem to me to be a pretty major shortcoming in the IB design, IMHO, as it would limit the usefulness of OOP).
JoBu1324
A: 

I would say without too much detailed investigation, that you should make sure every object in IB is connected through properties that retain, to File's Owner. That is the number one reason I have seen for crashes. As soon as something is referred to, or even not referred to, but not a child of file's owner in some way, it causes a crash. Start with no connections, no delegates, aside from the ones needed to make this chain. If this works without a crash, make one connection, then test, then repeat. Scrolling crashes almost always happen because something was autoreleased.

If you got object b without using [[B alloc] init] expect it to be gone after the run loop proceeds. (After the first time you are able to touch your view). The cure is to tell object b to retain, generally after making a reference to it in another object,

-(void)connectTo:(B*)b {
     self.myReference = b
     [B retain];
}

The other solution to this is through IB. in the headers do this:

@interface a : NSObject{
    id<UIPickerViewDelegate> myReferenceToDelegate;
}

@property(nonatomic, retain) IBOutlet id<UIPickerViewDelegate> myReferenceToDelegate

@end

Then you need to go into interface builder, and drag a connection from myReferenceToDelegate on object A to the object B. Once this is done, make sure File's owner has this type of connection to A.

Thiese interface builder connections can be tricky, because they don't tell you much about the problem, and they don't do as much as you might thing behind the scenes.

Good luck solving this one.

Alex Gosselin