Cannot form weak reference to UIScrollView sub class (EXC_BAD_INSTRUCTION) when dismissing modal view


Cannot form weak reference to UIScrollView sub class (EXC_BAD_INSTRUCTION) when dismissing modal view



Hi everyone I've been debugging this issue for quite some time but no luck so far. I am quite lost here and have no clue on the reason causing this crash and how to fix it. I will be very grateful if anyone can offer me some help on this, thanks a lot!



I've prepared a sample project to demonstrate the issue at GitHub here.



The scenario is as the following:



There are two view controllers, namely the root view and modal view, each has a custom scroll view (class namely SubScorllView) as sub view, and the modal view has a button for dismissing the modal view.


SubScorllView



The scroll views are sub-classes of UIScrollView, each with their corresponding delegate protocol, and their class hierarchy is as below:



UIScrollView
∟ SuperScrollView
.....∟ SubScrollView



The app launches and runs in a very simple manner, in AppDelegate's didFinishLaunchingWithOptions:


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor blackColor];

RootViewController * rootVC = [[RootViewController alloc] init];
self.navVC = [[UINavigationController alloc] initWithRootViewController:rootVC];
self.navVC.navigationBarHidden = TRUE;

self.window.rootViewController = self.navVC;
[self.window makeKeyAndVisible];

ModalViewController *modalVC = [[ModalViewController alloc] init];
[self.navVC presentViewController:modalVC animated:YES completion:nil];

return YES;
}



And the views are loaded from xib files, which the scroll views' delegate are also set inside, and there are some overrides regarding the methods for initiating and setting delegate for the scroll view sub-classes.



The problem occurs when I dismiss the modal view through clicking the "Close" button in the modal view, when the button is clicked, the following happens:


- (IBAction)didPressedCloseButton:(id)sender {

self.subScrollView.delegate = nil;
[self dismissViewControllerAnimated:YES completion:nil];

}



And the app crashes at the following segment in SuperScrollView:


SuperScrollView


- (void)setDelegate:(id<SuperScrollViewDelegate>)delegate {

_superScrollViewDelegate = delegate;

// trigger UIScrollView to re-examine delegate for selectors it responds
super.delegate = nil;
super.delegate = self; // app crashes at this line
}



With the following error message in the console:



objc[6745]: Cannot form weak reference to instance (0x7fa803839000) of
class SubScrollView. It is possible that this object was
over-released, or is in the process of deallocation.



I don't understand why the app would crash and giving the above error message, or how should I fix it. I tried to search with the error message but seems that message is mostly related to other classes like text views, while some others solved it with setting the delegate of the scroll view to nil before deallocating but it doesn't work in my case.



==========



Update: Just tested if this happens on iOS 8 with simulator, it doesn't crash like on iOS 9 at all.





This is probably related to the inexplicable change in behavior in iOS 9+/ OS X 10.11+ where assigning an object undergoing deallocation to a weak variable crashes
– user102008
Feb 9 '17 at 20:58




3 Answers
3



When the SuperScrollView is deallocated, setDelegate is called implicitly. In iOS 9, you cannot set the delegate to self, because self is in the process of being deallocated (no idea why this worked in iOS 8). To work around this issue, you can check first to see if the passed in delegate parameter is not nil, and only then set super.delegate to self:


- (void)setDelegate:(id<SuperScrollViewDelegate>)delegate {

_superScrollViewDelegate = delegate;

// trigger UIScrollView to re-examine delegate for selectors it responds
super.delegate = nil;

if(delegate)
{
super.delegate = self;
}
}



If for some reason you need to support self responding to the UIScrollView delegate methods even when _superScrollViewDelegate is nil, you could create a parameter


@interface SuperScrollView ()

@property (nonatomic, weak) SuperScrollView * weakSelf;

@end



at the top of the file, and set it in setup


- (void)setup {

super.delegate = self;
self.weakSelf = self;
}



Then, in setDelegate, just check that weakSelf is not nil. If weakSelf is nil, then self is in the process of deallocating and you should not set it to the super.delegate:


- (void)setDelegate:(id<SuperScrollViewDelegate>)delegate {

_superScrollViewDelegate = delegate;

// trigger UIScrollView to re-examine delegate for selectors it responds
super.delegate = nil;

if(self.weakSelf)
{
super.delegate = self;
}
}





Thanks a lot! I have been trying to debug and solve this issue for quite some time. Made some changes as you suggested and it is working perfectly.
– alanlo
Mar 15 '16 at 6:24





Regarding why it is working on iOS 8, I am not sure if this is the reason, but during my search for solution I've found this topic stating that Apple made some changes on UIScrollView's delegate, but his solution doesn't seem to solve my problem. Here is the link: forums.developer.apple.com/thread/19027
– alanlo
Mar 15 '16 at 6:25





Thanks a lot! I spent two hours to deal with a mystery iOS 9 crash until I saw this.
– Oliver Zhang
Nov 1 '17 at 5:33



super.delegate = self ,super here is UIScrollView, super.delegate is of type UIScrollViewDelegate, and self is of type UIScrollView, so you are setting UIScrollView's delegate to be a scroll view, which doesn't make sense, normally controller should be the delegate of UIScrollView.


super.delegate = self


super


UIScrollView


super.delegate


UIScrollViewDelegate


self


UIScrollView


UIScrollView


UIScrollView



When you dismiss modal view controller, it is in the process of deallocation. super.delegate = self;, here self is a scroll view which is a subview of self.view, which belongs to the modal view controller. so self is also deallocating.


super.delegate = self;


self


self.view


self



I had the same problem in Swift and cncool's answer helped me.
The following (considering being in the parent class's instance) fixed my issue:


deinit {
self.scrollView.delegate = nil
}






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Comments

Popular posts from this blog

paramiko-expect timeout is happening after executing the command

how to run turtle graphics in Colaboratory

Export result set on Dbeaver to CSV