return data from sendAsynchronousReques - objective-c

Objective c: I have a serviceClass connecting to a db with NSURLConnection and sendAsynchronousRequest.
I want to be able to use this class method and return the db-data to any other classmethod requesting it.
But since the sendAsynchronousRequest is returning void - how can i do this?
I cant get my head around it at all atm. Blocks? But how...
Please help
atm I am creating a user object directly in this codechunk bellow:
[NSURLConnection
sendAsynchronousRequest:urlRequest
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *urlResponse,
NSData *data,
NSError *error) {
if ([data length] > 0 && error == NULL){ // do success or error call with true or false
NSMutableData *incomingData;
if(!incomingData) {
incomingData = [[NSMutableData alloc]init];
}
[incomingData appendData:data];
NSString *string = [[NSString alloc]initWithData:incomingData
encoding:NSUTF8StringEncoding];
//incomingData = nil;
// create dictionary from json
NSData *jsondata = [NSData dataWithData:incomingData];
NSMutableDictionary *userDictionary = [NSJSONSerialization JSONObjectWithData:jsondata
options:NSJSONReadingMutableContainers
error:NULL];
// create user object
User *user = [User userFromDictionary:userDictionary];
NSLog(#"user back to object successfull!! %#", user);
}
}

You can use property for incomingData and then use that property to pass data to other methods/class.
#property (nonatomic, retain) NSMutableData * incomingData;
Now you can use self.incomingData to set value, and you need to return. You can use it in other methods, and when you need to pass this to other class, you can pass it.

Related

Objective C variables in Async block does not change the Class variable value

I try to use my parsed JSON values from async block in other methods and viewDidLoad but, it returns the value inside the block, but in other methods it just return null
here is my Viewcontroler.m file code:
#import "ViewController.h"
#interface ViewController ()
#end
NSString *buttonText;
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self URLconnection];
NSLog(#"this is value: %#", buttonText);
}
- (void)URLconnection {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#“XXXXXXXXXX"]];
NSURLSessionDataTask *task = [[self getURLSession]
dataTaskWithRequest:request completionHandler:^( NSData *data,
NSURLResponse *response, NSError *error )
{
dispatch_async( dispatch_get_main_queue(),
^{
// parse returned JSON array
NSError *jsonError;
NSArray *parsedJSONArray = [NSJSONSerialization
JSONObjectWithData:data options:NSJSONReadingMutableContainers
error:&jsonError];
NSLog( #"%#", [parsedJSONArray valueForKey:#"data" ] );
NSDictionary *dictionary = [parsedJSONArray
valueForKey:#"data"];
NSLog(#"this is dictionary %#",
dictionary[#"buttonText"]);
buttonText = dictionary[#"buttonText"];
} );
}];
[task resume];
}
#end
in the code above the NSLOG in the block return value but NSLOG in the viewDidLoad return null
Because the code in the block is executed asynchronously, it means when you are trying to print the value in the viewDidLoad() it has no value, so it is printing Null, which is right. When Block ends it's process and get's the data from the server, it will have the value in your variable and that prints the correct response.
Hope it clears.

objective c invisible array out of function param request

I'm triyng to get array from server. But it doesn't work. I'm mean in the function everything is okay with my array but out of it it's null. How to fix?
for(int i=1; i<5; i++){
NSString *category = [NSString stringWithFormat:#"%d",i];
NSString *encrypt = #"encrypt=93mrLIMApU1lNM619WzZje4S9EeI4L2L";
NSString *latitude = #"latitude=32.794044";
NSString *longtitude = #"longitude=34.989571";
NSString *params = [NSString stringWithFormat:#"%#&category=%#&%#&%#&area=CENTER",
encrypt,category,latitude,longtitude];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"http://admin.t-club.co.il/api/get-buissness"]];
NSData *postBody = [params dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:postBody];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError)
{
if(!connectionError)
{
_myDict =[NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
_tmpArray = [_myDict objectForKey:#"result"];
NSLog(#"my array %#",_tmpArray);//here array isn't null
}
}];
[_myArray addObjectsFromArray:_tmpArray];
}
NSLog(#"my array %#",_tmpArray);//here is null
It looks like what you're aiming for is to make several async requests in sequence. This can be done by adding a little abstraction.
First, a method that makes just one request and provides a dictionary in response of the parsed JSON result...
- (void)makeRequestWithParams:(NSString *)params completion:(void (^)(NSDictionary *, NSError *))completion {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"http://admin.t-club.co.il/api/get-buissness"]];
NSData *postBody = [params dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:postBody];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
if(!connectionError) {
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
completion(dictionary, nil);
} else {
completion(nil, connectionError);
}
}];
}
Please note that the NSURLConnnection methods have been replaced by NSSession, so this code will need to change to that soon.
Now something that calls that first method over and over. This one takes an array of request parameters as input, fills a mutable array with the dictionary results, and calls a completion block when its done...
- (void)makeManyRequestsWithParams:(NSArray *)arrayOfParams fillingArray:(NSMutableArray *)result completion:(void (^)(BOOL))completion {
if (arrayOfParams.count == 0) return completion(YES);
NSString *nextParams = arrayOfParams[0];
[self makeRequestWithParams:nextParams completion:^(NSDictionary *dictionary, NSError *error) {
if (!error && dictionary) {
[result addObject:dictionary];
NSArray *remainingParams = [arrayOfParams subarrayWithRange:NSMakeRange(1, arrayOfParams.count-1)];
[self makeManyRequestsWithParams:remainingParams fillingArray:result completion:completion];
} else {
completion(NO);
}
}];
}
Finally, your original loop's job is now limited to just assembling the parameters. Once those are in an array, call to make the requests...
- (void)test {
NSMutableArray *arrayOfParams = [NSMutableArray array];
for(int i=1; i<5; i++){
NSString *category = [NSString stringWithFormat:#"%d",i];
NSString *encrypt = #"encrypt=93mrLIMApU1lNM619WzZje4S9EeI4L2L";
NSString *latitude = #"latitude=32.794044";
NSString *longtitude = #"longitude=34.989571";
NSString *params = [NSString stringWithFormat:#"%#&category=%#&%#&%#&area=CENTER",
encrypt,category,latitude,longtitude];
[arrayOfParams addObject:params];
}
NSMutableArray *result = [NSMutableArray array];
[self makeManyRequestsWithParams:arrayOfParams fillingArray:result completion:^(BOOL success) {
if (success) {
NSLog(#"all done, result is %#", result);
} else {
NSLog(#"sadness");
}
}];
// don't expect results to be ready here. they won't be.
// see how they are logged above in the completion block?
}
NSURLConnection sendAsynchronousRequest is asynchronous meaning it will be moved to a background thread and execution will continue without waiting for the task to complete. So by the time it gets to your bottom NSLog, the request will still be processing and the value of _tmpArray will be null.
You can use sendSynchronousRequest to have the request complete before moving on.
http://codewithchris.com/tutorial-how-to-use-ios-nsurlconnection-by-example/#synchronous

Best memory management practice for blocks and completion handlers

I have the following code in my app, and there is a memory leak with the 'dict' object. So I have a few questions about the best practice for this after the code:
// Convert JSON to dict
NSError *error = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
// If error return nil
if (error)
completion(nil, [self handleSerializationError:error]);
else if (![UsefulFunctions objectContainsData:dict[#"data"]])
completion(nil, NO);
// No error then return dict
else
completion(dict, NO);
});
About the code: The completion handler passes back the dict object which is then used to create core data entities (hence the main thread) based upon the calling function. The data being serialised is from an NSURLConnection. So the questions are as follows:
1) Is this the correct practice for passing back data in a completion handler?
2) Which function should take care of the memory management, should it be the calling class?
3) Is it worth wrapping this in an auto-release pool, or is that not how they are supposed to be used (kind of a separate question).
4) Can anyone see any obvious reasons from this function alone why the dict is retained, or is it purely down to the calling class?
Thanks for any help
EDIT (Whole Function), just to confirm I am using ARC and this function is called from the main thread:
- (void)downloadJSONFromURL:(NSURL *)url withCompletionHandler:(void (^)(id object, BOOL retry))completion
{
// Check URL
if (url)
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *authToken = [NSString stringWithFormat:#"token %#", [UsefulFunctions returnActiveAPIKey]];
[request setValue:authToken forHTTPHeaderField:#"Authorization"];
[request setHTTPMethod:#"GET"];
// Create an asynch request, don't want to hold up main queue
[NSURLConnection sendAsynchronousRequest:request queue:[self operationQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
// If data exists
if (data)
{
// Convert JSON to dict
NSError *error = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
// If error return nil
if (error)
completion(nil, [self handleSerializationError:error]);
else if (![UsefulFunctions objectContainsData:dict[#"data"]])
completion(nil, NO);
// No error then return dict
else
completion(dict, NO);
});
}
// If error
else if (connectionError)
{
//NSLog(#"Connection Error: %#, Code: %lu", connectionError.description, (long)connectionError.code);
dispatch_async(dispatch_get_main_queue(), ^{
// Return nil
completion(nil, [self handleConnectionError:connectionError]);
});
}
}];
}
}

Weird NSZombie error in nsmutuablearray

I had faced a weird error for a few days ago. Luckly, i had solved this error about in 4-5 day. However, i just wonder that the error why happen.
First of all, i will describe the situation with same sample of code.
#interface SampleViewController ()
#property (nonatomic, strong) NSMutableArray *selectedIssue;
#property (nonatomic, strong) NSOperationQueue *queueJSON;
#property (nonatomic, strong) NSOperationQueue *queueImage;
#end
#implementation SampleViewController
/** blah blah blah*/
- (void) fillIssueArrayMethodWithIssueId:(NSInteger) selectedId {
NSString *requestURL = [NSString stringWithFormat:#"%#Issues/Get/?id=%d", kAPIRootURL, selectedId];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:requestURL]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:kNetworkTimeOut];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFJSONResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id response) {
NSData *JSONData = [NSData dataWithContentsOfFile:filePath
options:NSDataReadingMappedIfSafe
error:nil];
NSMutableArray *responseObject = [NSJSONSerialization JSONObjectWithData:JSONData
options:NSJSONReadingMutableContainers
error:nil];
if(responseObject) {
if([responseObject isKindOfClass:[NSArray class]]) {
_selectedIssue = [NSMutableArray arrayWithArray:responseObject];
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { }];
[_queueJSON addOperation:op];
}
-(void)startDownloadImages {
NSArray *objPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *strPath = [objPaths lastObject];
if(!_queueImage)
_queueImage = [[NSOperationQueue alloc] init];
_queueImage.maxConcurrentOperationCount = 3;
NSBlockOperation *completionOperation = [NSBlockOperation new];
for (__block NSDictionary *object in _selectedIssue) {
// Photos
NSString *imgURL = object[#"ImageUrl"];
NSString *strImageFile = [NSString stringWithFormat:#"%#_%#", object[#"Id"], [imgURL lastPathComponent]];
__block NSString *strImagePath = [NSString stringWithFormat:#"%#/Images/Issues/%#", strPath, strImageFile];
NSURLRequest *request4Images = [NSURLRequest requestWithURL:[NSURL URLWithString:imgURL]];
AFHTTPRequestOperation *operation4Images = [[AFHTTPRequestOperation alloc] initWithRequest:request4Images];
[operation4Images setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id rawResult) {
BOOL isImageWrited = [rawResult writeToFile:strImagePath options:NSDataWritingAtomic error:nil];
if(isImageWrited) {
NSLog(#"Image Write Success : %#", operation.request.URL);
} else {
NSLog(#"Image Write Error: %#", imageWriteError.description);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Image Write Failed : %# : %ld", operation.request.URL, (long)operation.response.statusCode);
}];
[completionOperation addDependency:operation4Images];
}
[_queueImage addOperations:completionOperation.dependencies waitUntilFinished:NO];
[_queueImage addOperation:completionOperation];
}
#end
Here is problem;
In application lifecycle, fillIssueArrayMethodWithIssueId method get JSON data to mutuablearray first. Then start to download images by getting image URL from the array.
Each second time, i want to access the _selectedIssue in fillIssueArrayMethodWithIssueId method, my app got crash (SIGSEGV)
In according to long investigation, _selectedUssue become instantly zombie object when StartDownloadImages method finished. However in this method i have never reallocated this array. I have just read values in this array. I am sure that the array become zombie object in this method so the issue fixed when removed "__block" in for loop.
So the question, __block type how could affect the _selectedIssue arrays object ?
I just want to know what i am missing...
By the way,i have been tried in StartDownloadImages method before the for loop, i had create temporary array and this temp array just initiated with _selectedIssue mutableCopy or just copy. However, i again faced with zombie problem.

iOS App Wait for HTTP Response when calling a method?

I am trying to call a class method that takes a string and posts it to a site to receive a JSON response(among some other variables I have stored in the DataClass). I am stuck trying to return the data in the form of a response and can not at this point even NSLog the returned data. The question is, now that I have called my class method, how can the class method wait to return a response from an HTTP POST to return data? Once I return my JSON, I can expand it to a dictionary and process from there. Help is appreciated :)
Class Method:
//
// APISample.m
//
// Created by Sam on 1/6/13.
// Copyright (c) 2013 Sam. All rights reserved.
//
#import "APISample.h"
#import "DataClass.h"
#implementation APISample
#synthesize first_name = _first_name;
#synthesize last_name = _last_name;
#synthesize profile_pic_url = _profile_pic_url;
#synthesize responseData;
-(id)init
{
self = [super init];
return self;
NSLog(#"Loaded APISample and fetching");
}
+(id)getDataAboutUser:(NSString *)user_request_id;
{
DataClass *userdata=[DataClass getInstance];
NSLog(#"Loaded APISample and fetching %#", user_request_id);
NSMutableURLRequest *user_fetch_details = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://10.0.23.161/users/user_fetch_details.php"]];
[user_fetch_details setHTTPMethod:#"POST"];
NSMutableString *postString = [NSMutableString stringWithString:#"id=123"];
[postString appendString:#"&userrequest_id="];
[postString appendString:[userdata.str_userid copy]];
[postString appendString:#"&user_id="];
[postString appendString:[userdata.str_userid copy]];
[postString appendString:#"&identifier="];
[postString appendString:[userdata.str_identifier copy]];
[user_fetch_details setValue:[NSString stringWithFormat:#"%d", [postString length]] forHTTPHeaderField:#"Content-length"];
[user_fetch_details setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
NSURLConnection *connection=[[NSURLConnection alloc] initWithRequest:user_fetch_details delegate:self];
NSMutableData *responseData=[NSMutableData data];
[responseData appendData:[NSURLConnection connection:didReceiveData];
if (connection) {
// Create the NSMutableData that will hold
// the received data
// receivedData is declared as a method instance elsewhere
NSMutableData *responseData=[NSMutableData data];
} else {
// inform the user that the download could not be made
}
NSLog(#"Received Data %#", [[NSString alloc] initWithData:responseData encoding:NSASCIIStringEncoding]);
return [[NSString alloc] initWithData:responseData encoding:NSASCIIStringEncoding];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
NSString *receivedDataString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
if ([receivedDataString isEqualToString: #"error"]) {
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"An error has occured. The application will now exit. Unexpected Response!"
delegate:nil
cancelButtonTitle:#"Close"
otherButtonTitles:nil];
[errorAlert show];
exit(0);
}else{
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSString *firstnameResponse = [json objectForKey:#"first_name"];
NSString *lastnameResponse = [json objectForKey:#"last_name"];
NSString *profile_pic_urlResponse = [json objectForKey:#"profile_pic_url"];
NSLog(#"didReceiveData %# analysed " , firstnameResponse);
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"connectionDidFinishLoading");
NSLog(#"Succeeded! Received %d bytes of data",[self.responseData length]);
}
#end
I receive no data in the log after "Received Data" and do not see my error messages. Thanks you
The design pattern you are describing is called a CallBack. You need to be notified of an event occurring at some point in the future. In objective-c there are 4 main forms of callbacks.
Target Action Pairing (this is what is used with buttons, and things of the like. "When this button is pressed notify my target, and tell them to execute this action")
Delegation (you are using a form of delegation in the code above with NSURLConnection. When you see the word 'delegate' i want you to think 'helper object'. You are saying, "hey NSURLConnection, when important events happen, i would like you to tell this delegate (helper object) about these events)
Notifications (these are used a lot when dealing with model objects changing)
and finally... the one i would recommend for your purposes...
Blocks.
A block is a very cool variable. Most variables hold data. A block is a variable which holds code to be executed at some point in the future. So in your situation you could pass a completion block along with your method getDataAboutUser:(NSString *)user_request_id. So it would look like this.
getDataAboutUser:(NSString*)string withCompletion:(void(^)(NSData *finishedData))cBlock
Store that cBlock as an instanceVar. Then when your NSURLConnection finishes downloading all its data, you will execute the cBlock, passing in the finished data as an argument.
Blocks are a fairly complicated things if you have not used them before, so i would reccomend taking 20 minutes and reading this.
Since you need your method to wait for a response before returning, you can use NSURLConnection's convenience class method sendSynchronousRequest to carry out a synchronous request instead of creating and managing an NSURLConnection instance asynchronously.
So instead of your [[NSURLConnection alloc] init...] line you can do this:
NSURLResponse *response = nil;
NSError *error = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:user_fetch_details returningResponse:&response error:&error];
Following which you can immediately parse the JSON from responseData instead of doing that in the connection:didReceiveData delegate.
Edit: Just saw user698846's suggestion to modify your method signature to take a completion block. That's also a good and possibly cleaner way to approach your problem if you are at liberty to change your method signature (i.e. nobody is requiring your function to return synchronously). Either way, sendSynchronousRequest is possibly the easiest way out and there's no shame in it especially if there's nothing your app nor your user can do while waiting for the request to complete.
This is some code:
NSURLResponse *response = nil;
NSError *error = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:user_fetch_details returningResponse:&response error:&error];