Nested nsurl dataTaskWithRequest but after the completion handler called - objective-c

I have this part of code and it was work with "NSURLConnection sendSynchronousRequest" and they was having a dispatch and have the both API call in it with no issues, however since i am moving to "NSURLSession" and i would to call the next API after the first one response inside the completionHandler, do i need tho add any type of dispatches for the second API call?
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch was here
__block NSString* url = #"www.google.com";
[[LoaderService get] getRequestFrom:url completionHandler:^(TcHttpJSONResponse *response) { // first Call
if (!response.success) {
dispatch_async(dispatch_get_main_queue(), ^{
failBlock();
});
return;
}
url = [NSString stringWithFormat:#"www.google.com/drive"];
[[LoaderService get] getRequestFrom:url completionHandler:^(TcHttpJSONResponse *response) { // second Call
if (!response.success) {
dispatch_async(dispatch_get_main_queue(), ^{
failBlock();
});
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
successBlock(transactionsArray);
});
}];
}];
// }]; //end of dispatch
in LoaderService
- (void)getRequestFrom:(NSString *)url completionHandler:(void (^)(TcHttpJSONResponse *response))completionHandler {
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:[TcHttpHelper getRequestFromUrlWithAuthorizationToken:url token:[self getAuthToken]]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
completionHandler([[TcHttpJSONResponse alloc] initWithResponse:data urlResponse:(NSHTTPURLResponse*)response error:error]);
}] resume];
}

Related

Performing WKWebview loadrequest inside NSURLSession Completion handler

I'm trying to to do loadrequest WKWebview inside NSURLSession Completion handler
but i'm having problem because it has to be in the main thread i need it inside the completion block? BTW, i was able to accomplish this with NSURLConnection sendSynchronousRequest but i don't want to use that depracted api.
- (void)tryServePage {
// cant call main thread here
NSString *localNodeServerURL = #"http:/127.0.0.1/";
NSURL *url = [NSURL URLWithString:localNodeServerURL];
[[NSURLSession alloc] dataTaskWithURL:url completionHandler:^(NSData * _Nullable myData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
BOOL reachable;
if (myData) {
reachable=YES;
} else {
reachable=NO;
}
// now call ourselves back on the main thread
dispatch_async( dispatch_get_main_queue(), ^{
if(!reachable) {
[self tryServePage];
} else {
NSString *localNodeServerURL = #"http:/127.0.0.1/";
NSURL *url = [NSURL URLWithString:localNodeServerURL];
NSURLRequest *request = [NSURLRequest requestWithURL: url];
[self->_myWebView loadRequest:request];
}
});
}];
}

Extract Completions Handler Block into method using typdef

I would like to clean up my code some and extract a completion block to its own method. It's my understanding that I have to define a typdef with the same signature.
In the below example, I want to move the NSURLSessionUploadTask's on completion handler to its own method. I've looked at the Apple documentation and other SO questions, but I don't know if I am searching for the right thing.
typedef void (^PostCompletionHandler)(NSData *data,NSURLResponse *response,NSError *error);
#interface MyDemoClass() {
#property (copy, nonatomic) PostCompletionHandler completePostHandler;
#end
#implementation MyDemoClass
- (void) post {
NSData *postData = //data to pos;
NSMutableURLRequest * req = //create request;
NSURLSession * session = // create session;
NSURLSessionUploadTask *queryTask = [session uploadTaskWithRequest:req
fromData:postData
completionHandler:^(NSData *data,NSURLResponse *response,NSError *error) {
if (error) {
NSLog(#"Error: %#",error.localizedDescription);
} else {
NSDictionary * response = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
NSLog(#"Post Response: %#",response.description);
}
}];
[queryTask resume];
}
- (void)someMethodThatTakesABlock:(PostCompletionHandler)blockName {
}
#end
This is what I would like to do is something like this:
NSURLSessionUploadTask *queryTask = [session uploadTaskWithRequest:req
fromData:postData
completionHandler:^(NSData *data,NSURLResponse *response,NSError *error) {
// What do I do here?
}];
- (void)someMethodThatTakesABlock:(PostCompletionHandler)blockName {
//how do I access the data, response and error parameters?
if (error) {
NSLog(#"Error: %#",error.localizedDescription);
} else {
NSDictionary * response = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
NSLog(#"Post Response: %#",response.description);
}
}
The someMethodThatTakesABlock: has a single parameter, which is the block. The block is a reference to code that can be executed. It does not contain data.
If you want to separate out your completion handling to a separate method, then write a method
- (void)processCompletionWithData:(NSData*)data response:(NSURLResponse*)response error:(NSError*)error {
// Process it...
}
Then in the completion handler
NSURLSessionUploadTask *queryTask =
[session uploadTaskWithRequest:req
fromData:postData
completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) {
[self processCompletionWithData:data response:response error:error];
}];

Asynchronously Api calls and returning data outside the block

I don't understand why I am getting null array outside the block code, even though I am using __block keyword on my array.
I am successfully getting data from a backend api with following code
`-(void)getJsonResponse:(NSString *)urlStr success:(void (^)(NSArray *responseDict))success failure:(void(^)(NSError* error))failure
{
NSURLSession *session = [NSURLSession sharedSession];
NSURL *url = [NSURL URLWithString:urlStr];
// Asynchronously API is hit here
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// NSLog(#"%#",data);
if (error)
failure(error);
else {
NSArray *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
// NSLog(#"%#",json);
success(json);
}
}];
[dataTask resume]; // Executed First
}`
Then in my function for returning the data I am using following
`- (NSArray *)get_data:(NSDictionary *)credentials{
NSString *urlStr =[ NSString stringWithFormat:#"http://test.com %#",credentials];
__block NSArray *jsonArray= [[NSArray alloc]init];
[self getJsonResponse:urlStr success:^(NSArray *responseArray) {
jsonArray = responseArray;
NSLog(#"%#",responseArray);
} failure:^(NSError *error) {
// error handling here ...
}];
NSLog(#"%#",jsonArray);
return jsonArray;
}
`
The issue here is although I am successfully getting data within getJsonResponse block, but when I am trying to return the response data array as function return I am getting null for jsonArray. I thought assigning __block infront of jsonArray should retain the data assign within the block code ?
The second approach is not to use Async way like following
`- (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
{
NSError __block *err = NULL;
NSData __block *data;
BOOL __block reqProcessed = false;
NSURLResponse __block *resp;
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable _data, NSURLResponse * _Nullable _response, NSError * _Nullable _error) {
resp = _response;
err = _error;
data = _data;
reqProcessed = true;
}] resume];
while (!reqProcessed) {
[NSThread sleepForTimeInterval:0];
}
*response = resp;
*error = err;
return data;
}`
That way its blocking the main thread whilst waiting for data.
I would suggest using the same approach of getJsonResponse for your get_data function:
- (void)get_data:(NSDictionary *)credentials finish:(void(^)(NSArray *data))finish{
NSString *urlStr =[ NSString stringWithFormat:#"http://test.com %#",credentials];
__block NSArray *jsonArray= [[NSArray alloc]init];
[self getJsonResponse:urlStr success:^(NSArray *responseArray) {
jsonArray = responseArray;
if (finish) {
finish(jsonArray);
}
} failure:^(NSError *error) {
// error handling here ...
}];
}

Multi threading objective-c

I have to search database using keyword typed from key board from iphone.
When ever i search the key board halts. and if i run in the background then i find an empty array.
I want both key board so that key board doesnot halts and wait until array is full with data.I am using objective-c.
dispatch_semaphore_t task = dispatch_semaphore_create(0);
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
} else {
//int i=0;
//NSLog(#"%#",responseObject);
header=responseObject;
dispatch_semaphore_signal(task);
}
}];
[dataTask resume];
dispatch_semaphore_wait(task, DISPATCH_TIME_FOREVER);
return header;
You've taken an inherently asynchronous method, dataTaskWithRequest, and have made it synchronous (i.e. it blocks the thread from which it was called). Get rid of all of that semaphore stuff. It's a really bad pattern, anyway.
I assume you did that because you wanted to return data from a network call. You shouldn't do that. You should use a completion handler pattern, e.g.
For example, let's imagine your method currently looks like:
- (id)performRequest:(NSURLRequest *)request {
__block id header;
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
} else {
header = responseObject;
}
dispatch_semaphore_signal(semaphore);
}];
[dataTask resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return header;
}
You should change it to a void return type, and add a completionHandler parameter:
- (void)performRequest:(NSURLRequest *)request completionHandler:(void (^)(id _Nullable responseObject, NSError * _Nullable error))completionHandler {
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
completionHandler(nil, error);
} else {
completionHandler(responseObject, nil);
}
}];
[dataTask resume];
}
And you'd call it like so:
[self performRequest:request completionHandler:^(id responseObject, NSError *error) {
// use responseObject and error here
}];
// but not here

How to show JSON data in UIView labels

About every single tutorial and example on the internet I see shows how to fetch JSON from some url and show it in Tableview. This is not my problem I know how to do that with AFNetworking framework or with native APIs.
My problem is that after I have downloaded the JSON, I want to show some of it in my UIView labels. I have actually succeeded doing this when I was trying to find a way around NSURLSession inability to cache in iOS 8. But I didn't realize that it was synchronous.
Factory.m
+ (Factory *)responseJson
{
static Factory *shared = nil;
shared = [[Factory alloc] init];
NSHTTPURLResponse *response = nil;
NSString *jsonUrlString = [NSString stringWithFormat:#"http://urltojson.com/file.json"];
NSURL *url = [NSURL URLWithString:[jsonUrlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSError *error = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:10.0];
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error) {
NSLog(#"error");
} else {
//-- JSON Parsing
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:nil];
//NSLog(#"Result = %#",result);
shared.responseJson = result;
}
return shared;
}
My question is that is it possible to use for example AFNetwoking to do the same thing? Am I missing some method that I need to call like in case of a TableView
[self.tableView reloadData];
I would like to use that framework because I need to check Reachability and it seems to implement it already.
Edit as asked to show more code
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self factoryLoad];
[self setupView];
}
- (void)factoryLoad
{
Factory *shared = [Factory responseJson];
self.titles = [shared.responseJson valueForKeyPath:#"data.title"];
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
- (void)setupView
{
self.issueTitleLabel.text = [self.titles objectAtIndex:0];
}
There are a couple oddities in the code you posted.
Factory, which appears to be a singleton class, should be instantiated inside a dispatch_once to ensure thread safety.
In ViewController.m, you are calling factoryLoad on the main thread, which is subsequently calling sendSynchronousRequest on the main thread. Apple's NSURLConnection Documentation warns against calling this function on the main thread as it blocks the thread, making your application unresponsive to user input.
You should not be passing in nil as the error parameter in NSJSONSerialization JSONObjectWithData:.
In your case I would recommend separating the fetching of data from the construction of your singleton object.
Factory.m
+(Factory *)sharedFactory {
static Factory *sharedFactory = nil;
dispatch_once_t onceToken;
dispatch_once(&onceToken, {
sharedFactory = [[Factory alloc] init];
});
}
-(void)fetchDataInBackgroundWithCompletionHandler:(void(^)(NSURLResponse *response,
NSData *data,
NSError *error)
completion {
NSHTTPURLResponse *response = nil;
NSString *jsonUrlString = [NSString stringWithFormat:#"http://urltojson.com/file.json"];
NSURL *url = [NSURL URLWithString:[jsonUrlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:10.0];
NSOperationQueue *downloadQueue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request
queue:downloadQueue
completionHandler:completion];
}
Now you should be able to create a reference to the data with a guarantee that the download request has finished and thus the data will exist.
ViewController.m
-(void)factoryLoad {
[[Factory sharedFactory] fetchDataInBackgroundWithCompletionHandler:^(void)(NSURLResponse *response, NSData *data, NSError *error){
if(!error) {
NSError *error2;
NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error2];
if(error2){ /* handle error */ }
self.titles = [serializedData valueForKeyPath:#"data.title"];
[Factory sharedFactory].responseJSON = serializedData;
}
else {
// handle error
}
}];
}
This will guarantee that the download has completed before you try to access any of the downloaded information. However, I've left a few things out here, including any sort of activity indicator displaying to the user that the app is doing something important in the background. The rest is, uh, left as an exercise to the reader.
Ok I took a deeper investigation into Morgan Chen's answer and how to block.
The example code took some modification but I think It works as it should and is better code.
In Factory.m
+ (Factory *) sharedInstance
{
static Factory *_sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
-(void)fetchDataInBackgroundWithCompletionHandler: (void(^)(BOOL success, NSDictionary *data, NSError *error)) block
{
NSString * baseURL = #"http://jsonurl.com/file.json";
AFHTTPRequestOperationManager * manager = [[AFHTTPRequestOperationManager alloc] init];
__weak AFHTTPRequestOperationManager *weakManager = manager;
NSOperationQueue *operationQueue = manager.operationQueue;
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(#"internet!");
[weakManager.requestSerializer setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[operationQueue setSuspended:NO];
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(#"no internet");
[weakManager.requestSerializer setCachePolicy:NSURLRequestReturnCacheDataElseLoad];
[operationQueue setSuspended:YES];
break;
default:
break;
}
}];
[manager.reachabilityManager startMonitoring];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager GET:baseURL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (responseObject && [responseObject isKindOfClass:[NSDictionary class]]) {
block(YES, responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { // invalid request.
NSLog(#"%#", error.localizedDescription);
block(NO, nil, error);
}];
}
In ViewController.m I call this method on viewDidLoad
-(void)factoryLoad
{
[[Factory sharedInstance] fetchDataInBackgroundWithCompletionHandler:^(BOOL success, NSDictionary *data, NSError *error) {
if (success) {
NSLog(#"we have stuff");
self.responseData = data;
self.titles = [self.responseData valueForKeyPath:#"data.title"];
[self setupView];
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
}];
}