问题
[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:
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.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