Am having a major problem with an NSMutableArray, I'm probably missing something really obvious - have probably been looking at it so much that I just can't see it.
Am reading some Tweets and then using the results to populate an NSMutableArray :
#synthesize testArray;
- (void)viewDidLoad{
[super viewDidLoad];
testArray = [[NSMutableArray alloc] init];
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:#"http://search.twitter.com/search.jsonq=kennedy&with_twitter_user_id=true&result_type=recent"]
parameters:nil requestMethod:TWRequestMethodGET];
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
if ([urlResponse statusCode] == 200) {
// The response from Twitter is in JSON format
// Move the response into a dictionary
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
NSArray *results = [dict objectForKey:#"results"];
//Loop through the results
for (NSDictionary *tweet in results) {
tweetStore *tweetDetails = [[tweetStore alloc] init];
NSString *twitText = [tweet objectForKey:#"text"];
//Save the tweet to the twitterText array
tweetDetails.name = #"test";
tweetDetails.date = #"test";
tweetDetails.tweet = twitText;
[testArray addObject:tweetDetails];
}
tweetStore *retrieveTweet = (tweetStore*)[testArray objectAtIndex:0];
NSLog(#"tweet is: %#", retrieveTweet.tweet);
//NSLog(#"Array is: %#", testArray); - *can* view Array etc here
}
else {
NSLog(#"Twitter error, HTTP response: %i", [urlResponse statusCode]);
}
}
];
NSLog(#"test array: %#", testArray); //Array is now empty......
}
I can view the array within the code where I've marked it, retrieve the objects I want etc, it works perfectly. But as soon as I try to access or display anything from the array outside of this then it seems to be empty. Any help or pointers would be appreciated.
The problem is that the code in the block is performed in an asynchronous way, so it is executed after the NSLog code.
The code executed follows this flow:
First you perform the initialization:
[super viewDidLoad];
testArray = [[NSMutableArray alloc] init];
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:#"http://search.twitter.com/search.jsonq=kennedy&with_twitter_user_id=true&result_type=recent"]
parameters:nil requestMethod:TWRequestMethodGET];
You start the request:
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) { <block_code> } ];
You print array information and it is empty (correct):
NSLog(#"test array: %#", testArray);
Now the asynchronous request ends and the code in the block is executed: so the array is populated correctly only in the block code.
Related
I make a call to the youtube API to get the title of a video. I then want to display the title of the video on the screen in a table. How do I access the title after the block has finished executing?
Here's the code to get the title
-(void)getVideoTitle:(NSString *)urlStr success:(void (^)(NSDictionary *responseDict))success{
urlStr = [NSString stringWithFormat:#"https://www.googleapis.com/youtube/v3/videos?part=contentDetails%%2C+snippet%%2C+statistics&id=%#&key={API_KEY}",urlStr];
NSURL *url = [[NSURL alloc] initWithString:urlStr];
// Create your request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// Send the request asynchronously
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *connectionError) {
// Callback, parse the data and check for errors
if (data && !connectionError) {
NSError *jsonError;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
if (!jsonError) {
success(jsonResult);
// NSLog(#"Response from YouTube: %#", jsonResult);
}
}
}] resume];
}
Here's how I call the above function:
[self getVideoTitle:#"zB4I68XVPzQ" success:^(NSDictionary *responseDict){
NSArray *itemsArray = [responseDict valueForKey:#"items"];
NSDictionary *item = itemsArray[0];
NSDictionary* snippet = [item valueForKey:#"snippet"];
NSString *title = [snippet valueForKey:#"title"];
}];
How do I get access the title variable outside the block after the block has finished executing?
I have tried the following with no luck
dispatch_async(dispatch_get_main_queue(), ^{
[self updateMyUserInterfaceOrSomething];
});
In your code:
NSString* recievedTitle __block = nil; //title is here, after block below run
[self getVideoTitle:#"zB4I68XVPzQ" success:^(NSDictionary *responseDict){
NSArray *itemsArray = [responseDict valueForKey:#"items"];
NSDictionary *item = itemsArray[0];
NSDictionary* snippet = [item valueForKey:#"snippet"];
recievedTitle = [snippet valueForKey:#"title"]; //here you write it
// or
NSString *title = [snippet valueForKey:#"title"];
[self updateInterfaceWithTitle: title]
}];
///
- (void)updateInterfaceWithTitle:(NSString*)title{
//use title here
}
I have a client that runs their search functionality on their website through cloudsearch. I have been going through the documentation for days, and haven't been able to make a successful search request. I created an NSMutableRequest object, and am running that request through the AWSSignature method [signature interceptRequest:request]; but my task.result is coming back (null).
Here is my code:
AWSTask *task = [signature interceptRequest:request];
[task continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) {
NSLog(#"task.result fromSearch:%#", task.result);
NSData *responseData = task.result;
NSString* newStr = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(#"newStr:%#", newStr);
NSLog(#"task.error:%#", task.error);
return nil;
}];
Am I on the right track, or is there a better way to do this through the aws iOS sdk?
To put a little more flesh on the bones of Robert's comment, I did it with some help from AFNetworking like so:
#import <AWSCore/AWSSignature.h>
#import <AWSCore/AWSService.h>
#import <AWSCore/AWSCategory.h>
#import <AWSCore/AWSCredentialsProvider.h>
#import <AWSCore/AWSTask.h>
#import "AFNetworking.h"
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = [[NSOperationQueue alloc] init];
}
- (void)performSearch {
AWSAnonymousCredentialsProvider* credentialsProvider = [[AWSAnonymousCredentialsProvider alloc] init];
NSString* searchHost = #"<CloudSearchEndPoint>.eu-west-1.cloudsearch.amazonaws.com";
NSString* query = [self.searchTerms aws_stringWithURLEncoding];
NSURL* searchURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://%#/2013-01-01/search?q=%#", searchHost, query]];
AWSEndpoint* endpoint = [[AWSEndpoint alloc] initWithURL:searchURL];
AWSSignatureV4Signer* signer = [[AWSSignatureV4Signer alloc] initWithCredentialsProvider:credentialsProvider endpoint:endpoint];
NSMutableURLRequest* mutableRequest = [[NSMutableURLRequest alloc] initWithURL:searchURL];
AWSTask* task = [signer interceptRequest:mutableRequest];
[task continueWithBlock:^id(AWSTask* _Nonnull t) {
if (t.error) {
NSLog(#"Error: %#", t.error);
} else if (t.completed) {
NSLog(#"Result is %#", t.result);
}
AFJSONRequestOperation* operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:mutableRequest success:^(NSURLRequest* request, NSHTTPURLResponse* response, id JSON) {
NSLog(#"Success fetching results!");
if (JSON) {
NSDictionary* hitsContainer = [JSON objectForKey:#"hits"];
NSArray* hits = [hitsContainer objectForKey:#"hit"];
NSMutableArray* allResults = [[NSMutableArray alloc] initWithCapacity:hits.count];
for (NSDictionary* hit in hits) {
NSDictionary* fields = [hit objectForKey:#"fields"];
[allResults addObject:fields];
}
self.searchResults = allResults;
[self.tableView reloadData];
}
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Failure fetching search results :-( %#", error);
}
];
[self.queue addOperation:operation];
return nil;
}];
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);
}
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.
Ok, I'm pretty new to this and been struggling with what you guys may feel is quite an easy exercise. I have trawled high and low and cannot find a good tutorial or walkthrough on how to do this. Basically, I am using the code below to obtain tweets and I only want the 'text' part of the tweet. How do I extract it out of the NSDictionary in order to use the 'text' key in a tableview? I have tried [dict objectForKey:#"text"] but it does not work - 'dict' does not seem to contain a 'text' attribute. Thanks in advance for any help.
// Do a simple search, using the Twitter API
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:
#"http://search.twitter.com/search.json?q=iOS%205&rpp=5&with_twitter_user_id=true&result_type=recent"]
parameters:nil requestMethod:TWRequestMethodGET];
// Notice this is a block, it is the handler to process the response
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
if ([urlResponse statusCode] == 200)
{
// The response from Twitter is in JSON format
// Move the response into a dictionary and print
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
NSLog(#"Twitter response: %#", dict);
}
else
NSLog(#"Twitter error, HTTP response: %i", [urlResponse statusCode]);
}];
Yes there is an objectForKey#"text" but it is an array which means that every entry (tweet) has text (and several other attributes). So we've to loop through the tweets to get FOR every tweet the text.
In your .h file
NSMutableArray *twitterText;
In your .m file
Do this somewhere in viewdidload
twitterText = [[NSMutableArray alloc] init];
And now we can loop through your results. Paste this code where you've NSLog(#"Twitter response: %#", dict);
NSArray *results = [dict objectForKey#"results"];
//Loop through the results
for (NSDictionary *tweet in results)
{
// Get the tweet
NSString *twittext = [tweet objectForKey:#"text"];
// Save the tweet to the twitterText array
[twitterText addObject:(twittext)];
And for your cells in your tableView
cell.textLabel.text = [twitterText objectAtIndex:indexPath.row];
I think that should be working.