Return NSMutableArray from completionHandler (Objective C) - ios

I did post request to a web service and get response. I convert the response to NSMutableArray. My response in NSURLSessionDataTask and now I want to return NSMutableArray for using outside of NSURLSessionDataTask. Here is my code:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"url"]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
NSString *postString = #"params";
NSString *postLength = [NSString stringWithFormat:#"%lu", ( unsigned long )[postString length]];
[request setValue:postLength forHTTPHeaderField:#"Content-Length" ];
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *task = [[self getURLSession] dataTaskWithRequest:request completionHandler:^( NSData *data, NSURLResponse *response, NSError *error )
{
dispatch_async( dispatch_get_main_queue(),
^{
NSDictionary *dicData = [NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error:nil];
NSDictionary *values = [dicData valueForKeyPath:#"smth"];
NSArray * dataArr = [dicData objectForKey:#"smth"];
NSArray * closeArr = [values objectForKey:#"0"];
NSUInteger dataCount = [dataArr count] ;
NSUInteger closeCount = [closeArr count] ;
NSMutableArray * newData = [NSMutableArray new] ; //<-- THIS ARRAY
for(int i = 0 ; i<dataCount && i<closeCount ; i++)
{
NSMutableDictionary * temp = [NSMutableDictionary new] ;
NSString * dataString = [dataArr objectAtIndex:i];
NSString * closeString = [closeArr objectAtIndex:i];
[temp setObject:dataString forKey:#"smth"];
[temp setObject:closeString forKey:#"smth"];
[newData addObject:temp];
}
NSLog(#"%#", newData);
} );
}];
[task resume];
I need return NSMutableArray * newData = [NSMutableArray new];
Long story short, I get json data from web service, then transform it to appropriate json format for displaying it in the chart(I use shinobicontrols). Now, I display chart with the help of local json. Here is the code:
_timeSeries = [NSMutableArray new];
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"AppleStockPrices" ofType:#"json"];
NSData* json = [NSData dataWithContentsOfFile:filePath];
NSArray* data = [NSJSONSerialization JSONObjectWithData:json
options:NSJSONReadingAllowFragments
error:nil];
for (NSDictionary* jsonPoint in data) {
SChartDataPoint* datapoint = [self dataPointForDate:jsonPoint[#"smth1"]
andValue:jsonPoint[#"smth2"]];
[_timeSeries addObject:datapoint];
}
When I am trying to implement this code in NSURLSessionDataTask, the chart doesn't appear. So I need return NSMutableArray(where my data in appropriate json format) outside.
How can I do this? Any ideas?
Thank you!

You cannot add a return statement in the completion handler since it may not be called if the session return an error. As a matter of fact, Xcode will give you an "Incompatible pointer type" error if you try to do that.
The best way I found to go around it is to set up your newData array as a property and make it available to the other methods in the class. If a specific method will need to handle this array when the url session task is over, you can call that method from the completion handler or use a notification.
Alternatively, if for some reason you do not want to user a class property, you can use the NSNotificationCenter, and pass the newData to the listener in the notification object.
EDIT: code example using a property
If you need the newData outside the completion block, an easy way is declaring the array as a property. This is not the only approach and probably not the best. But it doesn't add much complexity to the code.
You can declare the newData array in your .m class file:
#interface "whatever class you are using"
#property (nonatomic, strong) NSMutableArray *newData;
#end
Your can initialize the array in your viewDidLoad method:
- (void)viewDidLoad {
_newdata = [[NSMutableArray alloc] init];
}
In Your completion block, you would remove the initialization and add data to the array.
//NSMutableArray * newData = [NSMutableArray new] ; // REMOVE THE INTIALIZATION
for(int i = 0 ; i<dataCount && i<closeCount ; i++) {
NSMutableDictionary * temp = [NSMutableDictionary new] ;
NSString * dataString = [dataArr objectAtIndex:i];
NSString * closeString = [closeArr objectAtIndex:i];
[temp setObject:dataString forKey:#"smth"];
[temp setObject:closeString forKey:#"smth"];
[_newData addObject:temp];
}
Again, this is not the best approach but it is relatively simple. One problem with doing this, is that since you have a strong reference to the array, if you need to perform another URL call, and load new data into the array, you will need to empty it. Otherwise, the new data will be appended to the old one. You can do it by calling [_newData removeAllObjects]; before the URL session is called again.
EDIT 2: changed code based on the user comment:
- (void)loadChartData {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"url"]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
NSString *postString = #"params";
NSString *postLength = [NSString stringWithFormat:#"%lu", ( unsigned long )[postString length]];
[request setValue:postLength forHTTPHeaderField:#"Content-Length" ];
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *task = [[self getURLSession] dataTaskWithRequest:request completionHandler:^( NSData *data, NSURLResponse *response, NSError *error ) {
dispatch_async( dispatch_get_main_queue(), ^{
NSDictionary *dicData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
NSDictionary *values = [dicData valueForKeyPath:#"smth"];
NSArray * dataArr = [dicData objectForKey:#"smth"];
NSArray * closeArr = [values objectForKey:#"smth0"];
NSUInteger dataCount = [dataArr count] ;
NSUInteger closeCount = [closeArr count] ;
NSMutableArray * newData = [NSMutableArray new] ;
for(int i = 0 ; i<dataCount && i<closeCount ; i++) {
NSMutableDictionary * temp = [NSMutableDictionary new] ;
NSString * dataString = [dataArr objectAtIndex:i];
NSString * closeString = [closeArr objectAtIndex:i];
[temp setObject:dataString forKey:#"smth"];
[temp setObject:closeString forKey:#"smth"];
[newData addObject:temp];
}
NSLog(#"%#", newData);
_timeSeries = [NSMutableArray new];
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"AppleStockPrices" ofType:#"json"];
NSData* json = [NSData dataWithContentsOfFile:filePath];
NSArray* data = [NSJSONSerialization JSONObjectWithData:json options:NSJSONReadingAllowFragments error:nil];
for (NSDictionary* jsonPoint in data) {
SChartDataPoint* datapoint = [self dataPointForDate:jsonPoint[#"smth"] andValue:jsonPoint[#"smth"]];
[_timeSeries addObject:datapoint];
}
});
}];
[task resume];
// Code here has a good chance of being executed before the completion block is complete
// _newdata = [[NSMutableArray alloc] init];
// NSLog(#"%#", _newdata);
}

Related

Retrieving data from NSDictionary and putting it into an NSMutableArray

I am trying to get a value from an NSDictionary and put it into an NSMutableArray. Here's some of my code for example.
- (IBAction)pressedButton:(id)sender {
NSInteger numberOfResults = 3;
NSString *searchString = #"EMINEM";
NSString *encodedSearchString = [searchString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *finalSearchString = [NSString stringWithFormat:#"https://itunes.apple.com/search?term=%#&entity=song&limit=%li",encodedSearchString,(long)numberOfResults];
NSURL *searchURL = [NSURL URLWithString:finalSearchString];
dispatch_queue_t iTunesQueryQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(iTunesQueryQueue, ^{
NSError *error = nil;
NSData *data = [[NSData alloc] initWithContentsOfURL:searchURL options:NSDataReadingUncached error:&error];
if (data && !error) {
NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSArray *array = [JSON objectForKey:#"results"];
NSMutableArray *arrayTracks;
for (NSDictionary *bpDictionary in array) {
[arrayTracks addObject:[bpDictionary objectForKey:#"trackName"]];
// _author = [bpDictionary objectForKey:#"trackName"];
dispatch_async(dispatch_get_main_queue(), ^{
self.label.text = [arrayTracks objectAtIndex:0];
// self.label2.text = _author;
// NSLog(#"%#", [array objectAtIndex:0]);
});
}
}
});
}
As you can see from the above, it does not properly put the value into the array. If I set the _author to [bpDictionary objectForKey:#"trackName"] then it does work, but it goes through it all and sets the label as the last in the array. The above code outputs nothing (null).
Leverage foundation to your advantage, there's no need to iterate the array manually.
To get an array of track names you can simply use valueForKey: or valueForKeyPath:
NSArray *trackNames = [JSON valueForKeyPath:#"results.trackName"];
That being said, I urge you to perform networking tasks with networking API. You should never be using NSData to retrieve JSON. Stick to NSURLSession, it's cleaner and designed for networking tasks.
[[[NSURLSession sharedSession] dataTaskWithURL:itunesSearchURL
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// now you have a properly downloaded JSON
//and you even have access to the URL response headers and any errors.
}]resume];
Edit
As an aside, you never initialize your array of trackNames so calling addObject on a nil value will have no effect.

NSDictionary dictionaryWithObject issue

I'm trying to get Title data from the server but unfortunately I'm getting nothing as it is shown in the NSLog below. Shouldn't I get the Title dictionary? Please where would be my issue?
While I want to set that data in the UITableVeiw
- (void)getNews{
NSURL *url = [NSURL URLWithString:#"http://www.example.ashx"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
if (data.length > 0 && connectionError == nil)
{
NSDictionary *getData = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
if([[[getData objectForKey:#"Success"] stringValue] isEqualToString:#"1"]){
[dataNewsArray addObjectsFromArray:[[greeting objectForKey:#"Response"] objectForKey:#"Datasource"]];
}
NSDictionary *aDict = [NSDictionary dictionaryWithObject:dataHaberlerArray forKey:#"Title"];
NSLog(#"Check %#", aDict);
}
}];
}
NSLog result as like this;
Check {
Title = (
{
Content = "";
Date = "13.10.2014";
Time = "01:17:34";
Title = "example";
},
NSLog for dataNewsArray
2014-10-13 13:40:14.828 new_8[8742:346345] Check (
{
Content = " ";
Date = "13.10.2014";
Time = "01:38:53";
Title = "*test*";
},
write the below code to get one title
[[[aDict objectForKey:#"Title"] objectAtIndex:0] objectForKey:#"Title"]];
for all the titles use the below code
NSMutableArray *titleArray = [[NSMutableArray alloc] init];
for (int i=0; i<[[aDict objectForKey:#"Title"] count]; i++) {
[titleArray addObject:[[[aDict objectForKey:#"Title"] objectAtIndex:i] objectForKey:#"Title"]];
}
The above solution provide the answer, but there is some other way using predicate to get the data

How to use asynchronous data

I'm trying to fetch some data from a remote file. I'm creating an _items array inside the asynchronous request block, which is actually full of data, but when calling the _items anywhere else in my controller is still empty.
I'm not sure but i'm guessing that as the data are loaded asyncronously, when calling _items is still empty. So how how can i use the array with data, after the async task is completed?
- (IBAction)fetchEvent
{
_items = [[NSMutableArray alloc] initWithCapacity:5];
NSString *link = [NSString stringWithFormat:#"url...?id=%#", self.eId];
NSURL *url = [NSURL URLWithString:link];
NSURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
if (data.length > 0 && connectionError == nil)
{
NSDictionary *match = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
NSArray *odds = [match objectForKey:#"odds"];
OddsItem *item;
for ( NSDictionary *books in odds ) {
item = [[OddsItem alloc] init];
item.oddsBook = [odds objectForKey:#"book"];
[_items addObject:item];
}
}
}];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self fetchEvent];
}
mutable data is dangerous in multi thread situation. NSArray is better.
#property (nonatomic, copy) NSArray *items;
block can capture outside variables but can not modify it directly. In completion block try this
if (data.length > 0 && connectionError == nil)
{
NSDictionary *match = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
NSArray *odds = [match objectForKey:#"odds"];
NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:5];
for ( NSDictionary *books in odds ) {
OddsItem *item = [[OddsItem alloc] init];
item.oddsBook = [odds objectForKey:#"book"];
[items addObject:item];
}
self.items = items;
}

IOS - Array created in async returning only one object

I'm really having trouble figuring this out. I'm parsing JSON into an array asynchronously. Running NSLog on the async function prints out an array with multiple objects, which is what I want. But when I run the NSLog on the returned array in the ViewController it only prints out the last object of the array. I then run a count on it and there is, in fact, only 1 object in the array. Why is it only returning an array with one object from an array with multiple objects? Below is my code. Thanks for any input you might have.
Function performed asynchronously
- (NSArray *)locationsFromJSONFile:(NSURL *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:30.0];
NSURLResponse *response;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
NSError *error;
NSMutableDictionary *allTeams = [NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
if( error )
{
NSLog(#"%#", [error localizedDescription]);
}
else {
team = allTeams[#"10"];
for ( NSDictionary *teamArray in team )
{
teams = [NSArray arrayWithObjects: teamArray[#"team"], nil];
}
}
return teams;
}
ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
JSONLoader *jsonLoader = [[JSONLoader alloc] init];
NSURL *url = [[NSBundle mainBundle] URLForResource:#"teams" withExtension:#"json"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
teams = [jsonLoader locationsFromJSONFile:url];
int i = [teams count];
NSString *string = [NSString stringWithFormat:#"%d", i];
NSLog(#"%#", string);
});
}
You need to add object to the existing array instead of reinitalizing it everytime
teams = [NSArray arrayWithObjects: teamArray[#"team"], nil];
should be
else {
team = allTeams[#"10"];
teams = [NSMutableArray array];
for ( NSDictionary *teamArray in team )
{
[teams addObject:teamArray[#"team"]];
}
Also you are sending synchronous request using this code
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
you should use the sendAsynchronous methods. Synchronous request can be terminated by the OS itself and can lead to some confusion and bugs later on

How to enumerate through NSArray of NSDictionaries with a block call inside the loop?

I get an self.usersArray with 2 elements in the format:
(
{
userCreated = "2012-01-05 12:27:22";
username = Simulator;
},
{
userCreated = "2013-01-01 14:27:22";
username = "joey ";
}
)
This is gotten in a completion block after which I call another method to fetch points for these 2 users through a helper class:
-(void)getPoints{
self.usersPointsArray = [[NSMutableArray alloc] init];
for (NSDictionary *usersDictionary in self.usersArray) {
[SantiappsHelper fetchPointsForUser:[usersDictionary objectForKey:#"username"] WithCompletionHandler:^(NSArray *points){
if ([points count] > 0) {
[self.usersPointsArray addObject:[points objectAtIndex:0]];
}
NSLog(#"self.usersPointsArray %#", self.usersPointsArray);
}];
}
}
The final self.usersPointsArray log looks like:
(
{
PUNTOS = 5;
username = Simulator;
},
{
PUNTOS = 2;
username = joey;
}
)
But the problem is that the way the call for points is structured, the self.usersPointsArray is returned twice, each time with an additional object, due to the for loop, I know.
Here is the Helper class method:
+(void)fetchPointsForUser:(NSString*)usuario WithCompletionHandler:(Handler2)handler{
NSURL *url = [NSURL URLWithString:#"http://myserver.com/myapp/readpoints.php"];
NSDictionary *postDict = [NSDictionary dictionaryWithObjectsAndKeys:usuario, #"userNa", nil];
NSData *postData = [self encodeDictionary:postDict];
// Create the request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%d", postData.length] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
__block NSArray *pointsArray = [[NSArray alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
// Peform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"HTTP Error: %d %#", httpResponse.statusCode, error);
return;
}
return;
}
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
pointsArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
if (handler)
handler(pointsArray);
});
}
I cannot use the self.usersPointsArray with the initial objects, only with the finalized object. It wont always be 2 elements, i actually dont know how many it will be.
What would be the way to structure it so I get a final call when the self.usersPointsArray is complete and then I reload my tableview?
I think of your problem as a standard consumer-producer problem. You can create a queue count for the amount of items that will be processed (int totalToProcess=self.usersArray.count). Each time the completion handler is hit, it will do totalToProcess--. When totalToProcess reaches 0 you have processed all of the elements in your queue and can refresh your table.
If I understand your question correctly I believe this solves your problem. If not, hopefully I can with a bit more information.

Resources