When trying to create a NSURLSessionUploadTask using AFNetworkings AFURLSessionManager in a share extension I keep getting an error. The delegate for the session keeps getting called for
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
With the error
Error Domain=NSURLErrorDomain Code=-996 "Could not communicate with background transfer service" UserInfo=0x60800007a6c0 {NSLocalizedDescription=Could not communicate with background transfer service}
This happens if the app has a session and then the share extension tries to make a session. To init the session I do the following in both the app and the share extension.
NSURLSessionConfiguration* config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:kSHARE_UPLOAD_SESSION];
config.sharedContainerIdentifier = kAPP_GROUP;
self.sessionManager = [[AFURLSessionManager alloc] initWithSessionConfiguration:config];
self.sessionManager.attemptsToRecreateUploadTasksForBackgroundSessions = YES;
You must use a different "Identifier" (kSHARE_UPLOAD_SESSION) for the main app & the extension. You apparently cannot have 2 NSURLSession with the same identifier running at the same time. You should keep the same group though (kAPP_GROUP)
Related
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 have recently replaced NSURLConnection to NSURLSession in my code.
As I am using many synchronous url-requests and NSURLSession doesn't support one, I used semaphores to make NSURLSessionDataTask synchronous.
I referred this link: https://forums.developer.apple.com/thread/11519
I have a singleton 'Network Manger' with NSURLSession as a member variable. NSURLSession is instantiated only once and tasks are added to it.
But now synchronous request cause performance issues in my app. There are lags and app hangs when synchronous request is sent.
Her is the call to synchronous request:
NSURLResponse *urlResponse;
NSError *error;
id serverResponse=[[MyNetworkManager sharedInstance] synchronousDataTaskWithRequest:request
returningResponse:&urlResponse
error:&error];
Everything works perfectly if I do not make NetworkManager as singleton and instantiate it everytime.
MyNetworkManager *manger=[[MyNetworkManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] responseSerializer:nil];
NSURLResponse *urlResponse;
NSError *error;
id *serverResponse=[manger synchronousDataTaskWithRequest:request returningResponse:&urlResponse error:&error];
Notes:
1.Exact Steps: There are total 3 (2 async and 1 sync ) NSURLSessionDataTasks. Second async task is called in completion handler of first task. Third synchronous task is called from completion handler of second. This third task is blocked forever and code doesn't proceed.
2.Everything is happening on background thread.
Why does synchronous data task work only if it is added to a new NSURLSession? Why it doesn't work if added to NSURLSession which has already executed two data tasks?
I was facing the similar issue when I used to synchronus implementation using Semaphore and here is the cause of that issue :
I think that your an HTTP request is causing a redirection, and in the willPerformHTTPRedirection method, you haven't called the completionHandler for the new request. Try implementing the method as :
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
NSLog(#"willPerformRedirection");
completionHandler(request);
}
I am trying to write a custom resourceLoader delegate to work with an AVURLAsset. I have started with the AVARLDelegateDemo code from Apple. I am trying to playback an HLS url. I am testing on an iPad.
What I notice is that the playlist file (.m3u8) gets downloaded correctly. Then video file (.ts) also gets downloaded. I know that the .ts file is downloaded because I can see the GET request completing on the web server with status 200. I also set a breakpoint at the following line:
[loadingRequest.dataRequest respondWithData:data];
The length of data matches the file size and the first byte is the sync byte of the .ts (0x47) as expected.
The problem is that the app displays an error code. The following dialog pops up:
"The operation could not be completed. An unknown error occurred (-12881)"
Googling for this error has not turned up any information. I do not know what to check for or how to get more information. It is not as if the app is crashing and giving me a stack trace. The video refuses to play and I get no more information beyond the "unknown error -12881" This is not a lot to go on.
Also, if I point an unmodified version of the demo code at my playlist, the video plays just fine.
Can anyone tell me what is going wrong? Here is the code from my customized resource loader.
- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
NSURLRequest *redirect = nil;
redirect = [self generateRedirectURL:(NSURLRequest *)[loadingRequest request]];
if (redirect)
{
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:redirect.URL
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
[loadingRequest.dataRequest respondWithData:data];
[loadingRequest finishLoading];
}] resume];
} else
{
[self reportError:loadingRequest withErrorCode:badRequestErrorCode];
}
return YES;
}
For the record:
I contacted Apple Developer Tech Support about the issue with trying to use the AVAssetResourceLoaderDelegate to get access to the .ts files. This approach does not work because:
"
It is not possible to have access to the data as it is being
downloaded. iOS only allows only the following to be returned via
AVAssetResourceLoaderDelegate for HTTP Live Streaming media:
- key requests
- playlist
- media redirects
"
For my use case, I ended up using a local web server (https://github.com/swisspol/GCDWebServer) and sending all requests to a web server within the app. This web server then makes requests to the remote server
I am running a background NSURLSession session and i am trying to figure out a way to get the JSON response out of one of the NSURLDownloadTaskDelegate callbacks. I have configured my session to accept JSON responses.
NSURLSessionConfiguration *backgroundSession = [NSURLSessionConfiguration backgroundSessionConfiguration:#"com.Att.Locker.BackgroundUpload"];
backgroundSession.HTTPAdditionalHeaders = #{ #"Accept":#"application/json"};
session = [NSURLSession sessionWithConfiguration:backgroundSession delegate:uploader delegateQueue:nil];
I can easily parse JSON response for NSURLSessionDownloadTasks using the following callback. It writes the JSON response onto the sandbox in the form of NSURL.
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
//Reading the Json response from the sandbox using the NSURL parameter
}
My problem is if i encounter an error the callback above is not called, it seems to only get invoked in case of a successful download. Since i am using a background session i cannot use any of the NSURLSessionDataDelegate callbacks. I can only use the NSURLSessionDownloadTaskDelegate and NSURLSessionTaskDelegate and while i can get the task response using the following callback. I don't see the JSON in the response.
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)downloadTask.response;
NSDictionary *httpResponse = [response allHeaderFields];
NSLog(#"Response Header Fields:%#",[httpResponse allKeys]);
}
NSURLConnection has a didReceiveData parameter which gives us an NSData object which we can use to get the JSON response. I don't see that in the delegate callbacks for NSURLSession except for NSURLDataTask but we cant use data tasks in the background so how are we supposed to get the JSON response out ? Any help is appreciated
EDIT:
I usually experience this issue while i am running the app in the background (mostly when it is kicked out memory and not just suspended). I have implemented the callbacks in the appDelegate and i am able to re associate with the session.I think didFinishDownloadingToURL is only invoked in case of successful completion of a task but when a task fails there is no guarantee its going to be called but on the other hand didCompleteWithError gets called every time there is a failure
Using a download task, you can get the data using the didFinishDownloadingToURL as you stated.
All NSURLSession tasks have this delegate as well. If you get in here and error is not nil, then walah you have an error. It does not need to complete to get in here. If it does get in here with an error, then the delegate didFinishDownloadingToURL will not be called.
If there is no error, and all of your data downloads, than both delegates will be called.
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
NSLog(#"didCompleteWithError");
}
EDIT:
So something has to bet not setup correctly as there has to be a way to get the data.
Are you implementing application:handleEventsForBackgroundURLSession:completionHandler: in your AppDelegate which will hook your app back up to the completion handler to get the delegate calls?
I highly recommend watching the 2013 WWDC session #705, "Whats New in Foundation Networking". Background session talk begins at around 32 minutes, and the code demo begins around 37:50