I have an iOS app with a function which is in charge of making an asynchronous network request. The request itself works just fine, but the problem I am having is with the function return statement which is causing errors.
Here is my function:
-(NSArray *)get_data:(NSString *)size {
// Set up the data request.
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://mywebsite.com/info.json"]];
NSURLRequest *url_request = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// Begin the asynchronous data loading.
[NSURLConnection sendAsynchronousRequest:url_request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error == nil) {
// Convert the response JSON data to a dictionary object.
NSError *my_error = nil;
NSDictionary *feed = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&my_error];
if (feed != nil) {
// Store the returned data in the data array.
NSArray *topping_data;
for (int loop = 0; loop < [[feed objectForKey:#"toppings_data"] count]; loop++) {
NSString *size_name = [NSString stringWithFormat:#"%#", [[[feed objectForKey:#"toppings_data"] objectAtIndex:loop] valueForKey:#"Size"]];
if ([size_name isEqualToString:size]) {
topping_data = [[feed objectForKey:#"toppings_data"] objectAtIndex:loop];
}
}
return topping_data;
}
else {
return #[#"no data"];
}
}
else {
return #[#"no data"];
}
}];
}
I am getting the following error message on the line of code [NSURLConnection sendAsync....:
Incompatible block pointer types sending 'NSArray *(^)(NSURLResponse
*__strong, NSData *__strong, NSError *__strong)' to parameter of type 'void (^ _Nonnull)(NSURLResponse * _Nullable __strong, NSData *
_Nullable __strong, NSError * _Nullable __strong)'
What am I doing wrong here?
All I am trying to avoid, is the function returning BEFORE the asynchronous request has completed. Otherwise the function will not return any data, which is not what I want.
Thanks for your time, Dan.
best way to return data in async block is make a block callback as argument of function and callback return value here:
- (void)get_data:(NSString *)size completionHandler:(void (^)(NSArray *array))completionHandler {
// ...
[NSURLConnection sendAsynchronousRequest:url_request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// ...
completionHandler(array);
// ...
}];
}
use :
[self get_data:someString completionHandler:^(NSArray *array) {
// process array here
}];
The block returns nothing:
void ^(NSURLResponse *, NSData *, NSError *)
So you cannot return things:
return #[#"no data"];
The code that calls the block is not interested in what it returns; if you want to store state then add an instance variable or call a method.
Change
[NSURLConnection sendAsynchronousRequest:url_request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
to
[NSURLConnection sendAsynchronousRequest:url_request queue:queue completionHandler:^(NSURLResponse *_Nullable response, NSData *_Nullable data, NSError *_Nullable error)
Related
Right now I am developing I little class that has a method for sending a POST request. This method is intended for returning a ResponseModel (which basically has two ivars: code, message), this model is going to be map from response.
I am using dataTaskWithRequest:urlRequest completionHandler: method. Like this:
+ (void)sendPOSTRequest1:(id)data withResponse:(void (^) (ResponseModel * data) )taskResponse {
NSError *error = nil;
NSMutableURLRequest * urlRequest = [self getRequestObject];
[urlRequest setHTTPMethod:#"POST"];
NSData * requestData = [self encodeAndEncrypt:data];
[urlRequest setHTTPBody:requestData];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session
dataTaskWithRequest:urlRequest
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
ResponseModel * responseModel = [NSKeyedUnarchiver
unarchivedObjectOfClass:[ResponseModel class]
fromData:data
error:&error];
taskResponse(responseModel);
}];
[dataTask resume];
}
And call the method this way:
DummyModel * dummy = [[DummyModel alloc] init];
__block ResponseModel * result = [[ResponseModel alloc] init];
[HTTPRequest sendPOSTRequest1:dummy withResponse:^(ResponseModel *data) {
result = data;
NSLog(#"data %#",data);
}];
// It`s not sure that the asyncronous request has already finished by this point
NSLog(#"POST result : %#",result);
My problem is that I do not want to execute a code in call back block because I need to wait for the response in order to return a ResponseModel and whoever is implementing this can receive the Model and make other stuff.
I been researching for using NSURLConnection because it has a method for executing Synchronous request, but now It´s deprecated, so I been wondering: is It a way I can wait for a response using what I have in the code ?
You can use GCD to implement synchronous request like this:
swift code
public static func requestSynchronousData(request: URLRequest) -> Data? {
var data: Data? = nil
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
let task = URLSession.shared.dataTask(with: request, completionHandler: {
taskData, _, error -> () in
data = taskData
if data == nil, let error = error {print(error)}
semaphore.signal()
})
task.resume()
_ = semaphore.wait(timeout: .distantFuture)
return data
}
Objective-C code
+ (NSData *)requestSynchronousData:(NSURLRequest *)request {
__block NSData * data = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable taskData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(#"%#", error);
}
data = taskData;
dispatch_semaphore_signal(semaphore);
}];
[task resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return data;
}
You can use dispatch_async to handle UI interaction inside the block
DummyModel * dummy = [[DummyModel alloc] init];
__block ResponseModel * result = [[ResponseModel alloc] init];
[HTTPRequest sendPOSTRequest1:dummy withResponse:^(ResponseModel *data) {
result = data;
dispatch_async(dispatch_get_main_queue(), ^{
// handle some ui interaction
});
NSLog(#"data %#",data);
}];
I have a method that returns a string usually locally, but with a backup from the Web. I was retrieving some JSON using dataWithContentsOfUrl but want to switch to using a Session object which is better for the UI and also--if I am not mistaken--allows the server to set a sessionId on the phone, however, I'm struggling with the async issue.
With the old code, I just returned the JSON but I'm struggling with how to do this for the asynchronous result. I can't change the calling method which returns a string. What can I do with the asynchronous Api call to use the data that is retrieved?
async:
-(void)getAsyncAnswerFor:(NSString*) str {
NSString *surl = [NSString stringWithFormat: #"https://~.com//api.php?q=%#",str];
NSURL *url = [NSURL URLWithString:surl];
NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession]
dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//HOW DO I PASS THIS BACK TO THE CALLING METHOD OR IS THAT IMPOSSIBLE
}];
[downloadTask resume];
}
sync
-(NSString*)getAnswerFor:(NSString*) str {
NSError *error;
NSString *surl = [NSString stringWithFormat: #"https://~.com//api.php?q=%#",str];
NSData *data = [NSData dataWithContentsOfURL: [NSURL URLWithString:surl]];
NSMutableArray *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
//process JSON
if (error) {
return #"";
}
return #"processed JSON";
}
Would appreciate any suggestions.
If what I want to do is totally impossible, is it possible to set a sessionID on the phone without the Session object? I know setting a session ID is is not the greatest approach, but I'm trying to avoid a lot of authentication overhead.
You can pass a block to your asynchronous function and then call it when the url session completion handler is called. This is a trivial example:
- (void)doSomethingWithBlock:(void (^)(double, double))block {
...
block(21.0, 2.0);
}
I lifted this ^^ from the Apple Docs but you might be able to do something like this: (Note: I didn't check this in a compiler!)
-(void)getAsyncAnswerFor:(NSString*) str completion:(void (^)(NSData, NSURLResponse, NSError))block {
NSString *surl = [NSString stringWithFormat: #"https://~.com//api.php?q=%#",str];
NSURL *url = [NSURL URLWithString:surl];
NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession]
dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
block(data, response, error);
}];
[downloadTask resume];
}
You'll need to be careful if you try to reference self anywhere in the blocks.
I have a method, the method have return the nsdata value, but I don't known how to get the return value from NSURLSessionDataTask block. and how to call the getDownloadFileData methods.Code for task is :-
caller:
NSData *getFileDataResult = [self getDownloadFileData:pathString];
method:
- (NSData*) getDownloadFileData : (NSString*) filePath {
NSURLSessionDataTask *downloadFile = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:filePath] completionHandler:^(NSData *fileData, NSURLResponse *response, NSError *error){
// .....
// fileData should return out.
[downloadFile resume];
});
// I want to return the fileData after download completion.
// how to return?
}
Have anyone can give me a hand?
Thank you very much.
Please check my answer, I hope this helpful
- (NSData *)getDownloadFileData:(NSString *)filePath {
__block NSData *responseData = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSURLSessionDataTask *downloadFile = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:filePath] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
responseData = data;
dispatch_semaphore_signal(sema);
}];
[downloadFile resume];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return responseData;
}
- (void)whereToCall {
// Because to prevent the semaphore blocks main thread
dispatch_queue_t myQueue = dispatch_queue_create("com.abc", 0);
dispatch_async(myQueue, ^{
NSData *data = [self getDownloadFileData:#"urlString"];
});
}
- (void)betterGetDownloadFileData:(NSString *)filePath completion:(void (^)(NSData * __nullable data))completion {
NSURLSessionDataTask *downloadFile = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:filePath] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (completion) {
completion(data);
}
}];
[downloadFile resume];
}
I recommend you should design your code as my suggestion that using block instead.
First of all you have put resume method at wrong place. It should be like this:
- (NSData*) getDownloadFileData : (NSString*) filePath {
NSURLSessionDataTask *downloadFile = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:filePath] completionHandler:^(NSData *fileData, NSURLResponse *response, NSError *error){
// .....
// fileData should return out.
});
[downloadFile resume];//NOTICE THE PLACEMENT HERE
// I want to return the fileData after download completion.
// how to return?
}
Second thing is , you can simply create a NSData variable and assign it the value in completion block rather than passing data back.
OR
Simply do like this in completion block
if(fileData){
return fileData;
}
I was sending 10 requests of url (in a for in loop) to web service
and expected to get 10 JSON format data as return separately
here's my code:
NSArray *reqArray = [10 requests of url inside];
NSMutableArray * saveArray = [prepare to store 10 JSON data in here];
NSInteger counter = [reqArray count];
for (NSURLRequest *request in reqArray) {
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSDictionary *result1 = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSDictionary *result2 = [result1 objectForKey:#"keyForValueIWant"];
[saveArray addObject:result2];
counter -- ;
if (counter == 0) {
NSLog(#"all done");
}
}];
}
the completionHandler never gets executed and I always get nothing in return.
is there anything I misunderstand bout the NSURLConnetion or I did it wrong?
any advice would be appreciated!
EDIT
I found code gets executed after all the other code in viewDidLoad (where I put it) are done
even I tried to wrap it in dispatch_async(dispatch_get_main_queue(), ^{},
what could make it work immediately whenever I call it? sendSynchronous request?
I guess that you are starting those connections off the main loop, try with:
NSArray *reqArray = [10 requests of url inside];
NSMutableArray * saveArray = [prepare to store 10 JSON data in here];
NSInteger counter = [reqArray count];
for (NSURLRequest *request in reqArray) {
dispatch_async(dispatch_get_main_queue(), ^{
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSDictionary *result1 = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSDictionary *result2 = [result1 objectForKey:#"keyForValueIWant"];
[saveArray addObject:result2];
counter -- ;
if (counter == 0) {
NSLog(#"all done");
}
}];
});
}
Because NSURLConnection needs a runloop to execute, if the runloop ends, the connections ends.
I have a block to use as a completionHandler for an NSURLConnection asynchronous request whose main job is to spawn a new asynchronous request using the same block for the new requests completion handler. I am doing this because it effectively solves another problem which is to line up a sequence of asynchronous calls and have them fired off in the background. This is working marvelously for us, but we have a warning I am concerned about. Namely, XCode thinks I have a retain cycle. Perhaps I do, I don't know. I've been trying to learn about blocks over the last couple hours but I haven't found an explanation for recursive uses like mine. The warning states `Block will be retained by the captured object'.
My best guess so far is that a retain cycle is exactly what we want, and that to clear when we are done, we just nillify the block variable, which I'm doing. It doesn't get rid of the error, but I don't mind as long as I'm not leaking memory or doing some black magic I'm not aware of. Can anyone address this? Am I handling it right? If not, what should I be doing?
void (^ __block handler)(NSURLResponse *, NSData *, NSError*);
handler = ^(NSURLResponse *response, NSData *data, NSError *error)
{
[dataArray addObject:data];
if (++currentRequestIndex < [requestsArray count])
{
if (error)
{
[delegate requestsProcessWithIdentifier:_identifier processStoppedOnRequestNumber:currentRequestIndex-1 withError:error];
return;
}
[delegate requestsProcessWithIdentifier:_identifier completedRequestNumber:currentRequestIndex-1]; // completed previous request
[NSURLConnection sendAsynchronousRequest:[requestsArray objectAtIndex:currentRequestIndex]
queue:[NSOperationQueue mainQueue]
completionHandler:handler]; // HERE IS THE WARNING
}
else
{
[delegate requestsProcessWithIdentifier:_identifier completedWithData:dataArray];
handler = nil;
}
};
[NSURLConnection sendAsynchronousRequest:[requestsArray objectAtIndex:0]
queue:[NSOperationQueue mainQueue]
completionHandler:handler];
Try to store your handler block into an instance variable of your view controller (or whatever class you're in).
Assuming that you declare an instance variable named _hander:
{
void (^_handler)(NSURLResponse *, NSData *, NSError*);
}
Change your code to:
__weak __typeof(&*self)weakSelf = self;
_handler = ^(NSURLResponse *response, NSData *data, NSError *error)
{
[dataArray addObject:data];
if (++currentRequestIndex < [requestsArray count])
{
if (error)
{
[delegate requestsProcessWithIdentifier:_identifier processStoppedOnRequestNumber:currentRequestIndex-1 withError:error];
return;
}
[delegate requestsProcessWithIdentifier:_identifier completedRequestNumber:currentRequestIndex-1]; // completed previous request
__strong __typeof(&*self)strongSelf = weakSelf;
[NSURLConnection sendAsynchronousRequest:[requestsArray objectAtIndex:currentRequestIndex]
queue:[NSOperationQueue mainQueue]
completionHandler:strongSelf->_handler];
}
else
{
[delegate requestsProcessWithIdentifier:_identifier completedWithData:dataArray];
}
};
[NSURLConnection sendAsynchronousRequest:[requestsArray objectAtIndex:0]
queue:[NSOperationQueue mainQueue]
completionHandler:_handler];
void (^handler)(NSURLResponse *, NSData *, NSError*);
typeof(handler) __block __weak weakHandler;
weakHandler = handler = ^(NSURLResponse *response, NSData *data, NSError *error)
{
[dataArray addObject:data];
if (++currentRequestIndex < [requestsArray count])
{
if (error)
{
[delegate requestsProcessWithIdentifier:_identifier processStoppedOnRequestNumber:currentRequestIndex-1 withError:error];
return;
}
[delegate requestsProcessWithIdentifier:_identifier completedRequestNumber:currentRequestIndex-1]; // completed previous request
[NSURLConnection sendAsynchronousRequest:[requestsArray objectAtIndex:currentRequestIndex]
queue:[NSOperationQueue mainQueue]
completionHandler:weakHandler]; // HERE IS THE WARNING
}
else
{
[delegate requestsProcessWithIdentifier:_identifier completedWithData:dataArray];
}
};