自定义视图控制器转换时偶尔崩溃

3
我有一个UIViewController,使用在iOS 7中引入的转场代理模式,以自定义动画方式来呈现/解除另一个视图控制器。
我注意到在我的Crashlytics日志文件中,代码的这一部分偶尔会在生产环境中崩溃,并且我无法确定原因。它从未在我的测试中崩溃,并且似乎只影响10%左右的用户。 iPad,iPhone和iPod上的iOS 7和iOS 8也受到影响。
以下是来自Crashlytics的堆栈跟踪:
Crashed: com.apple.main-thread
EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0x00014000

Thread : Crashed: com.apple.main-thread
0  libobjc.A.dylib                0x303c3f76 objc_msgSend + 21
1  UIKit                          0x25ed0ac5 -[UIViewController _customAnimatorForDismissedController:] + 64
2  UIKit                          0x25ed0927 -[UIViewController _dismissViewControllerWithTransition:from:completion:] + 538
3  UIKit                          0x25e7e4b3 -[UIViewController dismissViewControllerWithTransition:completion:] + 822
4  UIKit                          0x25e7e4b3 -[UIViewController dismissViewControllerWithTransition:completion:] + 822
5  UIKit                          0x25e7e127 -[UIViewController dismissViewControllerAnimated:completion:] + 222
6  Skin Creator                   0x000f38e7 -[SEUSBodySideChooserViewController imageEditViewController:didFinishWithImage:] (SEUSBodySideChooserViewController.m:251)
7  Skin Creator                   0x000ae2e9 -[SEUSImageEditViewController sendFinishedDelegateMethod] (SEUSImageEditViewController.m:409)
8  Skin Creator                   0x000d585f -[SEUSCloseMenuController circleMenu:didSelectItemAtIndex:] (SEUSCloseMenuController.m:69)
9  Skin Creator                   0x000ceb75 -[SEUSCircleMenu menuButtonPressed:] (SEUSCircleMenu.m:203)
10 UIKit                          0x25dec427 -[UIApplication sendAction:to:from:forEvent:] + 70
11 UIKit                          0x25dec3c9 -[UIControl sendAction:to:forEvent:] + 44
12 UIKit                          0x25dd6fcd -[UIControl _sendActionsForEvents:withEvent:] + 584
13 UIKit                          0x25debdf9 -[UIControl touchesEnded:withEvent:] + 584
14 UIKit                          0x25db0b47 _UIGestureRecognizerUpdate + 10158
15 UIKit                          0x25de5afd -[UIWindow _sendGesturesForEvent:] + 784
16 UIKit                          0x25de53cd -[UIWindow sendEvent:] + 520
17 UIKit                          0x25dbbb5d -[UIApplication sendEvent:] + 196
18 UIKit                          0x2602f4e3 _UIApplicationHandleEventFromQueueEvent + 13874
19 UIKit                          0x25dba59f _UIApplicationHandleEventQueue + 1294
20 CoreFoundation                 0x228dd5e7 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 14
21 CoreFoundation                 0x228dc9fb __CFRunLoopDoSources0 + 222
22 CoreFoundation                 0x228db079 __CFRunLoopRun + 768
23 CoreFoundation                 0x22828981 CFRunLoopRunSpecific + 476
24 CoreFoundation                 0x22828793 CFRunLoopRunInMode + 106
25 GraphicsServices               0x29ba9051 GSEventRunModal + 136
26 UIKit                          0x25e1a981 UIApplicationMain + 1440
27 Skin Creator                   0x000ead6f main (main.m:16)

为了让您更好地理解,[SEUSBodySideChooserViewController imageEditViewController:didFinishWithImage:] 是一个委托回调函数,我在其中取消显示视图控制器。

我用来展示视图控制器的代码

_toPixelEditTransitioningDelegate = [[SEUSFadeInTransitioningDelegate alloc] init];
_imageEditViewController.transitioningDelegate = _toPixelEditTransitioningDelegate;
[self presentViewController:_imageEditViewController animated:YES completion:nil];

我用来关闭视图控制器的代码

[viewController dismissViewControllerAnimated:YES completion:nil];

我的转型代理

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
    SEUSFadeInTransitioning *transitioning = [[SEUSFadeInTransitioning alloc] init];
    return transitioning;
}

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    SEUSFadeInTransitioning *transitioning = [[SEUSFadeInTransitioning alloc] init];
    transitioning.reverse = YES;
    return transitioning;
}

实现UIViewControllerAnimatedTransitioning协议的过渡对象

- (instancetype)init
{
    if (self = [super init])
    {
        _reverse = NO;
        _duration = 0.25f;
    }
    return self;
}

- (void)animateToPixelEdit:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController* overviewVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController* pixelVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    [[transitionContext containerView] addSubview:overviewVC.view];
    [[transitionContext containerView] addSubview:pixelVC.view];

    pixelVC.view.alpha = 0;

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^
    {
        pixelVC.view.alpha = 1;
    }
    completion:^(BOOL finished)
    {
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];
}

- (void)animateToOverview:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController* pixelVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController* overviewVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    [[transitionContext containerView] addSubview:pixelVC.view];
    [[transitionContext containerView] addSubview:overviewVC.view];

    overviewVC.view.alpha = 0;

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^
    {
        overviewVC.view.alpha = 1;
    }
    completion:^(BOOL finished)
    {
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];
}

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
    return _duration;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    if (self.reverse == NO)
    {
        [self animateToPixelEdit:transitionContext];
    }
    else
    {
        [self animateToOverview:transitionContext];
    }
}

有人注意到有什么可能出错了吗?由于我无法复制崩溃,所以很难进行故障排除。


如果用户多次点击,是否有可能imageEditVC会调用其didFinish委托方法两次? - Acey
哦,天啊。如果真的那么简单,我不确定自己是应该感到愚蠢还是兴奋。现在就去测试一下! - Joel Bell
到目前为止,我还没有成功复制它,我会编写一个检查程序以防万一并将其推出。同时,有人看到可能引起此问题的其他原因吗? - Joel Bell
好的,再猜一下... toPixelEditTransitioningDelegate 是否被强制保留在某个地方? - Acey
我想我弄清楚了,多亏了你的评论。看起来快速点击可能会导致imageEditVC出现两次,然后当用户尝试在它自己上面呈现并关闭它时,会导致崩溃。感谢您的帮助! - Joel Bell
随意回答如果你想的话,我会接受。 - Joel Bell
2个回答

1

这段代码看起来很漂亮。我怀疑用户可以重复显示或关闭你的模态框 - 这是这些类型组件常见的故障点。


没错。在我的情况下,切换片段控制器选项卡和在点击时对视图控制器进行动画处理会导致崩溃。一个简单的解决方法是禁用UI控件上的交互,或者只需使用UIApplication.shared.beginIgnoringInteractionEvents()来忽略交互事件,并在动画完成后重新启用UIApplication.shared.endIgnoringInteractionEvents()。 - Raj D

0

我解决了一个类似于这个bug的问题,最终发现是transitioningDelegate(在问题示例中为_toPixelEditTransitioningDelegate)引用已被释放。

如果你看到EXEC_BAD_ACCESS错误,可以启用NSZombies来帮助你跟踪坏引用的对象。

希望这对其他人有用!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接