When I turn off my WiFi connection and run the following code on the iPhone 6s 10.2 simulator, the callback is never executed. I expected the callback to fire fairly quickly with an error like "No Internet connection".
NSLog(#"request-start");
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://www.google.com"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:0];
task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(#"request-end");
}];
[task resume];
My Problem
I'm fetching data when the app first loads. If wifi is turned off, I need to show an error. If I set a timeout, it is obeyed - but it would need to be 10+ seconds and I'd rather not make them wait. I've also tried to detect the network status with reachability, but the network status is often unknown when the app is first loaded.
timeoutIntervalForResource
This property determines the resource timeout interval for all tasks
within sessions based on this configuration. The resource timeout
interval controls how long (in seconds) to wait for an entire resource
to transfer before giving up. The resource timer starts when the
request is initiated and counts until either the request completes or
this timeout interval is reached, whichever comes first.
The default value is 7 days.
and
timeoutIntervalForRequest
Important
Any upload or download tasks created by a background session
are automatically retried if the original request fails due to a
timeout. To configure how long an upload or download task should be
allowed to be retried or transferred, use the
timeoutIntervalForResource property.
The default value is 60.
So, without your timeout set, your connection will run for 7 days.
In general an NSURLSession background session does not fail a task if
something goes wrong on the wire. Rather, it continues looking for a
good time to run the request and retries at that time. This continues
until the resource timeout expires (that is, the value in the
timeoutIntervalForResource property in the NSURLSessionConfiguration
object you use to create the session). The current default for that
value is one week!
Source
Related
When I use bluetooth to write data, I hope get response. But when the peripheral goes wrong, it doesn't send notification. I need set a timeout interval to handle this bad interaction. Like we use urlrequest:
/// Creates and initializes a URLRequest with the given URL and cache policy.
/// - parameter: url The URL for the request.
/// - parameter: cachePolicy The cache policy for the request. Defaults to `.useProtocolCachePolicy`
/// - parameter: timeoutInterval The timeout interval for the request. See the commentary for the `timeoutInterval` for more information on timeout intervals. Defaults to 60.0
public init(url: URL, cachePolicy: CachePolicy = .useProtocolCachePolicy, timeoutInterval: TimeInterval = 60.0) {
_handle = _MutableHandle(adoptingReference: NSMutableURLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeoutInterval))
}
How could I make it.
By "when the peripheral goes wrong" - if you mean that the peripheral crashes or stops working then you should get a BLE disconnection event to indicate the crash:
(centralManager:didDisconnectPeripheral:)
If this is not the case and you just stop receiving notifications after some time and the BLE connection is still alive, then there is no way to tell why the peripheral stopped sending notifications. The reason for this is that there is no specific "time" associated with notifications. Some peripherals send notifications every 1 second and some send notifications every 1 week. Some peripherals send notifications on a value change (e.g. if the temperature increases by 1 degree) and some send notifications on a user action (e.g. the user pressed a button).
The only workaround for this is if you add a timer in your central device, then every time you receive a notification using:
peripheral(_:didUpdateValueFor:error:)
you can reset that timer (if it is the exact notifications you're expecting to timeout). Then if the timer expires, you know that you did not receive your notification on time as expected and therefore you can flag an error or force a disconnection. This is just one example and there are a few variations of this that you can create (e.g. set a flag on peripheral(_:didUpdateValueFor:error:) that you check and reset every 30 seconds). You can find more information about timers in the links below:-
The ultimate guide to timer
Timer: Apple Developer Documentation
iOS Timer Tutorial
I hope this helps.
This is an interesting question.
After checking CoreBluetooth, i figured out that there's already an timeout Error in their list .
https://developer.apple.com/documentation/corebluetooth/cberror/2325746-connectiontimeout
But It's only works for the first time connect to peripheral.
So I think that, if you send a request, but you don't get any response, you have to make your own timer manager.
The concept for timeout is quite complicated. You need make a queue for timer, It's FIFO. You need an uniqueId for each request and you have to map it with your expected response.
Ex :
You call :
Call to get health info via BLE (any thread).
Call to get height info via BLE (any thread).
Call to get health info via BLE (any thread).
Your response :
height info.
health info.
After mapping your request with your response, you will notice that there's one expected response is missing. So how to know the request 1 or request 3 is missing response. It's your choice.
In conclusion, I think that you need a queue request and a queue expected response and a queue for timer. To manage these queue is not really a very big problem.
I am very confused by iOS caching system. It should be straightforward but it not (for me). I am writing an database App which allows for external web access to a limited range of additional reference material in the form of web pages. Sometimes a user might need access to the external web pages while in the field where there is no WiFi or Cell data. Simple, I thought, use URLCaching while iterating through the set of external pages during a time when the internet is available. Then, use a request policy of NSURLRequestReturnCacheDataElseLoad.
So, I created a shared cache in the AppDelegate, then a singleton session:
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.URLCache = self->_myCache;
sessionConfig.requestCachePolicy = NSURLRequestReturnCacheDataElseLoad;
sessionConfig.HTTPMaximumConnectionsPerHost = 5;
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
Then fill the cache using dataTask (since a trial of downloadTask did not fill cache):
NSURLSessionDataTask *myTask = [[NSURLSessionDataTask alloc] init];
do {
myTask = [self.session dataTaskWithRequest:req];
myTask.taskDescription = searchName; //Iterating several values of searchName
[myTask resume]; } while ...searchNames...
This fills the cache as evidenced by cache.currentDiskUsage and cache.currentMemoryUsage. Run the web read loop after initial fetches does not further increase size of cache.
Here is the problem:
Running this code fragment with internet off fails to read from cache
NSURL *nsurl=[NSURL URLWithString:#"https:searchName"];
NSURLRequest *nsrequest = [NSURLRequest requestWithURL:nsurl cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:60.0];
[_webView loadRequest:nsrequest];
in addition, once cache is filled, trying to repeat the initial round of fetches with internet off, leads to
TIC TCP Conn Failed [1:0x6000028ce580]: 1:50 Err(50)
Is there no cache interoperability between HTTP if filled with dataTask and reading back from cache with NSURLRequest? What am I doing wrong? Is there a different approach to solve my design goal?
After three days of experimentation, I am able to create a cache of about three hundred web pages (with associated embedded images) and read them back from persistent memory, across launches of the app.
The only approach to accessing the cache is to use the NSURLCache cachedResponseForRequest: method. Using other forms of accessing a URL with appropriate CachePolicy do not seem to work.
There does seem to be an unfortunate side effect of the cachedResponseForRequest: method in that it only passes the original html document to wkwebview, and none of the linked resources which are verified to also be in my cache.
Does anyone have a suggestion to get this limit?
I have tried and failed many times to get some inherited code to successfully send a background session request. When in the foreground, it works flawlessly, but in the background it will either never return, or, in some cases, receive a auth challenge.
The code to create the request is pretty boilerplate:
NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
sessionConfig.sessionSendsLaunchEvents = true;
sessionConfig.discretionary = false;
sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
sessionConfig.timeoutIntervalForResource = 60 * 60 * 24; //One day. Default is 7 days!
/* Create session, and optionally set a NSURLSessionDelegate. */
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
NSURLComponents *urlComponents = [NSURLComponents new];
urlComponents.scheme = #"https";
urlComponents.host = #"jsonplaceholder.typicode.com";
urlComponents.path = #"/posts/1";
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[urlComponents URL]];
request.HTTPMethod = #"PUT";
NSLog(#"Making request: %#", request);
/* Start a new Task */
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];
But I never get anything back. I do have the required background modes (probably)
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
and my delegate is delegating everything
<NSURLSessionDelegate, NSURLSessionDataDelegate,NSURLConnectionDataDelegate,NSURLConnectionDelegate,NSURLSessionTaskDelegate>
Any help would be appreciated- I'm pretty sure I'm missing just one line of code somewhere. I even went so far as to create a GitHub repo just to test this code out- it schedules a Local Notification to allow you to run the session task in the background.
GitHub repo for BackgroundSender
In the app delegate's handleEventsForBackgroundURLSession, you are doing nothing with the completion handler. You should:
save copy of the completionHandler;
start the background NSURLSession with that identifier; and
when done handling all of the delegate methods, call the saved completion handler; we often do that in URLSessionDidFinishEventsForBackgroundURLSession.
By the way, you shouldn't use data tasks with background sessions. You generally should use a download or upload task.
Also, those background modes are not necessary for background NSURLSession. Background fetch is designed for a different problem, namely periodically polling to see if your server has data available. If you need that, then by all means, use the fetch background mode, but realize that this is a separate topic from merely performing background requests.
See Fetching Small Amounts of Content Opportunistically in the App Programming Guide for iOS: Background Execution for a discussion of background fetch, and compare that to the Downloading Content in the Background section.
Likewise, the remote-notification key is unrelated, too. As the docs say, remote-notification is used when "The app wants to start downloading content when a push notification arrives. Use this notification to minimize the delay in showing content related to the push notification."
I'm using CKModifyRecordsOperation to save a set of records and if I have internet connection all works well and completion block is being called. But when I don't have connection the completion block is not being called and I don't get any information that my operations failed.
I'm using the following code in completion block
modifyOperations.modifyRecordsCompletionBlock = ^(NSArray *savedRecords, NSArray *deletedRecordIDs, NSError *error)
{
if(error){
NSLog(#"Error: %#", error.localizedDescription);
}
item.creatorRecordId = record.recordID;
};
and then I'm performing operation using
[self.publicDB addOperation:modifyOperations];
Any ideas how can I get an information if the operation failed for example in the case where there is no internet connection?
CloudKit operations have their qualityOfService property set to NSQualityOfServiceUtility by default.
Operations that use NSQualityOfServiceUtility or NSQualityOfServiceBackground may be marked as using discretionary network requests. The system can hold discretionary network requests if network connectivity is poor, so you might not get a response from the server until conditions improve and the system sends the request.
If you'd like your request to be sent immediately in all cases, set CKOperation.qualityOfService to NSQualityOfServiceUserInitiated or NSQualityOfServiceUserInteractive.
I need to upload files in the background with other HTTP POST requests before and after each upload.
Previously I was using beginBackgroundTaskWithExpirationHandler which was working perfectly till iOS 6 but from IOS 7 it is restricted for approx 180 seconds only which is a concern.
I have read the documents regarding NSURLSession were in we have Background transfer service. But the problem with this is it only allows upload and download in background. It doesn't allow me to make POST request after every upload in the background.
So is there any way to make the POST request along with the background uploads?
Any hint in the right direction would be highly appreciated.
I think you can use NSURLSessionDownloadTask to send a POST.
IMO, download task doesn't mean it is used for download. it means the response of your POST request (json/xml) will be downloaded to a local file. Then you can open that file and parse it to get the request.
if you want you can even use a NSURLSessionDownloadTask to upload files to S3. and the s3 response will be 'downloaded' to a local file..
for more information, see this question in the apple developer forum https://devforums.apple.com/thread/210515?tstart=0
I successfully round-trip a vanilla http call during a background task's callback, in production code. The callback is:
-[id<NSURLSessionTaskDelegate> URLSession:task:didCompleteWithError:]
You get about 30 seconds there to do what you need to do. Any async calls made during that time must be bracketed by
-[UIApplication beginBackgroundTaskWithName:expirationHandler:]
and the "end task" version of that. Otherwise, iOS will kill your process when you pop the stack (while you are waiting for your async process).
BTW, don't confuse UIApplication tasks (I call them "app tasks") and NSURLSession tasks ("session tasks").
If you use uploadTaskWithRequest:fromData:completionHandler: you can make your HTTP POST request from the completion handler block:
[backgroundSession uploadTaskWithRequest:request fromData:data completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200) {
NSMutableURLRequest *postRequest = [NSMutableURLRequest requestForURL:[NSURL URLWithString:#"http://somethingorother.com/"]];
request.HTTPMethod = #"POST";
.
.
.
NSURLResponse *postResponse;
NSError *postError;
NSData *postResponseData = [NSURLConnection sendSynchronousRequest:postRequest returningResponse:&postResponse error:&postError];
// Check postResponse and postError to ensure that the POST succeeded
}
}];