Context:
My app needs the user's current location in several unrelated view controllers and, as of now, I'm creating a CLLocationManager in each of these controllers and requesting the current location over and over.
However, it doesn't look right to me. It's duplicated code and inneficient.
I've seen several references on the internet mentioning a singleton location manager. And I also heard to avoid singletons as much as possible.
Question:
Should I create a singleton class that manages the location? If not, what other solution would you suggest to avoid this duplicated code?
And I also heard to avoid singletons as much as possible
The above statement is correct in case that some dealoocated cycle of the app run needs that data so after it finishes the singleton becomes a problem from memory perspective as it stays alive all the app life cycle , in your case ( all vcs inside the app needs the location ) is the best fit for a singleton to avoid the duplication - less-efficient code and high memory issues
Needless to say 90% of apple classes uses singletons e.x
URLSession.shared
NSNotificationCenter.default
UNUserNotificationCenter.current
UserDefaults.standard
FileManager.default
There is no hard and fast rule. Apple is quite clear that it's fine to instantiate more than one location manager. However, if you do that, you might confuse yourself, because each will need a delegate, each will need its own settings, and so forth. Thus, you are right to be wary of that approach.
It is a standard strategy to pick an instance that persists throughout the life of your app — your app delegate, or your root view controller, for example — and initialize an instance property with a location manager. There is no reason whatever to add the Singleton pattern to the mix; you simply make one once and keep hold of it.
Patterns are not evil. It depends on the user. They are solutions for a specific problems. It depends on how you see your problem and what you want to achieve.
In your case, you need a location manager instance through out your app and you use it in multiple places, so you need a wrapper manager. If you only need one configuration, then it makes sense to use singleton.
Apple is recommending to have a strong reference of the manager as long as you need it. CLLocationManager - Apple Documentation
Create an instance of the CLLocationManager class and store a strong reference to it somewhere in your app.
Keeping a strong reference to the location manager object is required until all tasks involving that object are complete. Because most location manager tasks run asynchronously, storing your location manager in a local variable is insufficient.
If you create a singleton location manager it has to do things differently than other singletons. You can't use the normal delegate pattern for it to inform other objects about the location updates and errors because the normal delegate pattern is a one-to-one relationship. You have to use multiple delegates (the singleton has an array of interested objects and it sends the same message, e.g. location update, to each one). Here's another question about why that's difficult to use:
Multiple Delegates in iOS
You can get around that with notifications but personally I think that's a bad pattern (it decouples things too much and makes it hard to follow paths of responsibility).
The singleton also has to keep track of whether any of its interested objects asked it to stop or start. If they've all asked it to stop then it should power down the updates. If just one wants updates it has to power them back up. Search for all the people building frameworks just to do the same task with the network indicator to see how much trouble this is.
If there's an error with requesting location you have to save that error and when (some later time) an object wants location, you have to retransmit the error. So really you want to have all interested objects connected and listening from the start to avoid that scenario.
I'm sure I could think of more hairy cases that you'd have to deal with.
The in-between option is to create a location manager class with your special setup, error checking and so on, and instantiate one of those whenever you need it. Use the delegate pattern to get messages from it (most will just be passing the messages along directly).
Related
I have some complex networking in my app( I don't use any third party dependencies, because of project requirements). For instance, I send three network requests in parallel after first two requests provide results. All my networking is done in separate models, known as networkClients(following MVC-S pattern) and are called directly from repository, not from ViewControllers. However, I need the last request to notify my viewController after I get response from network. How should I do that? I don't think notification center would be right solution because it can cause memory leaks and I have not found correct approach to complex problem like this. Please provide some prominent solutions. It should conform to good design pattern like MVVM or MVC and should not be some workaround or hack. Maybe delegates would work? I know that rxSwift would solve my issue, because I could start observing for results after initializing viewController and after data would be updated from repository my viewController would also be notified...
The right design doesn't have VCs observing the network clients directly. Those network operations should be assembling parts of a model, which is what the VC really cares about. Have the VC observe that singular model.
It can do this observing using one of the well known patterns for loosely coupled communication between objects. The OP correctly mentions delegates. Notification center and KVO are others. There's plenty of discussion on SO about which to use and how to implement. (I'd go with NSNotificationCenter as an easy and rational start).
So the order of operation is like this:
allocate the model
launch the network requests and setup those request completions (completion blocks, probably) to update that model with their responses. (the model can launch the requests, which is a reasonable practice).
create the view controller(s) that setup model observation when they initialize (probably, in viewWillAppear or later)
What about the fact that >1 requests are in flight simultaneously? A commenter above points out correctly that GCD offers a way to group those async operations into a single one. But you can do this yourself straight-forwardly: the model decides when it's completely built. The completion code for each request will change some condition in the model to the "ready" state. Each request completion can check to see whether all of the ready conditions are met, and only then post a "ready" notification for observers to see.
Another niggling issue: what if those requests all run very, very fast? Maybe there's some cached response that's ready early, making the model "ready" before the VC has had a chance to setup observation? Handle this straight-forwardly in the VC: before observing the model, check to see if it's ready already and run the same update code that runs on the notification.
I'm not sure exactly how to correctly construct my question. It is actually more of a "what is the advised practice" instead of an "how to" type of question. So I'll try to explain my main goal first.
I've started writing a small app which allows users to fill out forms and send them to a server. But the connectivity is a real problem. Often there are times that these filled out forms must be saved on the device until wi-fi is available again.
The app has multiple view controllers, each with different jobs as you can imagine. I am thinking of writing a class which will be initialized during the app launch and will continue to run through out the apps whole life cycle. This class will periodically check for an internet connection and when available, upload saved forms to the server and than remove them from local core data database.
I've read about the background tasks, singletons, app delegate functions, global functions etc. And honestly I am confused. Every article and/or post I've read criticizes the other method with no one noting the correct way of doing this. "Globals are bad", "singletons are evil", "don't use app delegate" etc... I'm not sure what is the advised practice to achieve this.
Can we please discuss what is the preferred way to write a "self contained, periodic check and upload function" which is independent from any viewcontroller and will run in the background during apps whole life cycle.
I'll like to know why Apple allow to create more than one instance of HMHomeManager and what is the purpose of it?
I would expect the instance of HMHomeManager to be a singleton.
I can't speak for Apple but I see no cases where HMHomeManager would benefit from being a singleton and several clear disadvantages.
HMHomeManager has a delegate property. The delegate pattern works well when you want a delegator to send messages to a single delegate or when many delegators might share a delegate. It is however not useful when one delegator might produce messages of interest to many delegates which would be the case if HMHomeManager were a singleton.
Singletons are not easily deallocated. Any app using a HMHomeManager as a singleton would keep that object in memory monitoring for changes to the home database even if it was no longer needed.
The assumption that there should only ever be one HMHomeManager may not hold true forever. While one HMHomeManager can contain many homes they all share a common user. Designing this class as a singleton would preclude an app from acting on behalf of multiple users at once. Even if there's never a need for such behavior developers might be wise to avoid selecting an interface which cannot support it early in the design process.
Don't create multiple instances of HMHomeManager.May be below code will be helpful. If you want to made any changes to the existing home try to execute the code after made the changes like add room,zone...
for(HMHome *home in appDelegate.homeManager.homes)
{
if([home.name isEqualToString:appDel.selectedHome.name])
{
appDel.selectedHome = home;
}
}
I would expect internally there is a singleton client that communicates with the home daemon, the same way CLLocationManager has a singleton CLLocationInternalClient.sharedServiceClient which allows us to scatter location managers throughout our app without having to worry about forwarding notifications or multicasting delegate methods around ourselves. I haven't examined HMHomeManager as closely and it does seem quite a rushed API so maybe not as well designed. It's concerning the documentation mentions "the manager for your app" as if we are only supposed to have one. Furthermore when you receive the home changed delegate call it tells us to "invalidate your own app's objects that cache the home's data". One thing I have noticed is when you init a HMHomeManager it inits all the rooms, services and accessory objects which in my case is about 400KB (calculated by initing 10 home managers and comparing the RAM difference) for my 301 characteristics, 68 services, 19 accessories home (I used Allocations in Instruments searching for 'HM' to learn this). So we probably should stick to one manager per app but there is a serious design problem with the API that when we toggle a characteristic the delegate method isn't called so we need to reach out to our app's object and invalidate it rather than it happening automatically for us like any other API would.
This is tagged with iOS, but I'm sure it could be useful for the other Parse SDKs as well. As you may know, Parse added the ability to create native PFObject subclasses to the iOS SDK not too long ago. This is a great addition for a number of reasons. Firstly, it allows compiler to check your code by creating dynamic properties for object attributes:
myObject[#"myAttribute"] is converted to myObject.myAttribute
Secondly, and more important to this question, custom subclasses can have added functionality. For example, say I have created an alarm app that stores Alarm objects on the Parse cloud. In my custom subclass, I can override the + (instancetype)object, - (void)saveEventually, and - (void)deleteEventually methods so that the alarm object can schedule/update/remove a UILocalNotification for itself upon creation, modification, or deletion.
Here's where things get complicated and my actual question comes in. Say a user creates an alarm on one device (which uploads it to the cloud), and then syncs it automatically to another device. The second device obviously updates it's contents in the background with PFQuery's - (BFTask *)findObjectsInBackground and then calls - (BFTask *)fetchIfNecessaryInBackground on each object to ensure that all of its substance is on the device. My question is: What method(s), if any, gets called when a PFObject subclass is found/fetched from the Parse cloud database? For that matter, what about objects initialized from the local datastore?
Like I mentioned, overriding various methods works perfectly for objects that are created and managed on the device, but I am baffled as to how one would run custom code from within a new object that just arrived in memory from the local or remote datastore. Any thoughts or suggestions on how to handle this would be much appreciated. The Parse documentation does not cover such a case, so it may not even be best practice, but it seems to me that it should be. Anyway, thank you for your time and your insights.
As for most subclasses of NSObject, the way to go is probably to override the -init method.
However, as you mentioned in your last paragraph, such practice is undocumented and you should probably avoid it. The way PFObjects work makes it possible to have multiple instances of the same object in memory (multiple PFObjects with the same objectId). And you do not control when or why these objects are created, so relying on code executed when they are initialized is probably a bad idea. If you have been using Core Data, note that Parse really handles things in a different way, so the best practices are different.
For example, I'm not saying this is the case, but what if a copy of each object is created before it is saved ? Of what if the object is created twice when making a query with the 'cache then network' policy ? Even if you make it work, you would still end up with something that could break with every update of the Framework.
I think you should bundle you initialization code in a method of your own that you would call yourself on objects when you receive them from a query or from the local datastore. Overriding is a good design and practice in object oriented programming, but there are some exceptions and I think this is one of them.
I'm starting to try and add support for state preservation and restoration to my iOS app, which has a Core Data component to it that I access via a UIManagedDocument.
I'm starting to add the restoration identifiers to my view controllers, and have hooked up the required functions (currently empty) within my AppDelegate, and controllers.
I have an object that could potentially be referenced by multiple view controllers so I plan to try and preserve and restore this within my AppDelegate and just have the relevant view controllers retrieve the object from the AppDelegate. Timing of this could be tricky as the app delegate method didRecodeRestorableState occurs after all the views have already called their own decodeRestorableStateWithCoder methods.
My main problem though is that this shared class as well as multiple ViewControllers all want to have NSManagedObject properties preserved and restored. I hope to be able to use the object's URIRepresentation to facilitate this but the problem I have is my AppDelegate will open my UIManagedDocument within my AppDelegate's willFinishLaunchingWithOptions method. It does this via the UIManagedDocument openWithCompletionHandler method. Due to the threading of this opening the document is successfully opened after all my views and app delegate have already tried to restore their saved state. The AppDelegate does send a notification out once the document is ready for use, so all my view controllers can listen to this notification.
I guess I just wonder is this the best, or even only strategy for dealing with this. My objects will need to hold onto the URIRepresentations that they restore and only once the document (and it's NSManagedObjectContext) is ready try to actually find and set the corresponding NSManagedObjects up that they saved out. As such the restoring is happening a lot later than the calls to perform the restoring would I assume usually perform all their restoring work. I worry whether a controller may potentially appear empty for a brief period of time whilst it waits for the document to open and then get properly initialised.
Is there any purpose in blocking and delaying the opening of my document in this case so yes the app takes longer to open but can at least restore more correctly with all the data required before any views appear. Are there any timers being ran to make sure certain methods don't take too long? Would it be more correct to show a different view whilst we're in this limbo state, not quite sure how to go about this but its the sort of thing you may see with other apps like say the Facebook app which is dependant on a network connection.
I can't seem to find any real explanation of this sort of issue within the documentation so far.
Any help is as always very much appreciated! Cheers
In the end I just implemented notifications from when my UIManagedDocument had finished loading. These were picked up by all controllers that had coredata managed objects it wanted to restore. During restoration I keep hold of the encoded URIs, and later when receiving this UIManagedDocument ready notification I just decoded the URIs to their respective managed objects.
The problem with the shared object that I described I solved by encoded and restoring in one place from my appDelegate and then using another notification out to systems to tell them that this shared object was now fully decoded and available for use.
Not ideal and involved creating quite a lot of hierarchies of methods to ensure all objects were decoded correctly but it works ok.
Sadly since then I've hit a stumbling block where UIDataSourceModelAssociation protocol methods are being called by the OS before my UIManagedDocument has finished opening. Sadly this means that I'm unable to do anything useful. So what i really need to do somehow is defer my app restoration until everything is loaded from a CoreData UIManagedDocument POV. That problem continues...