I would like to use AFNetworking to perform periodical JSON requests to my server (updating the user's profile and checking for changes).
What if the background job is running but the user pushed the "Back" button or anything that makes the ViewController to be destroyed? How can I manage this? I mean, in that case I would like to ignore the result and perform it again when the user returns to the View
Thank you
PS: I don't want a full working code. I just would like to know how can I know, from a background download job (ran using AFNetworking) if the ViewController has been destroyed or not.
In order to stop the running network request, you can send cancel to the network operation in method viewWillDisappear: of the View Controller.
Likewise, in order to "automatically" start the network request when the view becomes visible, use method viewWillAppear:.
Check out this question:
Best architecture for an iOS application that makes many network requests?
A simple way is to create a singleton instance of a custom Network Manager class which will handle all the request and network background jobs. You can access it from anywhere (e.g. view controllers) and it keeps its instance for the whole runtime of the app.
About singleton: http://en.wikipedia.org/wiki/Singleton_pattern
If you just need to achieve your task . mean, trying to get the changes if & only if the particular ViewController appears.
Use the methods
-(void) viewDidappear{
// Initialise Your task here
}
-(void) viewWillDisappear{
// Destoy(Cancel ) the task
}
One solution would be to implement the downloading in a way that uses NSNotification mechanism :
Set up the ViewController as a listener.
Set up the bg download to fire up a notification when the download process has been finished.
If the VC is still around when the notification fires, great - it
will handle notification and do whatever you need. If it is not around... nothing happens.
Related
When app starts I need to do some network requests. For example, App fetches my servers public key to operate a secure restful connection. If it can get the key, It does other network operations. My problem is that these operations are nested so one is finished, other one start but If there is a problem with one of them I need to display a message. I have AppDelegate class and SplashViewController in which I can do these operations. I'm not sure what will be the best approach in terms of speed when doing that;
1-) Start operations in AppDelegate and with notification, notify splashviewcontroller and display message If there is an error.
2-)Start operations in SplashViewController class.
3-)Wait all network operations to be finished before opening SplashViewController (I'm not sure If I can display error message in AppDelegate class)
Example code I run at AppDelegate;
APIClient.checkCMS { (result) in
switch result{
case .error(let error):
print(error)
//Notify SplashViewController?
case .success(let returnedObject):
print(returnedObject)
print("Devam")
}
}
The fact that you are even thinking about using notifications here to communicate between view controllers is probably a code smell. I think the AppDelegate approach is definitely wrong.
I'm making some assumptions about your app that may be incorrect, namely that you are using a storyboard/xibs and that your SplashVC is going to be instantiated and presented when the app runs, regardless of the state of the network call (i.e. the app isn't just going to hang until the outcome of the network call is known).
In this scenario, if you make the call from the AppDelegate you will use NSNotificationCenter to update the SplashVC. This seems like a bad idea, just because it introduces unnecessary complication to the design. You might just conceivably do everything from the AppDelegate if you are loading the VCs manually in code, but even then you probably don't really want the app to show nothing until the network call completes.
The way I would handle this is as follows:
(If you haven't already done it) encapsulate all of your network requests into a service object of some kind. Then you can use Dependency Injection with a singleton scope (perhaps using Swinject or another similar library). This would allow you to make network requests from anywhere in the app they are required.
Have a default VC (maybe SplashVC, or some other root VC) that loads first and dependency injects the network service from 1
Make the calls in the viewDidLoad of the SplashVC. Handle errors appropriately, (by showing an alert, or by presenting a custom modal VC, or whatever you like. This avoids having the app just show a black screen if the request is slow).
Usually, developers create a fake splash screen. It looks like splash, so user can't notice any difference. On controllers initializer(or in viewDidLoad) you can download all needed data and when everything is done go to next controller. Even more, you can choose where to go next(for example if the user was logged in before, he should be redirected to the main page).
Background: In order to make web requests to an API endpoint, I need to scrape a website and retrieve a token every 25-30 seconds. I'm doing this with a WKWebView and injecting some custom JavaScript using WKUserScript to retrieve AJAX response headers containing the token. Please focus on the question specifically and not on this background information - I'm attempting this entirely for my own educational purposes.
Goal
I will have different 'model' classes, or even just other UIViewControllers, that may need to call the shared UIViewController to retrieve this token to make an authenticated request.
Maybe I might abstract this into one "Sdk" class. Regardless, this 'model' SDK class could be instantiated and used by any other ViewController.
More info
I would like to be able to call the UIViewController of the WKWebView and retrieve some data. Unless I re-create it every 25 seconds, I need to run it in the background or share it. I would like to be able to run a UIViewController 'in the background' and receive some information from it once WKWebView has done it's thing.
I know there are multiple ways of communicating with another ViewController including delegation and segueing. However, I'm not sure that these help me keep the view containing the WKWebView existing in the background so I can call it's ViewController and have it re-perform the scrape. Delegation may work for normal code, but what about one that must have the view existing? Would I have to re-create this WKWebView dynamically each time a different model, or view controller, were to try and get this token?
One post suggests utilising ContainerViewControllers. From this, I gather that in the 'master' ViewController (the one containing the other ones), I could place the hidden WKWebView to do it's thing and communicate to the child view controllers that way via delegation.
Another post suggests using AppDelegate and making it a shared service. I'm completely against using a Singleton as it is widely considered an anti-pattern. There must be another way, even if a little more complex, that helps me do what I want without resorting to this 'cheat'.
This post talks about communicating between multiple ViewControllers, but I can't figure out how this would be useful when something needs to stay running and executing things.
How about any other ways to do this? Run something in a background thread with a strong pointer so it doesn't get discarded? I'm using Xcode 9.2, Swift 4, and iOS 11. As I'm very new to iOS programming, any small code examples on this would be appreciated.
Unfortunately, WKWebView must be in the view hierarchy to use it. You must have added it as a sub view of an on-screen view controller.
This was fine for me. I added this off-screen so it was not visible. Hidden attribute might have worked as well. Either way you must call addSubview with it to make it work.
There are some other questions and answers here which verify this.
Here is a way if you don't wish to use a singleton.
1- In the DidFinishlaunchingWithOptions, Make a timer that runs in the background and call a method inside the app delegate Called FetchNewToken.
2- In FetchNewToken, make the call needed and retrieve the new token (you can use alamofire or any 3rd library to make the call easier for you).
Up on successfully retrieving the token, save it in NSUserDefaults under the name upToDateToken
You can access this token anywhere from the application using NSUserDefaults and it will always be up to date.
I've just implemented the CLLocationManager in my AppDelegate, which is currently printing the location whenever it's updated.
What I'm trying to do now is trigger a method in one of my view controllers whenever that location is updated.
I've done something similar before using protocol, but I'm not sure if that's possible with the AppDelegate.
I'm working in Swift, but an Objective-C response would also be helpful as I'm looking for the logic behind how to achieve this.
You can post notifications to NSNotificationCenter from your application delegate (https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/) and subscribe to in in your controller (do not forget to unsubscribe in dealloc). Just make sure you do not update any GUI when application is in background (you can check state directly in controller).
I have an app that fetches data from a server using NSURLSessionDataTask. As of right now I am starting my HTTP GET Request in the init method of the UIViewController that displays the data. Is this the best/smartest place to kick off an HTTP request? If not, where should I do it?
I'm asking this question because when I exit my app and it goes into the background (and is not killed) and then re-open my application, the HTTP request is not fired off (because it is in the UIViewControllers init method) and the data being displayed is not up to date with what's on the server. I've tried putting it in viewDidLoad but this method is not called upon entering the foreground, neither is viewWillAppear nor viewDidAppear.
Should I be doing all of my HTTP requests in one of the UIApplications life cycle methods in my appDelegate?
In short, where is the best place to make HTTP requests in iOS?
Thank you, I can post code or explain more if needed.
first off, this is a huge question and probably impossible to answer fully here, but hopefully I can point you in the right direction so you can learn how to fish. :)
To stick to the Model-View-Controller paradigm, you will want to create a separate object for making your HTTP requests. An HTTP request would be considered part of your model. The benefit of this is being able to use your model in other iOS apps you create, for example.
As for where to put all of this stuff and what's the best design.... One thing that strikes me in your question is you want the data being displayed in your app to be up to date with what is on the server. On a high level, a really good way to do this is to use iOS's ability to multitask and perform functions for you when you app is in the background. You would need to register with the OS as an app that performs fetches to a server in the background.
According to Apple's documentation, "In Xcode 5 and later, you declare the background modes your app supports from the Capabilities tab of your project settings. Enabling the Background Modes option adds the UIBackgroundModes key to your app’s Info.plist file." From there you would need to research the UIApplicationDelegate's protocol methods – application:performFetchWithCompletionHandler: and -application:handleEventsForBackgroundURLSession:completionHandler:.
Also, you will need to look into NSURLSession a little more. If you want to use background fetching, NSURLSessionDataTask is not supported. You will need to use NSURLSessionDownloadTask, save the response to a file and process it however you need to. Also, as the app delegate method name above implies, you will need to read the NSURLSessionConfiguration Class Reference, specifically about backgroundSessionConfiguration.
The really cool thing about all of this is, after you have implemented it, your app UI will be up to date for the user – even if your app was killed by the user or by the OS. Pretty nifty.
Good luck and hope this all helps. I hope I didn't miss any other big pieces here.
I think it a personal preference. So I personally do it on the model objects. Lets say I have a Car object and a ShowroomViewController. I always declare a class method to Car object to call service to get all the cars.
#interaface Car
+(void)fetchCarsWithCompletionHandler:(void (^)(NSArray* cars, NSError *error ))handler;
-(void)getDetailsWithCompletionHandler:(void (^)(Car* car, NSError *error ))handler;
#end
Then call the class method on viewWillAppear(If I need to update the cars very often) or viewDidload(If I need to call the service once).
The other trick I mostly do is define a flag in the view controller like
#interface ShowroomViewController
#property(assign)BOOL needsModelUpdate;
#end
and I update the modal conditionally.
#implementation ShowroomViewController
-(void)viewWillAppear:(BOOL)animated{
if(self.needsModelUpdate){
[self fetchModel]
}
}
-(void)fetchModel{
__block __weak ShowroomViewController *weakRef=self;
[Car fetchCarsWithCompletionHandler:^(NSArray *cars, NSError *error) {
[weakRef setCars:cars];
[weakRef.tableView reloadData];
}];
}
#end
The reason I define this flag is I can change it somewhere else lets say applicationDidEnterBackground: method the change the flag. Or you can use KVO but I always find it overkill .
Do it however you want.
Personally, I create a class specifically for all communication with the server. Actually, my App has around 20 classes for different parts of the communication process. But yours is probably less complicated.
Add an instance of the class as an object in an xib file or else create an instance of it inside the app delegate's init method.
Use didEnterForeground to tell the other class that it needs to do it's stuff, but still use the init method to create an instance of the class.
The high level design of my app consists of the App Delegate being the owner of the model which is created in didFinishLaunchingWithOption and then the app delegate passes a reference to the model to any controller class that needs to use it.
On app launch my app needs to call home to a server and download some content. This must be something common done by many apps, my question is what is the standard way of doing so, in particular which object and at at what point should be responsible for instructing the model to connect to the server? Lets assume there is a function on the model called CallHome() implemented asynchronously using NSURLConnection which can notify interested classes when complete.
Where should I perform this:
1) Could it be done in didFinishLaunchingWithOptions?
2) If didFinishLaunchingWithOptions should execute and return before the model executes CallHome() then which class should call CallHome()? Can the AppDelegate do this? If so where?
3) Could the model invoke CallHome() itself, if so when?
4) Or is this actually the job of a controller? If so should it be the root controller?
5) However what if the root controller doesn't need a reference to the model otherwise? So does that imply it should be another controller?
What is the recommended approach for the high level design for this functional requirement?
Many thanks.
you should make any server requests not before first view controller's viewDidAppear you can trigger your web service request in viewDidAppear of first view controller.
the reason behind the scene is if you web service call is synchronous, it will block the main thread (your application ideally, should not block application's main thread) and hence on device your application will crash unexpectedly during launch and hence will be rejected by apple on submitting it for AppStore.