滚动使用 IB 设置的 UIPickerView 时崩溃

Posted

技术标签:

【中文标题】滚动使用 IB 设置的 UIPickerView 时崩溃【英文标题】:Crash when scrolling a UIPickerView set up with IB 【发布时间】:2009-12-07 10:23:26 【问题描述】:

这是我第一次使用 IB,但在与它相处了一两天后,我相信我开始理解它了。这只是我的说法,我可能在这里忽略了一些简单的事情:

我已经设置了一个 UIPickerView 并将它加入到它在 IB 中的 DataSource 和 Delegate 对象(在我的例子中是两个不同的类)。这允许选择器在我运行应用程序时出现,当它在之前的任何测试运行中都没有出现时,这是非常令人鼓舞的。 ;) 但是,当我滚动 UIPickerView 时,程序崩溃了,并且我找不到回溯中引用的任何代码。经过相当多的故障排除后,我认为我已经将崩溃范围缩小到两种不同的情况,就回溯而言:

-pickerView:numberOfRowsInComponent的返回值:>显示的行数

一旦开始选择新行的动作,应用就会崩溃 如果我尝试使用 -selectRow:inComponent:animated: 应用程序崩溃:

回溯(忽略主):

#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 ()

-pickerView:numberOfRowsInComponent的返回值:

应用程序在运动停止并选择行后崩溃 如果我尝试使用 -selectRow:inComponent:animated:,应用程序确实不会崩溃:

回溯(忽略主):

#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 ()

我的委托和数据源实现如下:

- (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");

【问题讨论】:

你能展示你对 HeartbeatTimerCallback() 的实现吗?此外,您能否将 -didSelectRow 中的 NSLog 更改为 NSLog(@"Selected %d in component %d", row, component) 以便获得预期的索引。还是在那之前就崩溃了? 我没有实现 HeartbeatTimerCallback()。我需要吗?回调中列出的方法或函数都不是我的。回复您的其他建议,不,该应用程序永远不会到达 -didSelectRow:inComponent:. 我已经弄清楚发生了什么,但我不知道如何正确修复它:我发现如果我覆盖 UIPickerViewDelegate 中的 -dealloc 方法并且故意不发送[super dealloc],应用程序不会崩溃。为什么会这样,使用 IB 处理这种情况的正确方法是什么? 你可能发布了一些你没有保留的东西。即超类在其中发布的东西是-dealloc 根据 Vladimir 下面的非常有用的回答,IB 正在发布 UIPickerView,正如其设计的那样。我遇到的问题是弄清楚如何正确保留它。恐怕我不理解他所指的文件——或者至少我试图将其翻译成代码的尝试失败了。 【参考方案1】:

稍微研究了 Apple 文档,它证明了我之前的猜测。来自Resource Programming Guide:

创建 nib 文件中的对象 保留计数为 1,然后 自动发布。当它重建 对象层次结构,然而,UIKit 重新建立之间的连接 使用 setValue:forKey 的对象: 方法,它使用可用的 setter 方法或通过以下方式保留对象 如果没有 setter 方法,则默认为 可用的。如果您定义网点 nib 文件对象,您还应该 定义一个用于访问的 setter 方法 那个出口。设置方法 网点应该保持他们的价值观, 和插座的设置方法 包含***对象必须 保留他们的价值观以防止他们 从被释放。如果你不 将***对象存储在 网点,您必须保留 由返回的数组 loadNibNamed:owner:options: 方法或 数组中的对象 防止那些对象 提前释放。

所以***对象是自动释放的,您必须在代码中保留它们。还描述了处理该问题的推荐方法:

对于 Mac OS X 和 UIKit, 推荐的管理方法 nib 文件中的***对象是 在文件中为他们创建出口 Owner 对象,然后定义 setter 保留和释放这些的方法 根据需要的对象。 Setter 方法给出 你一个合适的地方包括 你的内存管理代码,即使在 您的应用程序使用的情况 垃圾收集。一种简单的方法 实现你的setter方法是 使用@property 语法并让 编译器为你创建它们。

我已经在示例代码中测试了这种方法 - 为文件所有者类中的委托和数据源对象定义了出口,并在 IB 中将它们连接起来。并且在文件所有者类中为这些网点定义了一个属性:

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

工作正常。

【讨论】:

您的回答看起来很全面,但由于某种原因我无法使其正常工作。除了@property... 行之外,我还需要更多代码吗?例如,我是否需要 [myDelegate 保留];某处,如果有,在哪里?如果我把它放在文件所有者的 -viewDidLoad 中,它似乎不起作用。 您是否已将代理和数据源连接到文件所有者出口? 好的,我需要把它放下几天,这样我才能清醒地回过头来。在我制作 IBOutlets 之前,我无法将代理和数据源连接到 IB 中的文件所有者 - 我的最终代码看起来像 '@property (nonatomic, retain) IBOutlet iFartPickerDelegate *pickerDelegate;' (我把委托和数据源放在一起)。这让我将它连接到 IB 中,最终奏效了——而且它也很有意义。谢谢! 当,对不起!我错过了赏金截止日期,所以我无法选择你的答案作为正确的答案:/ 我希望你仍然得到一些分数......谢谢你的帮助。【参考方案2】:

根据您的最后一条评论,您的 UIPickerView 的委托可能会被删除,之后您的 picker.delegate 引用了无效的内存...... 可能的解决方案:

    确保您的委托对象在您使用选择器时有效 - 将其保留在某处并在您的选择器被销毁时释放(例如,在选择器的父视图控制器 dealloc 方法中) 在您的 dealloc 方法中,将选取器的委托属性设置为 nil - 它必须消除崩溃,但它也会停止处理选取器事件。

【讨论】:

你的意思是像 [self retain];在 -init 你的第一个例子?知道为什么要释放委托对象会很有帮助,因为(对我来说)为什么在使用 IB 时选择器的委托会在选择器之前被释放是完全没有意义的。 这只是我的猜测,我可能在这里错了。你如何创建你的委托对象? 实际上,在撰写本文时,我什至没有 -init。它由我在问题中专门列出的最后两种方法组成。我也根本没有在我的代码中引用委托 - IB 完成了所有工作。所以,你所看到的就是我所拥有的。我在想我可能会遗漏一些我需要的东西。【参考方案3】:

您说您的pickerview 的委托和数据源是不同的类。你在哪里设置这些?在您的 xib 中或以编程方式设置连接?您为委托和数据源创建的对象是否可能没有保留?

所以下次需要引用它们时,它们已经被释放,你得到一个异常。

为什么要使用不同的对象作为委托和数据源?为什么不在视图控制器本身中实现它们?

【讨论】:

出于各种原因,我想让它们远离我的 viewController。 IB 不需要我在视图控制器中实现委托和数据源,是吗? (在我看来,这似乎是 IB 设计中的一个重大缺陷,恕我直言,因为它会限制 OOP 的有用性)。【参考方案4】:

我想说,没有太多详细调查,您应该确保 IB 中的每个对象都通过保留的属性连接到文件所有者。这是我见过的崩溃的第一个原因。一旦某些东西被引用,甚至没有被引用,但在某种程度上不是文件所有者的孩子,它就会导致崩溃。开始时没有连接,没有代表,除了建立这个链所需的那些。如果这在没有崩溃的情况下工作,请建立一个连接,然后测试,然后重复。滚动崩溃几乎总是发生,因为某些东西是自动释放的。

如果你在没有使用 [[B alloc] init] 的情况下获得了对象 b,那么预计它会在运行循环继续后消失。 (第一次之后,您可以触摸您的视图)。解决方法是告诉对象 b 保留,通常在另一个对象中引用它之后,

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

另一个解决方案是通过 IB。在标题中这样做:

@interface a : NSObject
    id<UIPickerViewDelegate> myReferenceToDelegate;


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

@end

然后您需要进入界面生成器,并将对象 A 上的 myReferenceToDelegate 的连接拖到对象 B。完成后,确保文件的所有者与 A 具有这种类型的连接。

这些界面构建器连接可能很棘手,因为它们不会告诉您太多有关问题的信息,而且它们做的事情也不像您在幕后所做的那样。

祝你好运解决这个问题。

【讨论】:

以上是关于滚动使用 IB 设置的 UIPickerView 时崩溃的主要内容,如果未能解决你的问题,请参考以下文章

UIPickerView如何滚动到指定的行

ios UIPickerView 技巧集锦(包括循环滚动)

UIPickerView 在滚动时跳过数组中的值

触摸 UITextField 时设置 UIPickerView 的默认值

设置 UIPickerView 行的背景颜色

从 NSArray 对象设置 UIPickerView