【问题标题】:a Custom Segue that Simulates a Push Segue turns VC into Zombie模拟 Push Segue 的自定义 Segue 将 VC 变成僵尸
【发布时间】:2012-10-10 21:13:41
【问题描述】:

[使事情简明扼要]

我写了一个自定义的转场。

-(void)perform {
UIView *preV = ((UIViewController *)self.sourceViewController).view;
UIView *newV = ((UIViewController *)self.destinationViewController).view;

[preV.window insertSubview:newV aboveSubview:preV];
newV.center = CGPointMake(preV.center.x + preV.frame.size.width, newV.center.y);
[UIView animateWithDuration:0.4
 animations:^{
     newV.center = CGPointMake(preV.center.x, newV.center.y);
     preV.center = CGPointMake(0- preV.center.x, newV.center.y);}
    completion:^(BOOL finished){ [preV removeFromSuperview]; }];
}

当 segue 被触发时,没有例外。但是它会解除分配 destinationViewController

当单击destinationViewController 中触发另一个segue 的按钮时,应用程序会崩溃。

我尝试删除[preV removeFromSuperview],但无济于事。

[详情]

我最近开始使用 Object-C,并编写了一个自定义 segue 来模拟 push segue。

第一次触发时,一切正常。

但是在那之后,无论触发什么segue, 应用程序崩溃,我会收到 EXC_BAD_ACCESS 错误。


我的第一个猜测是这与内存管理有关。有些东西必须被释放,但我不知道它是什么。

我的第二个猜测是,这与 UIViewUIWindow 提供的基础设施有关。但是再一次,由于我缺乏知识和经验,我无法弄清楚真正的问题是什么。

我知道我实际上可以采取一种简单的方法并通过使用 rootviewcontroller 并简单地隐藏导航栏来创建推送转场,但我真的很想知道我看似结构良好的自定义转场到底有什么问题并学习代码结构下面发生了什么。


[更新]

感谢 Phillip Mills 和 Joachim Isaksson 在进行一些实验并使用断点和 Zombie 工具后提出的建议,

这是我意识到的:

  1. 自定义 segue 被按钮触发后,只有当下一个 segue 也被按钮触发时,应用才会崩溃。使用viewDidAppear 触发下一个 segue 不会导致任何崩溃。

  2. 崩溃的主要原因:

一个 Objective-C 消息被发送到一个被释放的对象(僵尸)

[#, 事件类型, refCt, Library, Caller]

0   Malloc  1   UIKit   -[UIClassSwapper initWithCoder:]
1   Retain  2   UIKit   -[UIRuntimeConnection initWithCoder:]
2   Retain  3   UIKit   -[UIRuntimeConnection initWithCoder:]
3   Retain  4   UIKit   -[UIRuntimeConnection initWithCoder:]
4   Retain  5   UIKit   -[UIRuntimeConnection initWithCoder:]
5   Retain  6   UIKit   -[UIRuntimeConnection initWithCoder:]
6   Retain  7   UIKit   -[UIRuntimeConnection initWithCoder:]
7   Retain  8   UIKit   -[UIRuntimeConnection initWithCoder:]
8   Retain  9   UIKit   UINibDecoderDecodeObjectForValue
9   Retain  10  UIKit   UINibDecoderDecodeObjectForValue
10  Retain  11  UIKit   -[UIStoryboardScene setSceneViewController:]
11  Retain  12  UIKit   -[UINib instantiateWithOwner:options:]
12  Release 11  UIKit   -[UINibDecoder finishDecoding]
13  Release 10  UIKit   -[UINibDecoder finishDecoding]
14  Release 9   UIKit   -[UIRuntimeConnection dealloc]
15  Release 8   UIKit   -[UIRuntimeConnection dealloc]
16  Release 7   UIKit   -[UIRuntimeConnection dealloc]
17  Release 6   UIKit   -[UIRuntimeConnection dealloc]
18  Release 5   UIKit   -[UINibDecoder finishDecoding]
19  Release 4   UIKit   -[UIRuntimeConnection dealloc]
20  Release 3   UIKit   -[UIRuntimeConnection dealloc]
21  Release 2   UIKit   -[UIRuntimeConnection dealloc]
22  Retain  3   UIKit   -[UIStoryboardSegue initWithIdentifier:source:destination:]
23  Retain  4   ProjectX    -[pushlike perform]
24  Retain  5   UIKit   -[UINib instantiateWithOwner:options:]
25  Retain  6   UIKit   +[UIProxyObject addMappingFromIdentifier:toObject:forCoder:]
26  Retain  7   UIKit   -[UIProxyObject initWithCoder:]
27  Retain  8   UIKit   -[UIRuntimeConnection initWithCoder:]
28  Retain  9   UIKit   UINibDecoderDecodeObjectForValue
29  Retain  10  UIKit   UINibDecoderDecodeObjectForValue
30  Release 9   UIKit   -[UINib instantiateWithOwner:options:]
31  Release 8   UIKit   +[UIProxyObject removeMappingsForCoder:]
32  Release 7   UIKit   -[UINibDecoder finishDecoding]
33  Release 6   UIKit   -[UIRuntimeConnection dealloc]
34  Release 5   UIKit   -[UINibDecoder finishDecoding]
35  Release 4   UIKit   -[UINibDecoder finishDecoding]
36  Release 3   ProjectX    -[pushlike perform]
37  Retain  4   libsystem_sim_blocks.dylib  _Block_object_assign
38  Retain  5   UIKit   -[UIApplication _addAfterCACommitBlockForViewController:]
39  Release 4   UIKit   -[UIStoryboardSegue dealloc]
40  Release 3   UIKit   _UIApplicationHandleEvent
41  Release 2   UIKit   -[UIStoryboardScene dealloc]
42  Retain  3   UIKit   _applyBlockToCFArrayCopiedToStack
43  Release 2   UIKit   _applyBlockToCFArrayCopiedToStack
44  Release 1   UIKit   __destroy_helper_block_739
45  Release 0   UIKit   _applyBlockToCFArrayCopiedToStack
46  Zombie  -1  UIKit   -[UIApplication sendAction:to:from:forEvent:]

这意味着(如果我没记错

自定义 segue 以某种方式触发了释放对象的操作,在单击触发另一个 segue 的按钮(在 destinationViewController 中)后,将向该对象发送一条 Objective-C 消息。


更多详情

没有一个prepareForSegue 被调用,因为我不需要在视图之间传递数据。

我的 segues 都是以相同的方式触发的:

- (void)viewDidLoad
{
    [super viewDidLoad];
    CGRect buttonFrame = CGRectMake( 10, 40, 200, 50 );
    UIButton *button = [[UIButton alloc] initWithFrame: buttonFrame];
    [button setTitle: @"Go" forState: UIControlStateNormal];
    [button addTarget:self action:@selector(nextView) forControlEvents:UIControlEventTouchUpInside];
    [button setTitleColor: [UIColor blackColor] forState: UIControlStateNormal];
    [self.view addSubview:button]; 
}

- (void)nextView{
    [self performSegueWithIdentifier:@"push" sender:self];
}

我启用了我的 ARC,所以我自己并没有真正执行任何释放操作..


[更新 2]

变成僵尸的对象是自定义segue的destinationViewController

在自定义 segue 中不调用 removeFromSuperview 并不能阻止对象变成僵尸。

只要我使用常规模型转场或推送转场(使用 rootViewController)而不是我制作的自定义转场,就不会有任何僵尸,一切都会正常工作。

【问题讨论】:

  • 查看崩溃的详细信息可能很有用。如果您在 Objective-C 异常上设置断点(使用 Breakpoint Navigator),它可能会提供更好的信息。崩溃点的堆栈回溯也可能会给出一些提示。
  • Zombie tool in Instruments 对这个程序有什么要说的吗?
  • 感谢您的建议。 @JoachimIsaksson 僵尸工具最有帮助!这是一个伟大的发现,我刚刚更新了问题:)
  • @Arch 我认为需要一些代码......因为 segues 使它崩溃,并且第一次崩溃后的任何 segue,第一个 prepareForSegue 将是我的主要嫌疑人。
  • @JoachimIsaksson 但我根本没有使用prepareForSegue,因为我不需要在视图之间传递数据。我的segues都很简单。它们由[self performSegueWithIdentifier:@"segue" sender:self] 触发。

标签: iphone objective-c runtime-error uistoryboardsegue data-management


【解决方案1】:

在 iOS 中,viewController 和它的 .view 之间的关系是特殊的。

所有这些运行时调用(initWithCoder:UIStoryboardSegue initWithIdentifier:source:destination: 等)都指向试图访问与幕后 viewController 相关的东西,而您所做的一件事就是删除了它的主要 .view从它在其superview中的预期位置。

通过在 sourceViewController 的 .view 上执行 removeFromSuperview,您正在招致破坏。

如果你想要一个不是 push-segue 的视图,你可以将 segue 设置为 Modal segue。这将使您不必弄乱导航栏,但可以允许 CocoaTouch 运行时为您完成 segue 的工作(以及之后的清理工作)。

如果您真的希望控制留在同一个 viewController 中,那么您可以修改您的代码以执行 [preV setHidden:YES][preV setAlpha:0]。它仍然会在那里,所以它不会变成僵尸,你可以通过反转上述两个动作中的任何一个来恢复它。

您甚至可以尝试删除(或注释掉该调用)对 [preV removeFromSuperview] 的调用,并在您从 newV 中执行的任何操作返回时将其滑回。

编辑

要考虑的另一件事是在变量preV 上使用the __block Storage Type,因为您是在本地声明它,并且因为您要离开作用域并且原始viewController 可能会导致它消失。在完成块中,您最终可能会引用一个已经将其引用计数降至 0 并在您到达那里时将其删除的变量。 __block 旨在防止这种情况发生。

【讨论】:

  • 谢谢!是的,我认为这也是问题所在。但是,我把completion:^(BOOL finished){ [preV removeFromSuperview]; }改成completion:^(BOOL finished){ [preV setHidden:YES]; }后,还是变成了僵尸..
  • 有趣;您是否尝试过(例如)用从 sourceViewController 到 destinationViewController 的常规推送 segue 替换您的自定义 segue?只是想知道在这种情况下你是否仍然会因为这段代码而得到僵尸?
  • 是的,我试过了,只要是常规模型转场或常规推送转场(当然是根视图控制器),我就不会遇到任何僵尸。
【解决方案2】:

你正在崩溃只是因为你的新控制器在 segue 执行后没有保留。

你要做的是:

  • src 和 dest 控制器被实例化
  • 您执行动画
  • 完成后,您删除 src 视图
  • 您的 src 控制器被释放,但窗口的rootViewController 仍然指向它,并且您的目标视图控制器没有添加到窗口的层次结构中。

这将按预期工作:

-(void)perform {
  UIView *preV = ((UIViewController *)self.sourceViewController).view;
  UIView *newV = ((UIViewController *)self.destinationViewController).view;

  UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
  newV.center = CGPointMake(preV.center.x + preV.frame.size.width, newV.center.y);
  [window insertSubview:newV aboveSubview:preV];

  [UIView animateWithDuration:0.4
                   animations:^{
                       newV.center = CGPointMake(preV.center.x, newV.center.y);
                       preV.center = CGPointMake(0- preV.center.x, newV.center.y);}
                   completion:^(BOOL finished){
                       [preV removeFromSuperview];
                       window.rootViewController = self.destinationViewController;
                   }];
}

【讨论】:

  • 现在一切都说得通了:D 谢谢!
【解决方案3】:

您需要保留对destinationViewController 的引用,以免它被释放。显然,您不能在自定义 segue 中执行此操作,因为该对象在转换完成后被释放。例如,标准的 push segue 通过将视图控制器添加到 UITabBarControllerviewControllers 属性来实现这一点。

在你的情况下,你可以打电话

[sourceViewController addChildViewController:destinationViewController];

在视图控制器之间建立正确的连接。不要忘记在你的反向搜索中调用removeFromParentViewController

【讨论】:

    猜你喜欢
    • 2013-01-18
    • 1970-01-01
    • 2018-10-28
    • 2012-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-15
    相关资源
    最近更新 更多