weakSelf being deallocated before block - ios

So somehow my weakSelf variable is being deallocated before my block has a chance to execute. This only happens in one specific scenario, the other times I hit this block it works fine. Here's what my code looks like:
__weak typeof(self) weakSelf = self;
DBTEligibleAccountFetcher *accountFetcher = [[DBTEligibleAccountFetcher alloc] init];
NSArray *eligibleDepositAccounts = [accountFetcher fetchDepositEligibleAccounts];
if(eligibleDepositAccounts.count == 1) {
DBTDepositAmountLimitsHandler *limitChecker = [[DBTDepositAmountLimitsHandler alloc] init];
[limitChecker handleRequest:self.navigationController bankAccount:eligibleDepositAccounts.firstObject completionBlock:^(DBDepositCheckAccountLimits *limitDetails) {
containerController.limitDetails = limitDetails;
[weakSelf.navigationController handleNewRootPush:containerController withCompletion:completionBlock animated:YES];
}];
} else {
[self.navigationController handleNewRootPush:containerController withCompletion:completionBlock animated:YES];
}
By the time it gets to the weakSelf.navigationController... line, weakSelf is nil. I'm not sure how to even debug this or how the variable could get deallocated before it is even used.
Does anyone know any scenarios that could be going on right now? Or any tips on how I might go about debugging this issue?

Let's say you have a view controller, the user taps a button, therefore you send some information to the server asynchronously and you are prepared to handle when the request comes back. Meanwhile your user switches to another view and your view controller gets deallocated before your method returns.
First consider what you want to happen. Sometimes a request has become pointless if the view controller is gone. Say you have a site that translates German to French. If the translation comes back and your view controller is gone, you just ignore the result. But if you have to process the result, whether the view controller is there or not, then you need to rearrange your code so that it works even if the view controller is nil because the user switched to a different view.

Related

Passing blocks between view controllers

Iv searched a lot for this and cant find an answer to my specific question. But basically my question is, can i pass a completion block into another view controller and redefine it in the new view controller.
So for example in view controller A i have a method to perform a download with a completion block. First i create my block property in view controller A.
#property (copy)void (^downloadCompleteBlock)(NSArray *downloadItems);
I tried changing this to strong as opposed to copy but this did not solve my problem.
Then i define the completion block as follows.
self.downloadCompleteBlock = ^(NSArray *downloadItems) {
NSLOG(#"download complete in view controller A";
};
Then i call my download method passing in this completion block.
[self download:self.downloadCompleteBlock];
However, if this completion handler is not called by the time i leave this view controller (if the download isn't complete) I would want the completion block to perfrom something different on the next view controller. So in my prepare for segue i attempted to pass in this block to view controller B.
[controllerB setCompletionBlock:self.downloadCompleteBlock];
And this method in view controller B then redefines what happens when this completion block gets called.
- (void)setCompletionBlock:(void(^)(NSArray *downloadItems))downloadFinishedBlock {
downloadFinishedBlock = ^(NSArray *downloadItems) {
self.collectionData = downloadItems;
[self.collectionView reloadData];
};
}
However, the original block in view controller a still gets called when the download finishes as opposed to the block in view controller B. Anyone know how to have the completion block in view controller B called if that view is loaded before the download completes? I know i could use a notifier but i'm curious if i can do this with blocks.
Thanks
This is kind of a tough problem. At its heart is the problem of how to keep the block around after the first view controller goes away. Your current code solves that problem unwittingly by having the block refer to self. The vc is retained by that reference, which is good news if it needs to be around when the request finishes, but it's bad news because now the vc and the block will retain each other forever. (Google 'retain cycle'.)
So how do we get a long-running process that runs a block on completion and might outlive two or more view controllers? For starters, break that process into its own object. The interface of that object would look like:
#interface DownloadThingy
#property (copy)void (^downloadCompleteBlock)(NSArray *); // note, no need for dummy param names here
- (id)initWithRequestParams:(id)whateverIsNeededToStart;
- (void)start;
#end
Now, the view controller that want to start this can declare a strong property to it, create one, give it a completion block (see below**), and start it. When it's time for a segue, it can pass the downloadThingy to another vc, who can give it a different completion block.
** Since the request object is being kept as a property in one or more vcs, and since it retains the block, you still need to look out for a retain cycle:
(vc->downloadThingy->block->vc)
In VcA, do this:
- (void)startADownloadThingy {
self.downloadThingy = [[DownloadThingy alloc] initWithRequestParams:someParams];
__weak VcA *weakSelf = self;
self.downloadThingy.downloadCompleteBlock = ^(NSArray *downloadItems) {
// don't use self in here, use weakSelf
}
}
VcB will get called on the segue; it might or might not need to follow the same precaution. The distinction is whether this second vc retains a downloadThingy property. If it doesn't plan to hand it off to any other vc, it can skip the property, and thereby skip the worry about a retain cycle.
// another vc is handing off a running downloadThingy
- (void)heresARunningDownloadThingy:(DownloadThingy *)downloadThingy {
// if we have our own property, then
self.downloadThingy = downloadThingy;
// and we need to do the weakSelf trick
__weak VcA *weakSelf = self;
self.downloadThingy.downloadCompleteBlock = ^(NSArray *downloadItems) {
// don't use self in here, use weakSelf
}
}
Or...
// another vc is handing off a running downloadThingy
- (void)heresARunningDownloadThingy:(DownloadThingy *)downloadThingy {
// we do not have our own property
downloadThingy.downloadCompleteBlock = ^(NSArray *downloadItems) {
// feel free to use self in here
}
}
One last thing: it's a good practice for the DownloadThingy to aggressively nil out its block after it's through invoking it. So when the request is done, have it do this...
// DownloadThingy.m
// request is complete
self.downloadCompleteBlock(arrayFullOfResults);
self.downloadCompleteBlock = nil;

Am I properly using delegation? If not, how should I be doing this?

So I have taken the plunge into Objective-C programming for iOS development. I have a little app that I am working on, nothing special, but something to help teach me the ropes. The problem I am having is as follows:
Currently, I have two classes. The first being
ViewController
and the second being one that I created myself called
UserDecision
The View controller shows what is on screen, and UserDecisions currently takes the information from buttons pressed on screen, and performs the proper logic on it while working with my model class. My issue is, that I have an update UI method in UserDecision which needs to update the button properties (text, visibility, etc.) in ViewController if certain events take place. Because of this, I can't user an instance of ViewController because I won't be able to access the buttons on screen. So for this I created a delegate system:
#protocol updateUIDelegate <NSObject>
-(void)hideAll;
-(void)makeBackVisible;
-(void)updateOutput:(NSString *)output;
-(void)updateChoices:(NSString *)choices;
-(void)updateTrueButton:(NSString *)trueString;
-(void)updateFalseButton:(NSString *)falseString;
-(void)removeChoiceFromArray;
#end
The above protocol is defined in UserDecision.h, and then I assigned my ViewController as my delegate:
#interface ViewController : UIViewController <updateUIDelegate>;
And then I flush out said methods in my ViewController.m:
#pragma - updateUIDelegates -
//Called when the last screen is displayed
-(void)hideAll{
[_trueButton setHidden:true];
[_falseButton setHidden:true];
[_choicesText setHidden:true];
[_backButton setHidden:true];
[_resetButton setHidden:false];
}
//Makes back button visible
-(void)makeBackVisible{
[_backButton setHidden:false];
}
//Updates the text on the false button
-(void)updateFalseButton:(NSString *)falseString{
[_falseButton setTitle:falseString forState:UIControlStateNormal];
}
//Updates the text on the true button
-(void)updateTrueButton:(NSString *)trueString{
[_trueButton setTitle:trueString forState:UIControlStateNormal];
}
//Updates the output text box
-(void)updateOutput:(NSString *)output{
[_outputText setText:output];
}
//Updates the choices textbox
-(void)updateChoices:(NSString *)choices{
if(!choicesArray){
choicesArray = [[NSMutableArray alloc] initWithCapacity:4];
}
//If this is the first button press, add string to array and display
if([_choicesText.text isEqualToString:#""]){
[choicesArray addObject:choices];
_choicesText.text = [NSString stringWithFormat:#"%#", choices];
}
//Otherwise, add the new string to the array, and print the array
//using a comma as a way to concatinate the string and get rid of
//the ugly look of printing out an array.
else{
[choicesArray addObject:choices];
[_choicesText setText:[NSString stringWithFormat:#"%#",[choicesArray componentsJoinedByString:#", "]]];
}
}
//Removes the last choice from the array
-(void)removeChoiceFromArray{
[choicesArray removeLastObject];
[_choicesText setText:[NSString stringWithFormat:#"%#", [choicesArray componentsJoinedByString:#","]]];
}
This allows me to call theses methods by sending them as a message to self.delegate in my UserDecision class when needed.
This is my current setup. My issue has become that I want to create a modal seque view that pops up at the end (after a user presses a button to bring up the view), and which can be dismissed afterward. The problem I have is that this view, from the reading and research I have done online, can only be dismissed through delegation, unless I want things to get nasty. Now, I tried to implement this information in my class, but then I read that a class can only be a delegate to one other class. And since my ViewController(which is my main window) is already a delegate of my UserDecision class, I can't make it a delegate of the new View I have created, and thus can't dismiss the view. So, I am here to ask for your help. How can I go about solving this issue?
Also, for more of my code, should you want to have a look, here is a link to my gitHub: https://github.com/Aghassi/Xcode/tree/master/Bubble%20Tea%20Choice/Bubble%20Tea%20Choice
I read that a class can only be a delegate to one other class. And
since my ViewController(which is my main window) is already a delegate
of my UserDecision class, I can't make it a delegate of the new View I
have created
I don't believe that's true. You can make ViewController implement many different protocols, therefore being delegates to different classes/objects.
For example: (UITableViewDelegate and UITextViewDelegate can both be implemented on the same ViewController for 2 separate objects (UITextView and UITableView).
As for using delegation to close modal windows, another option is to use blocks as well.
It is possible for a viewController to dismiss itself. Just hook up a dismiss button to a function that calls something like:
[self.presentingViewController dismissViewControllerAnimated:YES];
or
[self.navigationController popViewControllerAnimated:YES];
Dismissal can be done with a delegate pattern but it is not required for everything.
You viewController class can be a delegate of multiple objects so it should be able to dismiss the modal view. The only issue is if its a delegate of multiple objects of the same class you may need to check which object is calling it.
Look at the tableView delegate methods as an example, the tableView calls them passing itself as the first parameter.
To dismiss a custom modal view you would define a different protocol anyway so there would be no problem with calling the same method.
See example below:
#protocol OSImageViewControllerProtocol <NSObject>
- (void)dismissImageViewer;
#end
#implementation OSImageViewController
- (void)loadView
{ //LOG(#"loadView called");
scrollView = [[ImageScrollView alloc] init];
scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(scrollViewDoubleTapped:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
doubleTapRecognizer.numberOfTouchesRequired = 1;
[scrollView addGestureRecognizer:doubleTapRecognizer];
self.view = scrollView;
}
- (BOOL)prefersStatusBarHidden {
return NO;
}
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer {
//LOG(#"scrollViewDoubleTapped called");
[self.delegate dismissImageViewer];
}
#end
#implementation ViewController
-(void)browseImage:(UIImage*)image
{
OSImageViewController *_imageViewerController = [[OSImageViewController alloc] init];
UIImage *img = [[UIImage alloc] initWithData:UIImagePNGRepresentation(image)];
_imageViewerController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
_imageViewerController.modalPresentationStyle = UIModalPresentationFullScreen;
_imageViewerController.delegate = self;
[self presentViewController:_imageViewerController animated:YES completion:^(void){
[_imageViewerController setImage:img];
}];
}
- (void)dismissImageViewer {
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
I believe you want to display a modal view from your ViewController.
Let the the modal view be managed by say ViewController2. In ViewController2.h declare a protocol of ViewController2
#protocol viewController2Delegate
-(void)dismissViewController2;
#end
Now make ViewController implement this protocol
#interface ViewController : UIViewController <updateUIDelegate,viewController2Delegate>
Add the method to the ViewController.m
-(void)dismissViewController2
{
[self dismissViewControllerAnimated:YES completion:nil];
}
Now whenever you push a modal view(managed by ViewController2) from ViewController you set the delegate to self. Your ViewController.m code might look like this
ViewController2 *objViewController2 = [[ViewController2 alloc]init];
objViewController2.delegate = self;
[self presentViewController:objViewController2 animated:YES completion:nil];
Hope this solves your problem
COMMUNICATION PATTERNS
Delegation is one of the communication patterns that more or less loosely coupled objects use to communicate each other. iOS Framework provides the following patterns: KVO, Notification, Delegation, Block, Target-Action.
In general, there are cases where the choice comes down to a matter of taste. However, there are many cases that are pretty clear cut.
It's also important to note that the use of each of this patterns results in a certain level of coupling among objects involved into the communication process.
Let's focus now on Delegation, Block, Target-Action.
DELEGATION
Level of coupling (proportional to the level of mutual ignorance) : loose
It allows us to customize an object’s behaviour (decoration) and to be notified about certain events (callback). In this case, the coupling is pretty loose, because the sender only knows that its delegate conforms to a certain protocol.
Since a delegate protocol can define arbitrary methods, you can model the communication exactly to your needs. You can hand over payloads in the form of method arguments, and the delegate can even respond in terms of the delegate method’s return value. Delegation is a very flexible and straightforward way to establish some sort of blind communication between two object that should be loosely coupled for design reason. Let's think to the communication mechanism between a tableview and its dataSource delegate.
Conversely, if two objects are that tightly coupled to each other that one cannot function without the other, there’s no need to define a delegate protocol (use composition instead). In these cases, the objects can know of the other’s type and talk to each other directly. Two modern examples of this are UICollectionViewLayout and NSURLSessionConfiguration.
TARGET-ACTION
Level of coupling : very loose
Target-Action is the typical pattern used to send messages in response to user-interface events. Both UIControl on iOS and NSControl/NSCell on the Mac have support for this pattern. Target-Action establishes a very loose coupling between the sender and the recipient of the message. The recipient of the message doesn’t know about the sender, and even the sender doesn’t have to know up front what the recipient will be. In case the target is nil, the action will goes up the responder chain until it finds an object that responds to it. On iOS, each control can even be associated with multiple target-action pairs.
A limitation of target-action-based communication is that the messages sent cannot carry any custom payloads. On the Mac action methods always receive the sender as first argument. On iOS they optionally receive the sender and the event that triggered the action as arguments. But beyond that, there is no way to have a control send other objects with the action message.
BLOCK
Blocks are usually used to pass to an object a behaviour to be executed before its lifecycle end. Besides, they can also substitute delegates with a caveat relevant to the potential creation of retain cycle.
self.tableView.didSelectRowAtIndexPath = ^(NSIndexPath *indexPath) {
...
[self.tableView reloadData];
...
}
In this case the sender retain the table view whose selection block retain the sender so we'd better use delegation pattern.
An example in which block communication shines:
self.operationQueue = [[NSOperationQueue alloc] init]
Operation *operation = [[Operation alloc] init];
operation.completionBlock = ^{
[self finishedOperation]
}
[operationQueue addOperation:operation];
There's a retain cycle in the above code as well, but once the queue removes the operation, the retain cycle is broken.
Blocks are a very good fit if a message we call has to send back a one-off response that is specific to this method call, because then we can break potential retain cycles. Additionally, if it helps readability to have the code processing the message together with the message call, it’s hard to argue against the use of blocks. Along these lines, a very common use case of blocks are completion handlers, error handlers, and the like.
A CHART HELPING US TO MAKE THE RIGHT CHOICE
source: objc.io
In your specific case, I'd use the target-action communication pattern to dismiss the presented modal view controller.
For example,
ModalViewController *modalViewController = [[ModalViewController alloc] init];
[self presentViewController:modalViewController animated:YES completion:^{
[modalViewController.closeButton addTarget:self action:#selector(dismissModalViewControllerAnimated:)
forControlEvents:UIControlEventTouchUpInside];
}];

[CustomViewController respondsToSelector:]: message sent to deallocated instance

This is used to work fine for my pre-ARC code, but since refactoring all the project to be ARC compatible, I start getting this crash:
[CustomViewController respondsToSelector:]: message sent to deallocated instance
My project is an iPad app with a split view, but contrary to apple documentation, previous developer has put another view controller to be visible on app launch before split view. So I know this is not the right way to do, but as I said it used to work before ARC integration so I need to get a workaround with this.
The root view controller which contain a menu of items, each item display a detail form to be filled, then a click on next button move to the next detail screen, etc.
The issue starts when I click on home button put on root view to get back to the home view, here is the relevant code that move the user to the home screen:
//this method is in the appdelegate, and it gets called when clikc on home button located on the root view
- (void) showHome
{
homeController.delegate = self;
self.window.rootViewController = homeController;
[self.window makeKeyAndVisible];
}
Then when I click on a button to get back to the split view (where are the root/details view), the app crashes with the above description. I profiled the app with instruments and the line of code responsible of that is located in the RootViewController, in the didSelectRowAtIndexPath method, here is the relevant code:
if(indexPath.row == MenuCustomViewController){
self.customVC=[[CustomViewController alloc] initWithNibName:#"CustomVC"
bundle:nil];
[viewControllerArray addObject:self.customVC];
self.appDelegate.splitViewController.delegate = self.customVC;
}
customVC is a strong property, I tried to allocate directly and assign to the instance variable but that didn't help to fix the crash. Any thoughts ?
EDIT:
Here is the stack trace given by instruments:
[self.appDelegate.splitViewController setViewControllers:viewControllerArray];//this line caused the crash
[viewControllerArray addObject:self.appDescVC];//this statement is called before the above one
self.custinfoVC=[[CustInfoViewController alloc] initWithNibName:#"CustInfo" bundle:nil];//this statement is called before the above one
self.appDelegate.splitViewController.delegate = self.appDescVC;//this statement is called before the above one
custinfoVC and appDescVC are strong properties.
I solved this problem by setting my delegates and datasources to nil in the dealloc method. Not sure if it'll help you but its worth a try.
- (void)dealloc
{
homeController.delegate = nil;
//if you have any table views these would also need to be set to nil
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
}
You may want to setup the CustomViewController during app launch, and display the other views modally on top if necessary. The error message you're getting is because something is getting released by ARC prematurely. It might have not manifested before because pre-arc stuff wasn't always deallocated immediately. ARC is pretty serious about releasing stuff when it leaves scope
Hard to tell without seeing a lot more of the code involved, and what line it breaks on, etc.
This:
- (void) showHome {
//THIS: where is homeController allocated?
homeController.delegate = self;
self.window.rootViewController = homeController;
[self.window makeKeyAndVisible];
}
EDIT:
Add this line right above the line that causes your crash
for (id object in viewControllerArray) {
NSLog(#"Object: %#",object);
}
I had the same Problem.If you are not using "dealloc" method then use "viewWillDisappear" to set nil.
It was difficult to find which delegate cause issue, because it does not indicate any line or code statement for my App So I have try some way to identify delegate, Maybe it becomes helpful to you.
1.Open xib file and from file's owner, Select "show the connections inspector" right hand side menu. Delegates are listed, set them to nil which are suspected.
(Same as my case)Property Object like Textfield can create issue, So set its delegate to nil.
-(void) viewWillDisappear:(BOOL) animated{
[super viewWillDisappear:animated];
if ([self isMovingFromParentViewController]){
self.countryTextField.delegate = nil;
self.stateTextField.delegate = nil;
}
}

UITableView calling wrong DataSource

I created a Master-Detail-Application, which uses one DetailViewController and multiple TableViewDataSources. Every time the user touches an item, i check the items class and choose the right TableSource for it.
Just like this:
if ([_detailItem isKindOfClass: [cAdress class]]) {
self.dataSource = [[AddressDetailTableSource alloc] init];
((AddressDetailTableSource *) dataSource).current = _detailItem;
} else if ([_detailItem isKindOfClass: [cActivities class]]) {
self.dataSource = [[ActivityDetailTableSource alloc] init];
((ActivityDetailTableSource *) dataSource).current = _detailItem;
}...
Sometimes i go more into Detail and push a new DetailView above the current DetailView. I do this a lot with some different views. Choosing an item in the MasterView causes, that the application goes back to the first DetailView (popToRootViewController).
I now have a problem with one view in particular. When this view is on Top and i choose an item in the MasterView, my App crashes. With NSZombies i found out, that it still tries to build the table with the wrong DataSource. Or at least it tries to call "titleForHeaderInSection" on the wrong DataSource. The error message is:
[ItemDetailTableSource tableView:titleForHeaderInSection:]:message sent to deallocated instance...
The error only occurs with this specific TableSource, also i treat same all the same.
Can anyone help me to get rid of this problem?
Any help is appreciated!
I think your app is trying to access datasource using deallocated instance, you better have individual classes for each tableView, it will simplify your work, always try to modulize the classes instead of trying to put everything in just one class.

Modal view does not redisplay?

I'm developing an app that needs to pick up a JPEG from a web site on start-up.
The splash screen displays, then the app attempts to get a web address from a file. If the file is missing I open a modal view (as UIModalPresentationFormSheet) that has a text view for the user to type in an address - the address is then saved to a file.
The user taps the OK button, and an attempt is made to get the JPEG. If the address was wrong, or the JPEG is not on the web server, the modal dialog must re-open so the user can change the web address to the correct one.
The splash screen view controller contains these methods:
- (void)openAddressDialog
{
serverView *viewController = [[serverView alloc]init];
[viewController setServerAddress:[businessLogic serverAddress]];
[viewController setDelegate:self];
[viewController setModalPresentationStyle:UIModalPresentationFormSheet];
[self presentModalViewController:viewController animated:YES];
}
Interestingly, when I called the openAddressDialog method from the viewDidLoad method the modal view did not appear. I had to move it to the viewDidAppear method. So presumably the view has to be in a particular state before it will entertain modal views.
- (void)closeDialog:(UIViewController *)dialogController:(Boolean)actionRequired
{
// If action required, get the server address from the dialog
if (actionRequired)
{
serverView *viewController = (serverView *)dialogController;
NSString *address = [[viewController serverAddress]copy];
[businessLogic setServerAddress:address];
[self dismissModalViewControllerAnimated:YES];
if (![logoImage image])
{
[logoImage setImage:[businessLogic eventLogo]];
if (![logoImage image])
{
[self openAddressDialog];
}
}
}
else
{
exit(0);
}
}
This is the delegate method called back from the modal view when the user has touched OK or Cancel. The actionRequired param indicates that OK was tapped. And if so, the new server address is picked up from the modal view, and the modal view is dismissed. An attempt is made to get the JPEG from the new address (in a business rules class), and if still no file can be found, the first method shown above (openAddressDialog) is called again so the user can correct the address again.
The modal view appears fine the first time, but will not reappear if the user entered the wrong address. Does this have something to do with me attempting to represent the modal view so quickly after dismissing it?
I'm quite new to iPad development, so would appreciate any advice.
One other thing, which demonstrates my inexperience of C++ perhaps, is ... if I declare a private method in the m file, let's call it
- (void) methodB
and that method calls another private method, let's call it
- (void) methodA
methodA must be defined earlier in the m file than methodB. If I also want methodA to call methodB I reach an impasse. The only way around that I am aware of is to declare methodB in the h file - which makes it public. How do I code this scenario so the outside world can see neither of the methods?
if use to create nib to serverView then do like this
serverView *viewController = [[serverView alloc]initWithNibName:#"serverView" bundle:nil];

Resources