Custom Segue that mimics Push Segue turns VC into Zombie

[Make short and understandable things]

I wrote a custom indent.

-(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 running segue, an exception does not exist. However, release destinationViewController .

The application will crash when a button is pressed that starts another session in destinationViewController .

I tried to remove [preV removeFromSuperview] , but to no avail.

[Details]

I recently started working with Object-C, and I wrote a custom segue that mimics a push sega.

At the first start, everything works fine.

But after that, no matter what session is running, the application crashes, and I get an EXC_BAD_ACCESS error.


My first guess is that this is related to memory management. Something there needs to be released, but I have no idea what it is.

My second assumption is that this is due to the infrastructure provided by UIView and UIWindow . But then again, due to a lack of knowledge and experience, I cannot understand what the real problem is.

I know that I can actually make a simple approach and create a push sega using the rootviewcontroller and just hide the navigation bar, but I really want to know what is exactly wrong with my seemingly well-designed custom segue and study what happens under cloth codes.


[Update]

Thanks to Phillip Mills and Joachim Isaksson for the suggestions, having done some experiments and using the control points and the Zombie tool,

here is what i understand:

  • After the user segue has been launched by the button, the application will only crash when the next session is also launched by the button. Starting the next session with viewDidAppear will not crash.

  • The main cause of the accident:

An Objective-C message was sent to a freed object (zombies)

[#, 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 means that (if I'm not mistaken)

the custom segue somehow called something that frees up the objects to which the Objective-C message will be sent after clicking the button (in destinationViewController ) that launches another segment.


More details

Not one prepareForSegue , since I don't need to pass data between views.

My sessions start 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 ARC enabled, so I really did not make any release.


[UPDATES 2]

The object that has been turned into a zombie is the destinationViewController user segment.

Do not call removeFromSuperview in custom segue does not stop the object from turning into a zombie.

As long as I use the usual series of models or the push sega (with rootViewController) instead of the usual one I made, there will be no zombies and everything will work fine.

+4
source share
3 answers

You get a failure only because your new controller was not saved after segue.

What you do is:

  • src and dest controllers are created
  • you are doing animation
  • At the end, you delete the src view
  • your src controller is freed, but the rootViewController window still points to it, and your destination view manager is not added to the window hierarchy.

This will work 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; }]; } 
+5
source

in iOS, the connection between viewController and its .view is special.

all of these runtime calls ( initWithCoder: UIStoryboardSegue initWithIdentifier:source:destination: etc.) indicate an attempt to access something related to the viewController backstage, and one thing you did is removed from main. view from where it was expected in its review.

by running removeFromSuperview in the sourceViewController view, you are inviting chaos.

If you want the view not to be push-segue, you can make segue Modal segue. this will allow you not to get confused with the navigation bar, and yet it may allow CocoaTouch runtime to do segue work (and cleanup after that) for you.

if you really want the control to remain in the same viewController, then you can change your code to execute [preV setHidden:YES] or [preV setAlpha:0] . he will still be there so that he does not turn into a zombie, and you can return to him, paying attention to which of these two actions you prefer.

you can even try to delete (or comment out the call) before [preV removeFromSuperview] and shift it back when you return from what you are doing in newV.

Edit

Another thing to consider is the use of the __block Storage Type for the preV variable, since you declare it locally, and since you leave the scope, and the original viewController can make it go away. in the completion block, you can get a link to a variable that already had a ref counter, dropped to 0 and deleted by the time you get there. __block designed to prevent this.

+2
source

You need to save the link to destinationViewController so that it does not free. Obviously, you cannot do this in your user segment, as this object is freed after the transition is completed. The standard push segue does this by adding a view controller to the viewControllers property for the UITabBarController , for example.

In your case, you can call

 [sourceViewController addChildViewController:destinationViewController]; 

to establish the correct connection between view controllers. Remember to call removeFromParentViewController in your backward segment.

0
source

Source: https://habr.com/ru/post/1440952/


All Articles