Attach parameters to NSURLSession Delegate Method - ios

I would like to pass a parameter into my delegate to determine to continue or check for cert.
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
NSLog(#"Parameter 1 %#", parameter);
}
NSURLSessionDataTask * dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if ([data length]>0 && error == nil) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", json);
resolve(json);
} else if ([data length]==0 && error ==nil) {
NSError *error = [NSError errorWithDomain:#"xxxx" code:400 userInfo:#{#"Error reason": #"No data returned."}];
reject(#"error", #"error description", error);
} else if( error!=nil) {
NSError *error = [NSError errorWithDomain:#"xxxx" code:400 userInfo:#{#"Error reason": #"Invalid request."}];
reject(#"error", #"error description", error);
}
}];
// Start The Task
[dataTask resume];
How can I pass a parameter from my URLSession into this delegate. I looked for a few hours and found nothing online about this. No surprise. Most Obj-c things I find no good references or examples or walkthroughs. Everything is extracted.

My solution was to store and retrieve the cert using SecureKey, if does not exist, just continue on, with the datatask, and it handled the message, authorized or not.
Depending on if the target route needed the cert.

Related

Class method not being called in Obj C

Here is where I call the class method. The call is made after a NSURLRequest is finished. All values are there, nothing is nil
[MemberInfo SetMemberInfo:memberId groupId:groupId token:token withContext:_context];
Here is the method implemented in the class generated by the core data "MemberInfo+CoreDataProperties.m"
+ (bool)SetMemberInfo:(NSString *)memberId groupId:(NSString *)groupId token:(NSString *)token withContext:(NSManagedObjectContext *)context
{
NSManagedObject *memberInfoObject = [NSEntityDescription insertNewObjectForEntityForName:#"MemberInfo" inManagedObjectContext:context];
[memberInfoObject setValue:memberId forKey:#"memberId"];
[memberInfoObject setValue:groupId forKey:#"groupId"];
[memberInfoObject setValue:token forKey:#"token"];
NSError *error = nil;
if (![context save:&error])
{
return false;
}
return true;
}
I have zero errors, and nothing in the logs that explains why. But this class method 'SetMemberInfo' is never hit. Any clues?
EDIT **
Full code where I call method
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error == nil)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ([httpResponse statusCode] == 200)
{
id object = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
if ([object isKindOfClass:[NSDictionary class]] && error == nil)
{
NSString *groupId = _tfGroupId.text;
NSString *memberId = _tfMemberId.text;
NSString *token = [object valueForKey:#"token"];
[MemberInfo SetMemberInfo:memberId groupId:groupId token:token withContext:_context];
}
}
}
}];
[postDataTask resume];
Must be something to do with the class that I has the class method in. I moved it to another class and it now makes the call.

iOS 9 Best solution for parsing JSON

I always used this solution when I needed to parse a feed JSON.
https://stackoverflow.com/a/20077594/2829111
But sendAsynchronousRequest is now deprecated and I'm stuck with this code
__block NSDictionary *json;
[[session dataTaskWithURL:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// handle response
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"Async JSON: %#", json);
[collectionView reloadData];
}] resume];
And with this the reloadData argument takes a long time to execute. I've alredy tried forcing back to the main queue with:
__block NSDictionary *json;
[[session dataTaskWithURL:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// handle response
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"Async JSON: %#", json);
dispatch_sync(dispatch_queue_create("com.foo.samplequeue", NULL), ^{[collectionView reloadData});
}] resume];
The problem is that the completion handler does not run on the main queue. But all UI updates must happen on the main queue. So dispatch that to the main queue:
[[session dataTaskWithURL:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// handle response
NSError *parseError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
// do something with `json`
dispatch_async(dispatch_get_main_queue()), ^{[collectionView reloadData]});
}] resume];
Why don't you try JSONModel library....... it is so simple to use
-(void)getEmployeePerformance:(EmpPerformanceRequest*)request
withSuccesBlock:(succesEmployeePerformanceResponseBlock) successBlock
andFailBlock:(FailResponseBlock) failBlock
{
NSString* weatherUrl = [[ABWebServiceUtil sharedInstance]getEmployeePerformanceURL];
[HTTPClientUtil postDataToWS:weatherUrl parameters:[request toDictionary] WithHeaderDict:nil withBlock:^(AFHTTPRequestOperation *responseObj, NSError *error)
{
if(responseObj.response.statusCode == HTTP_RESPONSE_SUCESS)
{
EmpPerformanceArrayModel *empPerfArrModel;
if(responseObj.responseString)
{
empPerfArrModel = [[EmpPerformanceArrayModel alloc]initWithString:result error:nil];
empPerfArrModel.employeesArray = [empPerformanceModel arrayOfModelsFromDictionaries:empPerfArrModel.employeesArray];
}
if(successBlock)
{
successBlock(responseObj.response.statusCode, empPerfArrModel);
}
}else if (failBlock)
{
failBlock(responseObj.response.statusCode);
}
}];
}
for more detail follow this link...... it will brief you well
https://github.com/icanzilb/JSONModel
Try parsing JSON in connectionDidFinishLoading so you will get response as NSDictionary.
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
Class NSJSONSerializationclass = NSClassFromString(#"NSJSONSerialization");
NSDictionary *result;
NSError *error;
if (NSJSONSerializationclass)
{
result = [NSJSONSerialization JSONObjectWithData: responseData options: NSJSONReadingMutableContainers error: &error];
}
// If the webservice response having values we have to call the completionBlockā€¦
if (result)
{
if (self.completionBlock != nil)
{
self.completionBlock(result);
}
}
}

iOS :: error is nil if not able to download file with NSURLSession

I am downloading files in the app using NSURLSession and NSURLSessionTask. If file is present on the server then it works fine. If file is not available then it writes the data into the file and don't download it.
For example :: if file is not available the it writes
Access is denied
in the file and completes the task without any error. In this case error comes as nil.
But i want to show alert box to the user saying file is not available.
Below is the code...
task1 = [session dataTaskWithURL:[NSURL URLWithString:[S3_SERVER_URL stringByAppendingString:propFile]] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
propfilePath = [[Util getInAppTempPath] stringByAppendingString:propFile];
NSLog(#"DestPath : %#", propfilePath);
[receivedData appendData:data];
NSLog(#"Succeeded! Received %lu bytes of data",(unsigned long)[data length]);
//[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[data writeToFile:propfilePath atomically:YES];
//NSLog(#"iOSDBVersion: %#", [plistDictionary objectForKey:#"iOSDBVersion"]);
//NSLog(#"ttodDBVersion: %#", [plistDictionary objectForKey:#"ttodDBVersion"]);
if(error == nil){
[Util getDataFromPlistFile];
[Util isAppRunBefore];
[self downloadDatabase];
} else {
[self alertNoPlist];
}
}];
[task1 resume];
It never goes into the else block.
I tired to put this in try catch block then also no luck.
How can I handle these kind of errors in iOS?
I assume the server is returning a response code such as 401 (Unauthorized), along with a body such as "Access is denied." This is a not an error in the NSURLSession sense. This is a completely legitimate response, so you wouldn't expect anything in error.
Instead, you need to check the response's statusCode value. This should be between 200-299 for success. As I said, you probably are getting 401.
Note that you're passed an NSURLResponse. You will likely need to cast this to NSHTTPURLResponse in order to access its statusCode.
Used below code to get the httpresponse status code.
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSLog(#"response status code: %ld", (long)[httpResponse statusCode]);
You are using dataTaskWithURL: for downloading the image. For download tasks you should use downloadTaskWithURL:.
[[session downloadTaskWithURL:[NSURL URLWithString:[S3_SERVER_URL stringByAppendingString:propFile]] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)
{
if (error)
{
// Show error
}
else
{
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:propfilePath] error:nil];
}
}] resume];
For more details refer :
NSURLSessionDownloadTask
NSURLSessionDataTask

Multiple web service calls at the same time in iOS

In my app i need to call two services at a time. for single service i am using the below code:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Instantiate a session object.
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURL *url = [NSURL URLWithString:#"my link"];
// Create a data task object to perform the data downloading.
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error != nil) {
// If any error occurs then just display its description on the console.
NSLog(#"%#", [error localizedDescription]);
}
else{
// If no error occurs, check the HTTP status code.
NSInteger HTTPStatusCode = [(NSHTTPURLResponse *)response statusCode];
// If it's other than 200, then show it on the console.
if (HTTPStatusCode != 200) {
NSLog(#"HTTP status code = %d", (int)HTTPStatusCode);
} else {
NSMutableArray *jsonData = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves error:nil];
NSLog(#"json data ==========> %#", jsonData);
}
}
}];
// Resume the task.
[task resume];
by using this i am getting the data. Now, at the same time i need to call another service. How can i achieve this? and How i will get the data?

Running NSURLSession completion handler on main thread

I am using a NSURLSession to get the values to populate a TableView. I am updating the TableView in the completion handler, but using [[NSThread currentThread] isMainThread] has shown me that the completion handler isn't running in the main thread. Since I should only updating the UI from the main thread, I know this isn't correct. Is there a way to trigger an action on the main thread from the completion handler? Or is using a NSURLSession the wrong way to go about this?
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:#"http://myurl"]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *jsonError = nil;
NSArray* jsonUsers = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (jsonError) {
NSLog(#"error is %#", [jsonError localizedDescription]);
// Handle Error and return
return;
}
self.userArray = jsonUsers;
[self.userTableView reloadData];
if ([[NSThread currentThread] isMainThread]){
NSLog(#"In main thread--completion handler");
}
else{
NSLog(#"Not in main thread--completion handler");
}
}] resume];
Yes, just dispatch your main thread stuff using GCD:
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:#"http://myurl"]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *jsonError = nil;
NSArray* jsonUsers = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (jsonError) {
NSLog(#"error is %#", [jsonError localizedDescription]);
// Handle Error and return
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
self.userArray = jsonUsers;
[self.userTableView reloadData];
if ([[NSThread currentThread] isMainThread]){
NSLog(#"In main thread--completion handler");
}
else{
NSLog(#"Not in main thread--completion handler");
}
});
}] resume];
#graver's answer is good. Here's another way you can do it:
NSURLSession* session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:nil
delegateQueue:[NSOperationQueue mainQueue]];
[[session dataTaskWithURL:[NSURL URLWithString:#"http://myurl"]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *jsonError = nil;
NSArray* jsonUsers = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (jsonError) {
NSLog(#"error is %#", [jsonError localizedDescription]);
// Handle Error and return
return;
}
self.userArray = jsonUsers;
[self.userTableView reloadData];
if ([[NSThread currentThread] isMainThread]){
NSLog(#"In main thread--completion handler");
}
else{
NSLog(#"Not in main thread--completion handler");
}
}] resume];
This way you create a session that calls the completion block and any delegate methods on the main thread. You may find this more aesthetically pleasing, but you do lose the advantage of running the "hard work" in the background.
Here is the best way to update UI from blocks and completion handler, and also when you not confrim which thread running your code.
static void runOnMainThread(void (^block)(void))
{
if (!block) return;
if ( [[NSThread currentThread] isMainThread] ) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
This is static method which will have a block, and will run on main thread, it will act like a
runOnMainThread(^{
// do things here, it will run on main thread, like updating UI
});
You can try this:
[self.userTableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
send a notification on completion that will be observed by the table view controller which will then do a reloadData;
this has the advantage that if you later move this download code off to a separate class, e.g. a model object, then no changes are required and also the code can be reused in other projects without making changes
Swift 3.1
DispatchQueue.main.async {
tableView.reloadData()
}

Resources