I'm trying to call multiple request from the share extension.
The request flow is follows
1. call request one - returns id
2. call request two using request one id
3. call request three using request two id
To make this work, I only have to dismiss the extension sheet after the request three completion.
As per apple documentation we can't hold the extension sheet for a long time. So we have to use the background session for each request and dismiss the extension sheet immediately.
But in my case the requests depends on its previous request completion. So if we dismiss the extension sheet then the OS kills the extension from memory and the request two and three calls never be made.
Is there any way to call these request one by one?
Any suggestion will be helpful. Thanks in advance.
Related
I'm trying to stop sending API requests in background, when the today extension is not visible. API requests are pretty expensive, so I would like to optimize the number of sent requests. Where should I put API request so it will be called only when the today extension become visible and will not be called in background?
I have already tried to set NCUpdateResultNoData however viewDidLoad is called in background in that case. In viewDidLoad I send API request to update the today extension when it becomes visible.
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler {
completionHandler(NCUpdateResultNoData);
}
Well this is a tricky question, what you can try is to fire your request in viewWillAppear: but this means that your UI will probably not be ready in viewDidAppear: but you can handle this using an activity indicator.
From the docs:
// If the widget has local state that can be loaded quickly, it should
do so before returning from ‘viewWillAppear:’. // Otherwise, the
widget should ensure that the state and layout of its view when
returning from 'viewWillAppear:’ will match that of the last time it
returned from 'viewWillDisappear:', transitioning smoothly to the new
data later when it arrives.”
I am having an issue with UIWebviews and a couple of custom NSURLProtocols that I have in my app.
All my non web view request are invoked with NSURLSession, so in order for those request to go through the protocols I need to set the setProtocolClasses array on my sessionconfig, at this point everything works as expected.
For my web views, I do a registration on the didFinishLaunchingWithOptions: method in the AppDelegate with the [NSURLProtocol register class[MyProtocol Class]]. If i don't re-register before the execution of the web view request, that web view request won't go through the protocol.
Do you guys have any idea why I have to re-register to my custom protocol every time I try to load a web view request?
What is the request URL? Is it possible that there's another protocol class that gets registered after you? Does canInitWithRequest get called on your class at webview request time?
I have a WatchOS2 app which displays data on the watch after calling NSURLSession. Since response takes some time, if the user opens another interface controller another call goes to
- (void)session:(WCSession *)session didReceiveMessageData:(NSData *)messageData replyHandler:(void(^)(NSData *replyMessageData))replyHandler
But if previous api output comes then it returns data through reply. Again the second data output should also be send. So this is giving a crash and my app hangs.
Is there a way to stop the previous reply from being sent by closing the request?
No, there is no way to cancel the previous request. It sounds like you are making the "currently visible interface controller" be the delegate of the WCSession, which would be mixing a lot of responsibilities. Instead I'd suggest adding something like a singleton class that is the permanent delegate of WCSession; and it persists and notifies, or dispatches incoming data to the right place.
Perhaps I have been reading the wrong stuff, but one thing that all of the literatures that I have been reading seem to agree on is that: iOS does not allow background threads to run for longer than ten minutes. That seems to violate one of the greatest principles of app development: the internet should be invisible to your users. So here is a scenario.
A user is going through a tunnel or flying on an airplane, which causes no or unreliable network. At that instant, the user pulls out my email app, composes an email, and hits the send button.
Question: How do I the developer make sure that the email is sent when network becomes available? Of course I am using email as a general example, but in reality I am dealing with a very much simple http situation where my app needs to send a POST to my server.
Side Note: on android, I use Path’s priority job queue, which allows me to set it and forget it (i.e. as soon as there is network it sends my email).
another Side Note: I have been trying to use NSOperationQueue with AFNetworking, but does not do it.
What you want to achieve can be done using a background NSURLSession. While AFNetworking is based on NSURLSession I’m not quite sure if it can be used with a background session that runs while your app doesn’t. But you don’t really need this, NSURLSession is quite easy to use as is.
As a first step you need to create a session configuration for the background session:
let config = URLSessionConfiguration.background(withIdentifier: "de.5sw.test")
config.isDiscretionary = true
config.waitsForConnectivity = true
The isDiscretionary property allows the system to decide when to perform the data transfer. waitsForConnectivity (available since iOS 11) makes the system wait if there is no internet connection instead of failing immediately.
With that configuration object you can create your URL session. The important part is to specify a delegate as the closure-based callbacks get lost when the app is terminated.
let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
To perform your upload you ask the session to create an upload task and then resume it. For the upload task you first create your URLRequest that specifies the URL and all needed headers. The actual data you want to upload needs to be written to a file. If you provide it as a Data or stream object it cannot be uploaded after your app terminates.
let task = session.uploadTask(with: request, fromFile: fileUrl)
task.resume()
To get notified of success or failure of your upload you need to implement the URLSessionDataDelegate method urlSession(_:task:didCompleteWithError:). If error is nil the transfer was successful.
The final piece that is missing is to handle the events that happened while your app was not running. To do this you implement the method application(_:handleEventsForBackgroundURLSession:completionHandler:) in your app delegate. When the system decides that you need to handles some events for background transfers it launches your app in the background and calls this method.
In there you need first store the completion handler and then recreate your URLSession with the same configuration you used before. This then calls it’s delegate for the events you need to handle as usual. Once it is done with the events it calls the delegate method urlSessionDidFinishEvents(forBackgroundURLSession:). From there you need to call the completion handler that was passed to your app delegate.
The session configuration provides some more options:
timeoutIntervalForResource: How long the system should try to perform your upload. Default is 7 days.
sessionSendsLaunchEvents: If false the app will not be launched to handle events. They will be handled when the user opens the app manually. Defaults is true.
Here is a small sample project that shows how everything fits together: https://github.com/5sw/BackgroundUploadDemo
Your app needs to store the data internally and then you either need something which will cause the app to run in the background (but you shouldn't necessarily add something specially if you don't already have a reason to be doing it) or to wait until the user next brings the app to the foreground - then you can check for a network connection and make the call.
Note that e-mail is very different to a POST, because you can pass an e-mail off to the system mail app to send for you but you can't do exactly the same thing with a POST.
Consider looking also at NSURLSessionUploadTask if you can use it.
In three words: you don't.
And that's actually a good thing. I certainly do not want to have to think and speculate about my last 20 apps, if they are still running in the background, using memory and battery and bandwidth. Furthermore, they would be killed if more memory is needed. How would the user be able to predict if it completed its task successfully? He can't, and need to open the app anyhow to check.
As for the email example, I'd go with showing the email as "pending" (i.e. not sent), until it transferred correctly. Make it obvious to the user that he has to come back later to fulfill the job.
While every developer thinks that his app has an extremely good reason for backgrounding, reality is, for the user in 99% it's just a pain. Can you say "task manager"? ;-)
I wrote a pod that does pretty much this - https://cocoapods.org/pods/OfflineRequestManager. You'd have to do some work listening to delegate callbacks if you want to monitor whether the request is in a pending or completed/failed state, but we've been using it to ensure that requests go out in poor or no connectivity scenarios.
The simplest use case would look something like the following, though most actual cases (saving to disk, specific request data, etc.) will have a few more hoops to jump through:
import OfflineRequestManager
class SimpleRequest: OfflineRequest {
func perform(completion: #escaping (Error?) -> Void) {
doMyNetworkRequest(withCompletion: { response, error in
handleResponse(response)
completion(error)
})
}
}
///////
OfflineRequestManager.defaultManager(queueRequest: SimpleRequest())
I have an app with tabbarcontroller. As of now when an async request is being sent on one of the tabs, the user cannot move to another tab. I want my app to function such that when the request is being sent, user is able to move to other tabs and the async request stays is progress still timeout. How can I achieve this?
If you are sending an async request from a view controller, the user will be able to move to another tab. That's the default behaviour. So unless you have built in logic to change that behaviour (i.e. waiting for an async until it has finished). If that's the case, then you should obviously remove that logic.
If that's not the case i suspect you are currently sending a synchronous request (and not an async request as stated in the question). Sending a synchronous request from a view controller would result in the behaviour as described in your issue. So if that's the case, you should rewrite that and make it an async request.
And if you are still unsure, just post your code!