a Custom Segue that Simulates a Push Segue turns VC into Zombie

回眸只為那壹抹淺笑 提交于 2019-12-04 13:30:07

问题


[To Make things short and clear]

I've written a custom segue.

-(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]; }];
}

When the segue is triggered there is no exception. However it would deallocate destinationViewController.

The app would crash when a button, that triggers another segue, in destinationViewController is clicked.

I tried removing [preV removeFromSuperview] but to no avail.

[Details]

I've recently started working with Object-C and I wrote a custom segue that simulates a push segue.

The first time it is been triggered, everything works fine.

But after that, no matter what segue is been triggered, the app crashes and I would receive a EXC_BAD_ACCESS error.


My first guess is that this has to do with memory management. Something out there has to be deallocated but I have no idea what it is.

My second guess is that this has to do with the infrastructure provided by UIView and UIWindow . But once again, due to my lack of knowledge and experience, I can't figure out what the real problem is.

I know I can actually take an easy approach and create a push segue by using a rootviewcontroller and simply hiding the navigation bar, but I really want to know what exactly is wrong with my seemingly well-constructed custom segue and learn what is going on underneath the fabric of codes.


[Updates]

Thank Phillip Mills & Joachim Isaksson for the suggestions, after conducting some experiments and making use of breakpoints and the Zombie tool,

this is what I realise:

  1. After the custom segue has been triggered by a button,the app would only crash when the next segue is also triggered by a button. Triggering the next segue using viewDidAppear would not lead to any crash.

  2. The main reason behind the crash:

An Objective-C message was sent to a deallocated object (zombie)

[#, event type, 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:]

which implies that (if I'm not wrong)

the custom segue has somehow triggered something that deallocates objects, to which an objective-C message would be sent after button (in destinationViewController) that triggers another segue is clicked.


More Details

Not a single prepareForSegue is been called since I don't need to pass data between views.

My segues are all triggered the same way:

- (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];
}

I have my ARC enabled so I didn't really do any of the deallocation myself..


[UPDATES 2]

The object that has been turned into a zombie is the destinationViewController of the custom segue.

Not calling removeFromSuperview in the custom segue does not stop the object from turning into zombie.

As long as I use a regular model segue or a push segue (with rootViewController) instead the custom one I made, there won't be any zombie and everything would work fine.


回答1:


You're getting a crash just because your new controller not retained after segue execution.

What you do is this:

  • src and dest controllers are instantiated
  • you perform your animations
  • in completion you remove src view
  • your src controller gets released, but window's rootViewController still pointing to it and your destination view controller not added to window's hierarchy.

This will work just as intended:

-(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;
                   }];
}



回答2:


in iOS, the relationship between a viewController and it's .view is special.

all of those runtime calls (initWithCoder: , UIStoryboardSegue initWithIdentifier:source:destination: , et al) point to an attempt to access something to do with the viewController behind the scenes, and the one thing you've done is removed it's main .view from where it was expected in its superview.

by performing a removeFromSuperview on the .view of the sourceViewController, you are inviting havoc.

if you want a view that is not a push-segue, you can make the segue a Modal segue. this will keep you from having to mess with the navigation-bar, and yet can allow the CocoaTouch runtime to do the work of the segue (and the cleanup afterwards) for you.

if you really want control to stay in the same viewController, then you could modify your code to perform [preV setHidden:YES] or a [preV setAlpha:0]. it will still be there so it doesn't turn into a zombie, and you can get back to it by reversing whichever of the above two actions you prefer.

you might even just try removing (or commenting out the call) to [preV removeFromSuperview], and sliding it back in when you return from whatever you're doing in newV.

Edit

Another thing to consider is the use of the __block Storage Type on the variable preV, since you are declaring it locally, and since you are leaving scope and the original viewController may be causing it to go away. in the completion block, you could end up with a reference to a variable that's already had its ref-count dropped to 0 and removed by the time you get there. __block is meant to prevent this.




回答3:


You need to keep a reference to destinationViewController so that it doesn't get deallocated. Obviously, you cannot do this in your custom segue as this object is freed after the transition has finished. The standard push segue does this by adding the view controller to the viewControllers property of a UITabBarController, for example.

In your case, you could call

[sourceViewController addChildViewController:destinationViewController];

to establish a proper connection between the view controllers. Don't forget to call removeFromParentViewController in your reverse segue.



来源:https://stackoverflow.com/questions/12988081/a-custom-segue-that-simulates-a-push-segue-turns-vc-into-zombie

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!