Safely call method of later UIViewController from GCD of object init - iphone

Thanks to Lock a NSMutableArray of an object but not the rest of my object when using GCD I have a user object that populates an array of locations via gcd at init in the hope that by the time we modally get to LocationViewController the locations are present in user.locations (where user is passed along the viewcontrollers), if they aren't there then I simply display 'loading...' in the picker.
I would ideally like to force a refresh from within the gcd of user in the dispatch_sync method. I can added a method to LocationViewController that will refresh the picker etc but I'm not sure how to then safely access this.
My first thought was in LocationViewController if the locations aren't there (i.e. nil) then to set a reference to this LocationViewController in user. Within the dispatch_sync I could then call the method [locCont mymethod] if locCont isnt nil. But I'm not sure how to set up the property in the user class?
#property (strong, nonatomic) LocationsViewController * locCont;
What worries me is a user can at anypoint logout and return to the root view. I'll then set
user.locCont = nil
will ARC sort any hungover memory nicely?
My other concern is what will happen if they don't choose to set a location and are on a later view. I guess I could handle that by setting user.locCont to nil in a prepareforsegue.
Is there a better way to get the LocationsViewController to refresh if the user has got there?

I would suggest to use notifications to avoid a strong connection between the "User" object and a view controller.
The LocationViewController would register for a custom notification in its init method
and unregister in dealloc.
The GCD code "posts" the notification when it is finished. That causes all listeners
to be notified, so that the LocationViewController can refresh its view.
See the NSNotificationCenter documentation, and
Send and receive messages through NSNotificationCenter in Objective-C? for good sample code that should help for a start.

If you have retrieval of data happening asynchronously by some user object, and you want view controllers to get access to this object, you would generally maintain some external reference to this user object. One common approach would be to make this user object a singleton. Another would be to make it a property of the app delegate. Either way, the user object probably would not maintain a reference to any view controllers. The user object would just be in the business of retrieving the information asynchronously, view controllers could access the current state of the user object through either the singleton or the app delegate.
If you want the user object to be able to inform view controllers when the data retrieval is done, you'd probably have it post a notification via the notification center. View controllers could register themselves to observe those notifications (and, before a view controller dismisses itself, remove itself as an observer of that notification).

Related

Objective-C object dealloc'd while other objects still has a delegate reference to it causes crashes. How to prevent this?

I have an app with a navigation controller as the root view. There are many views that can be pushed in.
The user has to create an account to use the app. The user can then log into this account from other devices, but only one device can be logged onto the same account at a time. So if multiple devices try to log into an account, only the latest device will be logged in and the other devices are logged off (a push is sent to the devices).
Since there are multiple views that the device could be showing before it was logged off, I call popToRootViewControllerAnimated: to get back to the root view. This is because when the user logs in the next time I only want the root view to be shown (the new account might not have access to the previously shown view).
If the user has an alert view or action sheet presented (which uses the current view as its delegate) before the push is received, the view will still be shown after the popToRootViewControllerAnimate: method is called. If the user then taps on a button for the alert view or action sheet, it will send a message to the dealloc'd view and crash the app.
An example:
myViewController is being shown to the user.
myViewController create an action sheet prompting the user for a decision.
The push is received for the device to log out.
The navigation controller pops all the views controllers and now shows myRootViewController.
Since the view controllers are popped, myViewController is now dealloc'd.
The action sheet from myViewController is still shown.
When the user selects an option form the action sheet, a message is sent to myViewController, and since it is already dealloc'd, a crash will occur.
Is there any way to prevent this?
One solution I have considered would be to keep track of all the objects that uses a specific view controller as its delegate. Then when that view controller dealloc's it will also set all the object's delegates to nil. This requires me to manually take care of every view controller when they create an object that uses itself as the delegate, since I cannot think of a way to automatically create and update this list.
Any better solution (or improvement to mine) would be appreciated!
Edit: The alert view and action sheet are only examples of some objects that I would use myViewController as a delegate. I am also using a number of other classes (and third-party libraries) that implements this delegate pattern.
A few ideas:
you can encapsulate the alert/action sheet view and delegate in a single class. Then when you need an alert view, create MyAlertView instead, which will also be its own delegate and will do [self release] after the user taps a button.
make your App Delegate the only delegate for all your alert views and action sheets. App Delegate is always around while the application is running, so there won't be a problem with a released delegate.
The problem with both solutions is that if you need your application to know what happened in the alert view/action sheet, you somehow need to tell the interested class of the user's choice.
You can do that by either using delegates of your own - which would mean you're back to square one - or use notifications: when the alert view/action sheet delegate is called, it would post a notification ([[NSNotificationCenter defaultCenter] postNotificationName:NotificationName object:self userInfo:userInfo];), while the interested object would look for that notification ([[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onNotification:) name:NotificationName object:nil];) and perform whatever tasks necessary in onNotification:(NSNotification*)aNotification method.
You'll be able to agree with yourself on what type of information is passed in those notifications (I would think the button number in a NSNumber class would be enough, or perhaps pass the button text, too). And you won't have to keep track of all alert views - just don't forget to remove the observer ([[NSNotificationCenter defaultCenter] removeObserver:self name:postNotificationName object:nil];) in the views' dealloc.
Edit:
"This requires me to manually take care of every view controller when they create an object that uses itself as the delegate, since I cannot think of a way to automatically create and update this list."
Actually you probably can do this in a semi-automated way: make a singleton object with a method like
-(id)delegate:(id)delegate for:(id)forWhom
And then instead of
someThingy.delegate = self;
you'd do
someThingy.delegate = [[DelegateLocker defaultLocker] delegate:self for:someThingy];
Inside the DelegateLocker you'd have a MutableDictionary with delegate class as a key and a MutableArray of someThingies as a value. Then in your view controllers' deallocs you'd call
[[DelegateLocker defaultLocker] delegateIsDying:self];
which would go through the thingies and assign delegate = nil for each
The drawback of course is that you'll be retaining all the thingies for an indefinite period of time instead of releasing them immediately.
So the ViewController that presented the action sheet iand set itself as the delegate right? So why dont you keep a reference to the ActionSheet in the ViewController, in the dealloc method of the view controller, you can check if the action sheet is visible, if it is then set the delegate of the action sheet to nil,and dismiss it...
so
-(void)dealloc
{
if(myActionSheet && [myActionSheet visible])
{
[myActionSheet setDelegate: nil];
//dismiss
}
}
Hope that helps
If you want automated solution, I think you can make a function to iterate through Ivars of your view controller to see if any Ivar has delegate property and set it to nil.

best way to use CoreLocation across multiple views

I have two views in my app, one is a general view where CoreLocation works away calculating the users location while the user is doing other stuff in the view. The second view is accessed by the user when they touch a button allowing them to locate themselves more accurately using a mapview and MapKit, i would like the mapview in this view to show the location that CoreLocation has already identified in the first view AND to continue displaying this location based on updates from CoreLocation in the other view.
Is the best way here to create a singleton that encapsulates the CoreLocation stuff and have this referenced in the view with the map, or to use notifications ? or to use some other better practice for my scenario ?
Thanks
I have a couple of apps that use CoreLocation in multiple places. From what I've read, you definitely want there to be just one instance of CLLocationManager. It's worked great as a singleton for me.
Hope this helps!
If I were you, I would do it this way:
Decide which view is going to be always loaded.
I assume, you want CalculatingView is loaded all the time, and MapView will be loaded/unloaded based on the user action.
Allocate and initialize a pointer to CLLocationManager inside CalculatingView. This will provide location property and also call delegate messages. Since the CalculatingView is loaded and retained, this pointer is always working too.
Set CLLocationManager's delegate to be CalculatingView, which might also be called self, if this view has allocated and initialized CLLocationManager pointer.
Implement delegate methods of CLLocationManager, in CalculatingView
If you like to, you can have MapView to be allocated and initialized within CalculatingView. But it's ok to have it in other places, as long as you can send message to MapView. Make sure they are valid by checking if it's not nil or if it respondsToSelector.
When the CLLocationManager's delegate, which is CalculatingView receives messages, send a message to MapView.
It's like relaying messages, but the messages that MapView should respond to don't have to be the same messages sent to CalculatingView like delegate method calls from CLLocationManager
By checking if MapView is valid, meaning if it's loaded to be displayed, you can decide to send messages to MapView or not
The essence is to decide which view is loaded consitently, to use delegate methods for sending(or relaying) messages to other pointers(in this cases, MapView pointer).
The singleton is good, but unless you are going to use CLLocationManager from multiple places, like more than 3~4 places, it's not that necessary, I think
Hope I didn't confuse you. Based on what you posted, it seems like this way can be simple solution for your goal. If I didn't catch your true intention, please let me know.
I am not sure this is the best way, but I've been setting up my main controller (the one that is loaded first) as a location manager delegate. When the location updates it fires off a notification with the new location as the notification object. Any controllers listening can then use that data however they need it.
As an aside, Apple's LocateMe app instantiates the location manager three times. So, by their example, having multiple LocationManagers might not be a problem.
From what I've read, best practice for this is to add CLLocationManager to your App Delegate as you can access it from any view.
Short sample code to put in your view where you need the CLLocationManager
....imports....
#implementation YourViewController
- (void)viewDidLoad {
self.myLocationManager = [[UIApplication sharedApplication] delegate].yourLocationManagerVarName;
}
#end
Hop that helps.
Maybe you should consider a MVC oriented approach. From your description your are missing a model layer representation of your user. Defining a simple User class with a basic CLLocation property would be a first step.
#interface User {}
#property (nonatomic, retain) CLLocation *location;
#end
#implementation User
#synthesize location;
- (void)dealloc {
self.location = nil;
[super dealloc];
}
#end
The same instance of the User will be passed to your view controller. It may be created in the app delegate.
Next create location services object for your app. It will start the CLLocationManager, and give the location to your user. You may have to set the GPS accuracy, ignore frames you don't want, and implement basic LBS logic here.
At this point, you have a feature full app, without any UI. This is a good design in the way it can be reused and tested.
Now stack your UI on top of that. Give your root controller a pointer to the User instance in your app delegate. Your view controller pass this pointer to modals / navigations view controllers it creates.
This controller start observing User's location changes in their viewDidLoad and react accordingly.
- (void)viewDidLoad {
[self observeValueForKeyPath:#"location" ofObject:self.user change:0 context:NULL];
}
Your view controller would also register for notification raised by your location services objects to display an alert to the user.
Based on other answers:
there is no real penalty to create multiple CLLocationManager instances in your code. The only side effect is that the api is asynchronous, thus you have to wait to get a valid location in your view controller. You can try to get the current location from the location manager on your viewDidLoad using locationManager.location API.
don't share stuff from your app delegate. This prevent code reuse. What if you reuse your views and you app delegate don't have a location manager ?
if you need more code, please ask.

Change the delegate of MGTwitterEngine

I have setup and successfully logged in via xAuth using an extended class of MGTwitterEngine, my question is if I want to pass this to another view controller, how can I change the delegate class, as it is some sort of weak reference
#interface MGTwitterEngine : NSObject <MGTwitterParserDelegate> {
__weak NSObject <MGTwitterEngineDelegate> *_delegate;
Am I best wrap this up into a singleton class and pass around that way, seems overkill to login in each time, or have I missed a painstakingly obvious way of sharing this object around
At the moment I have added a setDelegate method to the MGTwitterEngine but feel as though I am fighting the framework unnecessarily
If you're sharing the engine across multiple objects then you would want to have some other object/singleton wrap the engine and act as its sole delegate. If you've done database programming then think of it like a database connection -- you probably wouldn't have each view controller create its own database connection. Instead you'd create some sort of data manager object that is shared by the views and possibly abstracts away some of the DB internals.
If different view controllers handle different tasks -- like login, looking up users, querying messages, etc. then the delegate methods in your wrapper should be able to pass the responses along to the appropriate view controller.
If you have different view controllers calling the same methods (and if so, why?), you could still route responses back to the corresponding view controllers. As the MGTwitterEngine docs say, "Each Twitter API method returns an NSString which is a unique identifier for that connection." You would just need to pass an object (your view controller) or a block as an extra parameter to each of your wrapped methods. You can cache the twitter id string and this object/block in a mutable dictionary when your wrapper sends the response, then look up the connection id in the cache when it's time to handle the response.
actually, you can.
The delegate, is nothing but a variable in the MGTwitterEngine. Just add a instance of it in the next view controller adding the proper header and inplementation calls.
after instatiating the new view controller set:
nextViewController._mgTwitterEngine = self.mgTwitterEngine;
nextViewController.mgTwitterEngine.delegate=nextViewController;
then call the nextViewController.
Do not forget to set the delegate back to the original view controller when you return to it (either on viewDidAppear or viewWillAppear)
Hope that helps...
Best Of luck!
Use NSNotifications in the delegate.
Make the view controller where you wish the delegate to be add an observer. Have the delegate method for MGTwitterEngine post the notification.

UIViewController is popped from view stack and NSURLConnection crashes the application

I am pushing a UIViewController onto a UINavigationController. This view controller immediately starts a download of an xml feed and then parses it. However, if you hit the back button before it is done downloading, and crashes with EXC_BAD_ACCESS. The line that is crashing it is in parserDidEndDocument and is this line:
if (self.delegate && [self.delegate conformsToProtocol:#protocol(ModelDelegate)]) [self.delegate modelDidFinishParsing:self];
I assume it is crashing because it is trying to access self.delegate which is not assigned anymore. How do I get around this?
Also, I would release the model object in the modelDidFinishParsing method. How would I release this model if it never reaches this method.
I set up objects to handle my downloads (and other asynchronous or long running tasks) in the AppDelegate, then trigger them as required from various controllers. That way they are owned and have persistence through the life of the application.
The best way to do this is to pass them to the viewControllers that will need them (rather than the viewController "expecting" the appDelegate to have such and such an object ready and waiting) - dependency injection.
These objects update my model in some way when they finish and if I need to, I use NSNotifications to announce they are done. This isolates me from the mess I used to get into trying to cancel or swap delegates in viewWillDisappear etc to avoid the kind of issues you are running into.
The reason your app is crashing is probably because NSURLConnection retains its delegate (so it can call back to it reliably) but objects that this delegate has weak references to have been deallocated.
Ie, in your case what self.delegate points to has probably been deallocated when the view controller is popped but the delegate property has not been cleared (set to nil).
The solution to your problem is to clear (nil) self.delegate at the appropriate time when the UIViewController subclass is being popped off the navigation stack.
Note: retaining delegates is not usual behaviour for Cocoa classes. In situations where it happens contrary to standard practice it is documented (see the NSURLConnection docs).

iPhone Delegates - How to use them correctly

I have 2 or 3 views in my iPhone application where I have various pieces of functionality that use delegates. In all cases the delegates are assigned to "self", responding specifically to that view's actions and interacting with instance variables.
However, if I do something that takes a bit of time with a delegate, and leave the view, obviously it crashes my app as the delegate methods get called on a view I've left.
Typically in my delegate methods I am doing things like interacting with IBOutlets, calling other instance methods, saving data to Core Data etc...
How can I work with delegates better? Is what I'm doing typical, or not?
Thanks for any guidance!
Depends on the use case. If, for example, you've got a UINavigationController that manages a ViewController that use something such as Location Services, when you pop the View Controller off of the stack you're going to want to set the CLLocationManager's delegate to nil. You can do this in the dealloc method.
Can you give a specific example of an issue you're facing?
I've encountered this situation once when dealing with MapKit (race conditions involving delegate callbacks and delegate deallocation). However, in general, I think it's an indication of a bad design decision when your delegate becomes invalidated as a result of race conditions, but I could be wrong.
Typically, the objects that make use of your delegate should exist within the context of the delegate itself. So, for example, the same class that contains various IBOutlets that you want to manage with delegate callbacks should also be the delegate of those IBOutlets. That way, when the class (i.e., the delegate) is deallocated, the IBOutlets are (hopefully) also deallocated, so they won't be making callbacks to anything.
bpapa's right. More generally, if you have a potentially lengthy delegate callback, either make sure that 1) the delegate is an object that won't be deallocated during the lifecycle of the delegator (e.g., UINavigationController managing UIViewControllers) or 2) the delegator's delegate object is set to nil during the delegate's deallocation.
... That last sentence was a mouthful. :)
Your delegates should be declared as
#property (nonatomic, assign) id <MyDelegateProtocol> delegate;
This ensures a weak reference so that when you dealloc the object which has delegate, it only removes the reference and NOT the corresponding object. using (strong) will cause a crash on dealloc.
When calling your delegate, you can check
if (self.delegate && [self.delegate respondsToSelector:#selector(MyDelegateProtocolCallbackMethodName:)] {
[self.delegate MyDelegateProtocolCallbackMethodName:result];]
}
Generally I use delegates for proxy classes which fetch data from a server or edit screens like changing the title of a task in a todo list or editing a model in a database.
The advantage of a delegate is a very clear use case. If you find that multiple parts of your app need to be aware of when an event happens (triggered on 1 screen but perhaps listened for in another), I suggest using NSNotificationCenter to raise notifications, instead of or perhaps in addition to sending a message to your delegate.