I have a NSObject class 'DataDownload_A' for downloading data. Inside I use NSURLSession and NSURLSessionTask. Now the problem is when I set this object to nil, the dealloc won't be called. Here is a segment code of DataDownload_A:
NSString *urlString = [self URL];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.session = [NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:nil];
self.receivedData = [NSMutableData data];
self.task = [self.session dataTaskWithRequest:theRequest];
[self.task resume];
And if I comment the last two lines 'self.task = [self.session dataTaskWithRequest:theRequest];' and '[self.task resume];', dealloc will be called. Therefore I assume there must be some issues when I use them. I tried to called '[self.task cancel]', 'self.task = nil', '[self.session invalidateAndCancel];' and '[self.session resetWithCompletionHandler:nil];' before I set my object to nil. But didn't help.
So does anyone know what the mistake I made ?? Would be grateful letting me know. Thank you in advance.
It looks like the URLSession holds a strong reference to its delegate. I found this line in the docs on URLSession:
Important: The session object keeps a strong reference to the delegate
until your app explicitly invalidates the session. If you do not
invalidate the session, your app leaks memory.
That bit makes it sound like a URLSession holds a strong reference to its delegate from the moment it's set up until it's invalidated or your app is terminated, but it may be that it only creates a strong reference when a data (or other) task is started.
It sounds like you need to invalidate the URLSession in order to get it to release it's delegate. I suggest searching for that text in the URLSession docs in Xcode. Also search on "invalidate"
Related
I am currently trying to access a webpage where the user can login using their credentials, after entering their user and password - if correct it will redirect to a new url. This new url loads a webpage with a single string which I intend to use.
However, how am I able to check the contents of the redirected url? At the moment I am only able to check the Response/Data/Contents of the initial page loaded by the following method;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
casSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSString *urlAddress = #"https://originalurl.com";
NSURL *httpUrl = [NSURL URLWithString:urlAddress];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:httpUrl];
[loginPage loadRequest:requestObj];
NSURLSessionDataTask *redirect = [casSession dataTaskWithURL:httpUrl completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSString *newURL = [NSString stringWithFormat: #"%#", response.URL];
if ([newURL containsString:#"ticket=ST"]) {
NSString * registrationID = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"REGISTRATION: %#", registrationID);
if (registrationID != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
loginPage.hidden = YES;
});
}
} else {
NSLog(#"No ticket recieved");
}
}];
[redirect resume];
I'm not sure which delegate to use, in order to actively check every time a redirection happens and then obtain the contents of the new url?
Thanks.
You’re looking at this the wrong way. You should query the user for the login info directly and insert that into a single NSURLDataTask. Then the data task should query the server with the login info, and return some data.
This all happens with APIs (in a broad manner of speaking) where you will not present HTML contents to the user, but instead some sort of encoded data that is returned.
So for example, once you have a task defined from a URL or URLRequest, and you begin the task, you then use the completion handler to verify the returned data and/or error. If here, you may decode the returned data as a NSString, and then convert the JSON to objects, such as a user’s profile’s data (name, age, email, ...)
I did not go into detail in this answer because it is a very very broad topic, with many use cases. Look up some tutorials on NSURLDataTasks or consuming APIs from Swift and/or Objective-C.
We have crated session with below configuration code. I call this method for each task I crate.
+(NSURLSession ) getNewSessionWithID:(NSString )sessionID delegateObject:(id)sender
{
NSURLSession *session;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID];
NSOperationQueue *queue=[[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount=10;
queue.name=sessionID;
session = [NSURLSession sessionWithConfiguration:configuration delegate:sender delegateQueue:queue];
NSLog(#"Session ID :%#",session);
NSLog(#"QUEUE : %#",queue);
return session;
}
Even multiple sessions are created for multiple tasks only one session is active and only one task is executing and for that task only three part are downloading for that one session.
This method is called to create and start download task.
-(void)DLRequestAllRenge:(NSMutableArray*)arrayrange andFileinfo:(FileInfo *)fileInfoObj
{
NSMutableArray *arrayAllParts=[[NSMutableArray alloc]init];
fileInfoObj.tempPath=[fileInfoObj UniqueFileName:[NSTemporaryDirectory() stringByAppendingString:[fileInfoObj.Name stringByDeletingPathExtension]]];
[self createTempDirectory:fileInfoObj.tempPath];
NSLog(#"REQ Session:%#",fileInfoObj.session);
for (int i=0; i<arrayrange.count; i++)
{
NSString *rangString = [arrayrange objectAtIndex:i];
NSMutableURLRequest *request=[[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:fileInfoObj.URL]];
[request setValue:rangString forHTTPHeaderField:#"Range"];
NSString *fileName=[NSString stringWithFormat:#"%#(%d).data",[fileInfoObj.Name stringByDeletingPathExtension],i];
NSString *filePath=[fileInfoObj.tempPath stringByAppendingPathComponent:fileName];
FileInfo *subFileInfo=[[FileInfo alloc]init];
subFileInfo.URL=fileInfoObj.URL;
subFileInfo.Name=fileName;
subFileInfo.Path=filePath;
subFileInfo.Folder=[fileInfoObj getCurrentFolderName:filePath];
subFileInfo.Range=rangString;
subFileInfo.isDownloaded=NO;
subFileInfo.NSUrlSessionID=fileInfoObj.NSUrlSessionID;
subFileInfo.Progress=#"0";
subFileInfo.Priority=[NSString stringWithFormat:#"%d",i];
subFileInfo.fileDetail=#"Connecting...";
subFileInfo.fileStatus=RequestStatusDownloading;
subFileInfo.request=request;
subFileInfo.startTime=[NSDate date];
NSURLSessionDownloadTask *downloadTask = [fileInfoObj.session downloadTaskWithRequest:request];
downloadTask.taskDescription=filePath;
[downloadTask resume];
subFileInfo.DownloadTask=downloadTask;
[arrayAllParts addObject:subFileInfo];
}
fileInfoObj.parts=arrayAllParts;
[downloadingArray addObject:fileInfoObj];
[bgDownloadTableView reloadData];
}
Issue 1
Why all sessions are not active?
Issue 2
Why only three part for one task and one session is downloading?
Is there any way we can activate more session for download more part concurrently?
Please help me with this. Any help is appreciated.
Update
We are able to download data using the above code but the issue is I am not able to get any downloaded data when any downloading task is stopped using -suspend or -cancel.
Is there any way I am able to retrieve raw data not the resumeData but the original downloaded data?
I need to download some images and a video from server:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
for (int i = 0; i < _collectionArr.count; ++i) {
NSURL *URL = [NSURL URLWithString:URL_ADDRESS(portNumber,_collectionArr[i])];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"File downloaded to: %#", filePath);
if (i == _collectionArr.count - 1) {
[SVProgressHUD showSuccessWithStatus:#"Done"];
}
}];
[downloadTask resume];
}
I found that it is so slow! The speed is about 200K/s and the time with Android is 2/3 less than iPhone.
When I download a mp4 about 4.6M, it takes 15s.
2014-11-29 13:46:35.071 testDownload[2105:47825] Begin
2014-11-29 13:46:51.740 testDownload[2105:47825] File downloaded to: file:///Users/apple/Library/Developer/CoreSimulator/Devices/259F2CB8-01FD-47C2-A38F-6100A2FF350A/data/Containers/Data/Application/71E553BC-F85D-4BFA-8937-FE9026FDF65C/Documents/VTS_01_audi.mp4
But when I using other app to download movies it can be 2M/s. Am I wrong when I use afnetworking ? How it happens and what can I do to deal with it.
Another question is that I know it's wrong to monitor the last request with if (i == _collectionArr.count - 1){[SVProgressHUD showSuccessWithStatus:#"Done"];} But I don't know the right answer.
Thanks for your help.
A couple of thoughts:
The removal of the HUD when i hits count - 1 is not correct. You actually may remove it before they're all done. If you start two downloads, one huge one and one tiny one, the HUD will be dismissed when the second one finishes, but the other one might not be done yet. These run concurrently, so you have to wait until they're all done, not just when the last submitted one is done.
One way to do this is to use a dispatch group (which you enter as you submit the requests, leave in the completion block, and then add a dispatch group notification that removes the HUD).
You're not doing anything else outside of this code that might be blocking the main thread are you? AFNetworking dispatches its completion blocks to the main queue, and if you block the main thread for anything, it will affect this performance. You could either use Instruments to identify waiting threads, or, for testing purposes only, temporarily change the completionQueue of the manager to be some custom queue of your own creation.
For example, before your for loop, do something like:
manager.completionQueue = dispatch_queue_create("com.example.app.netCompletionQueue", NULL);
That would take main thread blocking out of the equation (though this strikes me as unlikely unless you're doing something extraordinary in the main thread). Once you confirm this is not the issue, though, comment out that line, because you really do want to use the main thread for your completion blocks.
Once you confirm the perf problem is not result on main thread contention issue, I'd suggest doing some benchmarking of alternative techniques (e.g. your own NSURLSession, etc.). This could help diagnose whether the problem is AFNetworking, rather than something like simulator vs device performance, caching, etc. Frankly, I find it unlikely that AFNetworking is, itself, the problem, though.
I have a View controller where I have a button which brings up the front camera. The user then takes a photo, the camera controller is dismissed and I show the picture taken in an Image View in the View Controller. Now, I start uploading the server to Amazon S3.
It does take some time for the uploading to complete and the user has to wait for that time before moving to the next view.
I would ideally like to move the user to the next view and let the uploading complete in the background. Is there some way I could do this uploading task in the background ? I know their is something called dispatch_queue which could be used to do this but I am not sure how. If I put the uploading code inside a queue in the view controller file and then move to the next view controller, will not the reference of the previous one be lost.
EDIT
I tried doing the following -
Making a new class which would be responsible for uploading to Amazon S3.
This new class is a delegate to AmazonServiceRequest which should be called when the uploading to Amazon S3 is complete.
On completion uploading to S3, I make a call to my server to save the URL in the database.
I make an object of this class inside the queue block like follows -
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
RIDEUploadPhotoService *uploadPhotoServiceObj = [[RIDEUploadPhotoService alloc] init;
[uploadPhotoServiceObj uploadAndSaveImage:imageToSave];
});
It seems to me that the control never comes inside the request complete which should get called when uploading to Amazon S3 is complete.
Just use one of the asynchronous upload-mechanisms of NSURLConnection/NSURLSession, so you don't have to mess around with background threads. For example:
NSURL *fileURL = [NSURL fileURLWithPath:...];
NSURLRequest *request = [NSURLRequest requestWithURL:...];
// modern way: NSURLSession
NSURLSessionUploadTask *uploadTask = [[NSURLSession sharedSession] uploadTaskWithRequest:request fromFile:fileURL completionHandler:...];
[uploadTask resume];
// old way: NSURLConnection
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:...];
// another old way: synchronous NSURLRequest executed in a background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLResponse *response;
NSError *error;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
});
No. The reference will not be lost.
You can use - (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg to perform the method aSelector in the background. If you have any delegates after the completion of the task, they will still be called.
Here is my code:
//login method
- (int) authenticateClient {
NSString *loginWeb = [NSString stringWithFormat:(#"http://192.168.118.1/login.php?uname=%#&pass=%#&submit=Log%%20In"), user, pass];
NSURL *login = [NSURL URLWithString:loginWeb];
NSData *loginData = [NSData dataWithContentsOfURL: login];
NSString *result = [[NSString alloc] initWithData:loginData encoding:NSUTF8StringEncoding];
NSMutableURLRequest *loginRequest = [[NSMutableURLRequest alloc] init];
[loginRequest setURL:login];
[loginRequest setTimeoutInterval:1];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:loginRequest delegate:self startImmediately:YES];
[connection start];
return [result integerValue];
}
My question is, when simulating having no network connection, my app freezes up and doesn't load or time out in the specified interval (1 second just to see if it works).
I read something about start immediately forces in to run on the current threat causing everything else to "pause" until the action is complete.
I have two questions:
1.) What is a better method to have the URL basically run in the background instead of pausing everything?
2.) How can I get a timeout method that actually works?
Much appreciation! Thanks!
Well, one second as timeout is really low. If you want to simulate different network conditions, you can use something like this. You don't need to this dance:
NSData *loginData = [NSData dataWithContentsOfURL: login];
NSString *result = [[NSString alloc] initWithData:loginData encoding:NSUTF8StringEncoding];
You don't need this as well, since the connection has started already:
[connection start];
Also from the documentation:
NSURLConnection’s delegate methods—defined by the
NSURLConnectionDelegate Protocol protocol—allow an object to receive
informational callbacks about the asynchronous load of a URL request.
Other delegate methods provide facilities that allow the delegate to
customize the process of performing an asynchronous URL load. These
delegate methods are called on the thread that started the
asynchronous load operation for the associated NSURLConnection object.
So for your questions:
Implement the NSURLConnection’s delegate
Check this & this.