I am using an NSMutableArray to store items in a list form from a web service. How do I read the data out of my array to display a random quote.
As i am trying to avoid hitting the webservice twice or am i over complicating it?
NSInteger randomInt = arc4random_uniform(3);
NSString *baseUrl = #"http://movie-quotes.herokuapp.com/api/v1/quotes";
NSString *webServiceUrl= [NSString stringWithFormat:#"%#/%ld", baseUrl,(long)randomInt];
randomQuotes = nil;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// Load the JSON string from our web serivce (in a background thread)
NSDictionary * dictionary = [JSONHelper loadJSONDataFromURL:webServiceUrl];
dispatch_async(dispatch_get_main_queue(), ^{
randomQuotes = [NSMutableArray array];
// Iterate through the array of quotes records in the dictionary
for (NSDictionary * oneQuote in dictionary)
{
Quotes* newQuotes =[[Quotes alloc] init];
// Add our new Customer record to our NSMutableArray
newQuotes.Quote = [oneQuote objectForKey:#"content"];
newQuotes.FilmName = [oneQuote objectForKey:#"film"];
[randomQuotes addObject:newQuotes];
}
});
});
You can get a random object out of an array like this.
NSUInteger randomIndex = arc4random_uniform(theArray.count);
[theArray objectAtIndex: randomIndex];
As long as you keep the array of quotes around, you won't need to hit the web service again.
Related
I have a nested for loop. I want to optimise it using GCD concurrency. Tried:
Replace both for loop with gcd_apply.
Replaced only inner for loop with gcd_apply.
I want to get final out put same without change in order.
-(NSMutableArray*)getsportNotificationObjectsByGroup{
NSMutableArray* notificationObjects = [NSMutableArray array];
NSString* defaultNoteValue;
defaultNoteValue = [WADeviceAndAppSettingPopUpManager getDefaultNoteValueBasedOnSystemAlertButtonAction];
for(WACategoryInfo * categoryInfo in self.allSportSCategories){
#autoreleasepool {
NSMutableArray *subArray = [[NSMutableArray alloc] init];;
for (NSDictionary *dictNotificationTags in categoryInfo.notificationTags) {
NSString *tagName, *tagCode;
BOOL isTagDefaultEnabled = NO;
tagName = [dictNotificationTags objectForKeyWithNullCheck:ktagName];
tagCode = [dictNotificationTags objectForKeyWithNullCheck:ktagCode];
isTagDefaultEnabled = [[dictNotificationTags objectForKeyWithNullCheck:kisTagDefaultEnabled] boolValue];
//********value computation*******//
NSString* val = #"";
if ([WAConfigLoader sharedInstance].isCollegeStyleApp) {
val = [WADeviceAndAppSettingPopUpManager getValueForSwitch:dictNotificationTags];
}
//*****value computation*********//
NSDictionary *sportDetail = #{
kname:tagName,
ktype:kswitch,
kid:tagCode,
kvalue : val,
ksportStringId:categoryInfo.sportStringID,
kTitleKey : tagName
};
[subArray addObject:sportDetail];
}
NSMutableDictionary *aNewDict = [[NSMutableDictionary alloc] init];
[aNewDict setObject:subArray forKey:kdata];
[aNewDict setObject:categoryInfo.sportTitle forKey:ksection];
[notificationObjects addObject:aNewDict];
}
}
return notificationObjects;
}
It's not mandatory to use GCD only, my concern is to maximise loop performance.
If you replace the inner loop with dispatch_apply, you have to take concern of these drawbacks:
You have to sync the call to [subArray addObject]
The order in subArray is indetermined
Therefore, I would recomment to only put the outer loop in a dispatch_apply work item, because it's results are added to a dictionary which does not provide any order. Nevertheless, you'd have to sync [notificationObjects addObject] anyway.
Just remember to dispatch_apply into a concurrent queue.
- (void)retrieveData
{
NSURL * url = [NSURL URLWithString:#"***/connection.php"];
NSData * data = [NSData dataWithContentsOfURL:url];
_json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
_questionsArray = [[NSMutableArray alloc]init];
for (int i = 0; i < _json.count; i++)
{
NSString * qID = [[_json objectAtIndex:i] objectForKey:#"id"];
NSString * qTitle = [[_json objectAtIndex:i] objectForKey:#"question_title"];
NSString * qA = [[_json objectAtIndex:i] objectForKey:#"A"];
NSString * qB = [[_json objectAtIndex:i] objectForKey:#"B"];
NSString * qC = [[_json objectAtIndex:i] objectForKey:#"C"];
NSString * qD = [[_json objectAtIndex:i] objectForKey:#"D"];
NSString * qAnswer = [[_json objectAtIndex:i] objectForKey:#"question_answer"];
question * myQuestion = [[question alloc] initWithQuestionID:qID andQuestionName:qTitle andQuestionA:qA andQuestionB:qB andQuestionC:qC andQuestionD:qD andQuestionAnswer:qAnswer];
[_questionsArray addObject:myQuestion];
}
[_json enumerateObjectsUsingBlock:^(NSDictionary *questionDictionary, NSUInteger idx, BOOL *stop) {
//Here I'm treating the index like an NSNumber, if your code is expecting a string instead use
//#(idx).stringValue
[_questions setObject:questionDictionary forKey:#(idx)];
//or the modern equivalent
//questions[#(idx)] = questionDictionary;
//If you want to use your 'questions class' then create one and put it into the array instead of the dictionary pulled from the array.
}];
NSLog( #"%#", _questions );
}
Logs (null)
random gobledy gook so my post isn't mostly code
random gobledy gook so my post isn't mostly code
random gobledy gook so my post isn't mostly code
random gobledy gook so my post isn't mostly code
random gobledy gook so my post isn't mostly code
If I understand your question correctly it becomes something like this
self.questions = .... //I assume this is the array you reference 'question' objects that is created by your retrieve data method
//this used to be created by pulling an object out of your questions dictionary with the key i interpreted as a string.
//now that it's an array you should be able to just reference it by index, assuming they were inserted in order
//I'm also assuming that what comes out of the aray is a question object given the code you provided with the signature
//- (id) initWithQuestionID: (NSString *) qID andQuestionName: (NSString *) qName andQuestionA: (NSString *) qA andQuestionB: (NSString *) qB andQuestionC: (NSString *) qC andQuestionD: (NSString *) qD andQuestionAnswer: (NSString *) qAnswer
Question *nextQuestion = self.questions[i];
self.answer = nextQuestion.questionAnswer;
self.questionLabel.text = nextQuestion.questionLabel;
//and so on
I also suggest the following edit to replace your for loop. It uses a for in loop instead, this saves you from having to keep track of an index and looks cleaner. It also helps so you don't keep repeating the [_json objectAtIndex:i] chunk of code. I also use modern objective-c syntax to access the dictionary.
for (NSDictionary *questionDictionary in _json)
{
NSString * qID = questionDictionary[#"id"];
NSString * qTitle = questionDictionary[#"question_title"];
...
question * myQuestion = [[question alloc] initWithQuestionID:qID andQuestionName:qTitle andQuestionA:qA andQuestionB:qB andQuestionC:qC andQuestionD:qD andQuestionAnswer:qAnswer];
[_questionsArray addObject:myQuestion];
}
If you need the key along with the object in the dictionary then you can clean it up in a similar way with the enumerateObjectsUsingBlock
[_json enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
//your code here
}];
EDIT
It sounds like what your really wanting to do is to pull down your JSON but keep all your other code the way it was when you were using a dictionary that you got from your plist. So in this case you want your parsing function to return a dictionary instead of an array. If that's the case it's worth sidestepping into computer science for a second.
NSDictionarys are also known as a hash, map, symbol table, or associative array. Some languages (such as Lua) don't have an array collection like NSArray, they only have dictionaries. From a dictionary you can create many of the other collections your used to like arrays (and sets too). Heres how it works: Instead of an ordered collection of elements with an index, you place the items in a dictionary and use what would have been the index as the key, and the value becomes, well, the value. For example an array and it's equivalent associative array (aka dictionary):
NSArray *array = #[#"hello", #"world", #"!"];
NSDictionary *dictionary = #{#(1): #"hello",
#(2): #"world",
#(3): #"!"};
This is exactly what your doing when you load in the data from your plist because the first elements key is 0 followed by another dictionary, and I'm supposing that the next element in the list is 1 followed by another dictionary. Inside your parsing function it becomes
NSMutableDictionary *questions = [[NSMutableDictionary alloc] init];
[_json enumerateObjectsUsingBlock:^(NSDictionary *questionDictionary, NSUInteger idx, BOOL *stop) {
//Here I'm treating the index like an NSNumber, if your code is expecting a string instead use
//#(idx).stringValue
[questions setObject:questionDictionary forKey:#(idx)];
//or the modern equivalent
//questions[#(idx)] = questionDictionary;
//If you want to use your 'questions class' then create one and put it into the array instead of the dictionary pulled from the array.
}];
This of course assumes that your api is going to return the JSON questions in the order you want.
How do I change this function to load content randomly from the API? I want to pass in a random integer and change the end point of the url.
NSString* WebServiceURL =#"http://movie-quotes.herokuapp.com/api/v1/quotes";
to this
http://movie-quotes.herokuapp.com/api/v1/quotes/2
where the number at the end is the quote number
Here is my relevant code:
-(void)LoadQuotesRandom
{
randomQuotes = nil;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// Load the JSON string from our web serivce (in a background thread)
NSDictionary * dictionary = [JSONHelper loadJSONDataFromURL:WebServiceURL];
dispatch_async(dispatch_get_main_queue(), ^{
randomQuotes = [NSMutableArray array];
// Iterate through the array of Customer records in the dictionary
for (NSDictionary * oneQuote in dictionary)
{
Quotes* newQuotes =[[Quotes alloc] init];
// Add our new Customer record to our NSMutableArray
newQuotes.Quote = [oneQuote objectForKey:#"content"];
newQuotes.FilmName =[oneQuote objectForKey:#"film"];
[randomQuotes addObject:newQuotes];
}
});
});
}
To restate more simply, I think you want a url string with a random integer suffix.
NSInteger randomInt = arc4random_uniform(SOME_MAX);
NSString *baseUrl = #"http://movie-quotes.herokuapp.com/api/v1/quotes";
NSString *webServiceUrl = [NSString stringWithFormat:#"%#/%ld", baseUrl, randomInt];
You'll need to #define SOME_MAX for the largest quote index one can request.
I suggest you separate this completely for the code that makes the request. Get one piece working that does any request given a string describing the url, and a separate piece that generates these url strings.
SO I have a program which calls the FlickR API, gets the URL's puts them into a dictionary and then assigns them into a table view, using an image view.
NSArray *photos = [self.flickr photosForUser:#"James Kinvig"];
int countAttempts = 0;
[[self.flickr photosForUser:#"James Kinvig"]count];
for (int i = 0; i < [[self.flickr photosForUser:#"James Kinvig"]count]; i++) {
for(NSDictionary *dictionary in photos) {
countAttempts++;
NSString *farmId = [dictionary objectForKey:#"farm"];
NSString *serverId = [dictionary objectForKey:#"server"];
NSString *photoId = [dictionary objectForKey:#"id"];
NSString *secret = [dictionary objectForKey:#"secret"];
self.url= [NSURL URLWithString:[NSString stringWithFormat:#"http://farm%#.staticflickr.com/%#/%#_%#.jpg", farmId, serverId, photoId, secret]];
//NSLog(#"self.url = %#", self.url);
NSLog(#"count = %d", countAttempts);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *imgData = [NSData dataWithContentsOfURL:self.url];
dispatch_sync(dispatch_get_main_queue(), ^{
UIImage *img = [UIImage imageWithData:imgData];
cell.imageView.image = img;
[cell setNeedsLayout];
});
});
}
}
return cell;
}
This is the method it calls, photosForUser:
- (NSMutableArray *) photosForUser: (NSString *) friendUserName
{
NSString *request = [NSString stringWithFormat: #"https://api.flickr.com/services/rest/?method=flickr.people.findByUsername&username=%#", friendUserName];
NSDictionary *result = [self fetch: request];
NSString *nsid = [result valueForKeyPath: #"user.nsid"];
request = [NSString stringWithFormat: #"https://api.flickr.com/services/rest/?method=flickr.photos.search&per_page=%ld&has_geo=1&user_id=%#&extras=original_format,tags,description,geo,date_upload,owner_name,place_url", (long) self.maximumResults, nsid];
result = [self fetch: request];
return [result valueForKeyPath: #"photos.photo"];
}
Which does a fetch to the flickr API.
What is happening though is that is stuck in an eternal loop. Even with the for statement being less than the count, it still eternal loops. I have NSLog'd the count of the FlickR photos and it = 11.
This may have something to do with it, but whenever I press the button to take me to the table view controller, I get a HUGE lag, close to a minute, and nothing is being calculated (photo-wise) as I've done a count++
Thanks
let me understand this.. By the last line of your first block of code, I conclude that that is the uitableview dataSource method, cellForRowAtIndexPath.. what doesn't really makes sense.. you you have a fetch there, you have A loop inside a loop, that is setting many images (by download them) in one single imageView, and this is happening for all your visible cells at the same time. This will never work!
The solution is:
1 - remove this method from the cellForRow, this is not the place to request the images
2 - create another method that will fetch the content
3 - create a method that will do your loops and store the images on the array so you don't need to do that many times, only one..
4 - reload the tableview after you finish the step 3
5 - use the array of images that is already done to set your images by indexPath.row in your cell..
6 - I recommend you to use a Library for imageCache (i.e https://github.com/rs/SDWebImage)
NSArray *photos = [self.flickr photosForUser:#"James Kinvig"];
for (int i = 0; i < [[self.flickr photosForUser:#"James Kinvig"] count]; i++)
{
for (NSDictionary *dictionary in photos)
{
You have two nested loops iterating over the same collection. This turns what should be an O(n) operation into O(n^2) and explains why your process is taking a very long time.
Since the loop bodies never use i, I would fix it by getting rid of the outer loop:
NSArray *photos = [self.flickr photosForUser:#"James Kinvig"];
for (NSDictionary *dictionary in photos)
{
I have a method which downloads some pics from a server. I had the buffer for the downloaded data defined withing async block (NSMutableArray *imgtmp) but haven't figured out how to get the array back out of there. What's the best way to access imgtmp in order to return it's contents, or set an instance variable from it?
I've been looking in the Apple Block docs but I must not be in the right section. Do I need to declare imgtmp using the __block keyword somehow? I tried that but imgtmp was still empty outside the block. Thanks!
EDIT: code updated with working model
- (void) loadImages
{
// temp array for downloaded images. If all downloads complete, load into the actual image data array for tablerows
__block NSMutableArray *imgtmp = [[NSMutableArray alloc] init];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0),
^{
int error = 0;
int totalitems = 0;
NSMutableArray *picbuf = [[NSMutableArray alloc] init];
for (int i=0; i < _imageURLS.count;i++)
{
NSLog(#"loading image for main image holder at index %i",i);
NSURL *mynsurl = [[NSURL alloc] initWithString:[_imageURLS objectAtIndex:i]];
NSData *imgData = [NSData dataWithContentsOfURL:mynsurl];
UIImage *img = [UIImage imageWithData:imgData];
if (img)
{
[picbuf addObject:img];
totalitems++;
}
else
{
NSLog(#"error loading img from %#", [_imageURLS objectAtIndex:i]);
error++;
}
}// for int i...
dispatch_async(dispatch_get_main_queue(),
^{
NSLog(#"_loadedImages download COMPLETE");
imgtmp = picbuf;
[_tvStatus setText: [NSString stringWithFormat:#"%d objects have been retrieved", totalitems]];
NSLog (#"imgtmp contains %u images", [imgtmp count]);
});// get_main_queue
});// get_global_queue
}
You're hitting that "final" NSLog call before any of your block code has executed. All your image loading stuff is wrapped in a dispatch_async. It is executed asynchronously whereas the NSLog is called right away.
I think the best thing for you to do is to pass imgtmp along to some persistent object. Perhaps your view controller can have a property like:
#property (nonatomic, copy) NSArray *images;
and you can assign that in the same block where you assign the text to _tvStatus.