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
Related
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];
}
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 ...
}];
}
My Code works just fine. What I need help, or clarification on is Nested NSURLSessionDataTask instances.
I'm making two asynchronously calls, the second call is dependent on the first.
So I make the first NSURLSessionDataTask (firstUrlCall) call which returns an array of objects. For each object in my array I then call the second NSURLSessionDataTask (secondUrlCall) and pass in a dataID.
As I mentioned before, it works. I just see alot of lines repeated and REPEATED CODE IS NOT SEXY!!!
So is there anything I can do to prevent this catastrophe? I need my code to be SEXY!
#property (nonatomic, strong) NSURLSession *Session;
FIRST CALL
-(void) firstUrlCall {
NSString *urlString = #"https://api.FIRSTURLCALL.com";
NSURLSessionDataTask *dataTask = [session
dataTaskWithURL:[NSURL URLWithString:urlString]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!error) {
NSDictionary *returnData = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
[returnData enumerateKeysAndObjectsUsingBlock:^(id dataID, id obj, BOOL *stop) {
/*
-->here is where I call secondUrlCall<--
*/
[self secondUrlCall:dataID];
}];
}
});
}];
[dataTask resume];
}
SECOND CALL
-(void) secondUrlCall:(NSString *)dataID {
NSString *urlString = [NSString stringWithFormat:#"https://api.SECONDURLCALL.com?dataID=%#",dataID];
NSURLSessionDataTask *dataTask = [session
dataTaskWithURL:[NSURL URLWithString:urlString]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
if ([[json objectForKey:#"type"] isEqualToString:#"sexy"]) {
[tableArray addObject:json];
// Reload table data
[self.tableView reloadData];
}
}
});
}];
[dataTask resume];
}
PS: Sorry if you were offended from my extensive use of the word SEXY :)
Oh my goodness! What if the network is intermittent or goes down half way through?
I would take the results of the first call and put each one into an operation queue, then when processing each operation if it fails you can re-queue it.
I am trying to pull some data from my local node server. The server is getting the get request and logging it, but for some reason my iOS app will not execute any of the code that I have in the completion handler. Here is the code:
- (IBAction) buttonPressed{
NSURL *url = [NSURL URLWithString:#"http://127.0.0.1:3000/"];
NSURLSessionDataTask *dataTask =
[self.session dataTaskWithURL:url
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error){
nameLabel.text = #"yay!";
/*
if (!error){
nameLabel.text = #"noerr";
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;
if (httpResp.statusCode == 200){
NSError *jsonErr;
NSDictionary *usersJSON =
[NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error:&jsonErr];
if (!jsonErr){
// nameLabel.text = usersJSON[#"username"];
nameLabel.text = #"nojerr";
}
else{
nameLabel.text = #"jsonErr";
}
}
}
else{
nameLabel.text = #"Err";
}
*/
}];
[dataTask resume];
}
When the program is run, the nameLabel is not changed to "yay". However if I try to change the nameLabel before the NSURLSessionDataTask line, it changes.
NSURLSessionDataTask runs in a background thread. To update anything in the user interface such as labels, buttons, table views, etc, you must do so on the main thread. If you want to update the label text from the completionHandler block then you need to update the label in the main thread like so:
dispatch_sync(dispatch_get_main_queue(), ^{
nameLabel.text = #"yay!";
});
try this magic:
static NSURLSession* sharedSessionMainQueue = nil;
if(!sharedSessionMainQueue){
sharedSessionMainQueue = [NSURLSession sessionWithConfiguration:nil delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
}
NSURLSessionDataTask *dataTask =
[sharedSessionMainQueue dataTaskWithURL:url completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error){
//now will be on main thread
}];
[dataTask resume];
This gives you the original behavior of NSURLConnection with the completing handler on the main thread so you are safe to update the UI. However, say you would like to parse the download or do some heavy processing, in that case you might benefit from the completion handler on the operation queue's background thread and then using dispatch_sync to the main thread as a final step.
I have been using NSURLConnection's sendAsynchronousRequest:queue:completionHandler: method which is great. But, I now need to make multiple requests in a row.
How can I do this while still using this great asychronous method?
There's lots of ways you can do this depending on the behavior you want.
You can send a bunch of asynchronous requests at once, track the number of requests that have been completed, and do something once they're all done:
NSInteger outstandingRequests = [requestsArray count];
for (NSURLRequest *request in requestsArray) {
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[self doSomethingWithData:data];
outstandingRequests--;
if (outstandingRequests == 0) {
[self doSomethingElse];
}
}];
}
You could chain the blocks together:
NSMutableArray *dataArray = [NSMutableArray array];
__block (^handler)(NSURLResponse *response, NSData *data, NSError *error);
NSInteger currentRequestIndex = 0;
handler = ^{
[dataArray addObject:data];
currentRequestIndex++;
if (currentRequestIndex < [requestsArray count]) {
[NSURLConnection sendAsynchronousRequest:[requestsArray objectAtIndex:currentRequestIndex]
queue:[NSOperationQueue mainQueue]
completionHandler:handler];
} else {
[self doSomethingElse];
}
};
[NSURLConnection sendAsynchronousRequest:[requestsArray objectAtIndex:0]
queue:[NSOperationQueue mainQueue]
completionHandler:handler];
Or you could do all the requests synchronously in an ansynchronous block:
dispatch_queue_t callerQueue = dispatch_get_current_queue();
dispatch_queue_t downloadQueue = dispatch_queue_create("Lots of requests", NULL);
dispatch_async(downloadQueue, ^{
for (NSRURLRequest *request in requestsArray) {
[dataArray addObject:[NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]];
}
dispatch_async(callerQueue, ^{
[self doSomethingWithDataArray:dataArray];
});
});
});
P.S. If you use any of these you should add some error checking.