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.
Related
I've created NSManagedObject* imagesArrayData that stores strings (paths) to images stored in the documents directory:
- (void)setImagesArray:(NSMutableArray *)imagesArray {
NSMutableArray* newImagesArray = [NSMutableArray new];
int i = 1;
for (UIImage* image in imagesArray) {
//generate path to createdFile
NSString* fileName = [NSString stringWithFormat:#"%#_%d", self.name, i];
NSString* filePath = [self documentsPathForFileName:fileName];
//save image to disk
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:filePath atomically:YES];
//add image path to CoreData
[newImagesArray addObject:filePath];
i++;
}
//set new value of imagesArray
imagesArrayData = [NSKeyedArchiver archivedDataWithRootObject:newImagesArray];
I am now not showing pathsToImages in header file, but property imagesArray:
-(NSMutableArray*) imagesArray {
NSMutableArray* images = [NSMutableArray new];
NSArray* imagePaths = [NSKeyedUnarchiver unarchiveObjectWithData:imagesArrayData];
for (NSString* imagePath in imagePaths) {
UIImage *image = [[UIImage alloc] initWithContentsOfFile: imagePath];
[images addObject:image];
}
return images;
The problem is, that whenever I want to get to [imagesArray objectatIndex:xxx], the imagesArray getter is called, and it takes time to recreate the full array. When trying to switch fast between images, the UI slows down.
What would be the elegant way to overcome this problem? Maybe creating another array full of images and updating it from time to time? Maybe something else? Please, help.
One thing you could do is refactor your getter to lazily load the array. If it is already defined, simply return it. If not, build it:
-(NSMutableArray*) imagesArray
{
if (!_imagesArray)
{
NSMutableArray* _imagesArray = [NSMutableArray new];
NSArray* imagePaths =
[NSKeyedUnarchiver unarchiveObjectWithData: imagesArrayData];
for (NSString* imagePath in imagePaths)
{
UIImage *image = [[UIImage alloc] initWithContentsOfFile: imagePath];
[_imagesArray addObject:image];
}
return _imagesArray;
}
I'm not sure what you mean about updating an array of images from time to time.
If your array of image names changes you will need some method to respond to those changes.
I have an app that creates an animation from images stored in a group in my project navigator (not Images.xcassets). This code "works" in that it animates properly, but using imageNamed causes a memory leak because the image files are not getting deallocated.
I can't figure out why adding with imageNamed: works adds images to my array, but imageWithContentsOfFile: doesn't.
A little info on the mechanics of my app:
self.myPhoto is set on the segue from another ViewController. The number of images can vary, so I test to see the file is "there" before adding it to the array.
Filenames follow this naming convention:
"1-1.jpg"
"2-1.jpg"
"2-2.jpg"
"99-1.jpg"
"99-2.jpg"
"99-3.jpg"
"99-4.jpg"
This code works, but the images don't deallocate, causing a memory leak:
- (void)startAnimation {
NSMutableArray *imageArray = [[NSMutableArray alloc] init];
for (int imageNumber = 1; self.myPhoto != nil; imageNumber++) {
NSString *fileName = [NSString stringWithFormat:#"%#-%d.jpg", self.myPhoto, imageNumber];
// check if a file exists
if ([UIImage imageNamed:fileName]) {
// if it exists, add it to the array
[imageArray addObject:[UIImage imageNamed:fileName]];
} else {
// otherwise, don't add image to the array
break;
}
}
self.myImageView.animationImages = imageArray;
self.myImageView.animationDuration = 1.5f;
self.myImageView.animationRepeatCount = 0;
self.myImageView.contentMode = UIViewContentModeScaleAspectFit;
[self.myImageView startAnimating];
}
I ran Instruments on it and saw I had a memory leak emanating from my animation. Digging around a little on StackOverflow, I discovered the manner I'm adding my files to myArray results in images not getting deallocated.
So I tried this, instead:
- (void)startAnimation {
NSMutableArray *imageArray = [[NSMutableArray alloc] init];
for (int imageNumber = 1; self.myPhoto != nil; imageNumber++) {
NSString *fileName = [NSString stringWithFormat:#"%#-%d", self.myPhoto, imageNumber];
// check if a file exists
if ([UIImage imageNamed:fileName]) {
// if it exists, add it to the array
[imageArray addObject:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle]pathForResource:[NSString stringWithFormat:#"%#", fileName] ofType:#"jpg"]]];
NSLog(#"%# added to imageArray", fileName);
} else {
// otherwise, don't add image to the array
break;
}
}
NSLog(#"There are %lu images in imageArray", (unsigned long)imageArray.count);
self.myImageView.animationImages = imageArray;
self.myImageView.animationDuration = 1.5f;
self.myImageView.animationRepeatCount = 0;
self.myImageView.contentMode = UIViewContentModeScaleAspectFit;
[self.myImageView startAnimating];
}
When I do it this way, the page where the animation loads appears, but the images don't get added to my array--the . This is a well-documented issue. Here are a few posts covering this problem:
Dirty Memory because of CoreAnimation and CG image
How do I use imageWithContentsOfFile for an array of images used in an animation?
Thank you for reading. I'm stumped, though I'm confident the resolution to this problem is a startlingly stupid oversight on my part. Prove me right ;)
I made some minor changes out of desperation and I stumbled into the "answer". Comments note where I made changes:
- (void)startAnimation {
NSMutableArray *imageArray = [[NSMutableArray alloc] init];
for (int imageNumber = 1; self.myPhoto != nil; imageNumber++) {
// I set the filename here, adding .jpg to it
NSString *fileName = [NSString stringWithFormat:#"%#-%d.jpg", self.myPhoto, imageNumber];
// check if a file exists
if ([UIImage imageNamed:fileName]) {
// if it exists, add it to the array
// I blanked out ofType, as it's set when I create the local fileName variable
[imageArray addObject:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle]pathForResource:[NSString stringWithFormat:#"%#", fileName] ofType:#""]]];
} else {
// otherwise, don't add image to the array
break;
}
}
self.myImageView.animationImages = imageArray;
self.myImageView.animationDuration = 1.5f;
self.myImageView.animationRepeatCount = 0;
self.myImageView.contentMode = UIViewContentModeScaleAspectFit;
[self.myImageView startAnimating];
}
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 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.
I am trying to add Images fetched from an external service to an NSMutableDictionary and seeing weird results. This is what I am doing:
- (void)fetchImages{
//Fetch Item Brand Images
//self.itemBrands is an NSArray of NSDictionaries
for (NSDictionary *itemBrand in self.itemBrands){
NSString *currentItemId = [itemBrand objectForKey:#"ITEM_ID"];
//Valid Item Id. This Log message is displayed
NSLog(#"Current Item Id: %#",currentItemId);
NSString *currentItemImageUrl = [[IMAGE_URL stringByAppendingString:currentItemId] stringByAppendingString:#".png"];
//Image URL is valid. This log message is displayed
NSLog(#"Current Image URL: %#",currentItemImageUrl);
NSURL *url = [NSURL URLWithString:currentItemImageUrl];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
if (image == nil){
//This log message is displayed when image is not present
NSLog(#"Image not Present 1");
}else{
//This log message is displayed when image is present
NSLog(#"Image Present 1");
[self.itemBrandImages setObject:image forKey:currentItemId];
}
}
//This for loop is not being executed at all. No log messages displayed.
for(id key in self.itemBrandImages){
NSLog(#"Current Item Id2: %#",key);
if ([self.itemBrandImages objectForKey:key] == nil){
NSLog(#"Image Not Present 2");
}else{
NSLog(#"Image Present 2");
}
}
}
The 2nd for loop where I am iterating over self.itemBrandImages is not being executed at all. None of the log messages inside are being displayed.
I tried the following before posting my issue here:
1) Researched similar problems in stack overflow and incorporated suggestion from one of them. The suggestion was "Perform an alloc init of the NSMUtableDictionary" in the init method of the .m file. This didn't help either.
2) To isolate the issue, I even tried adding a simple string to the NSMUtableDictionary instead of the image but even that does not seem to retained.
I am really confused as as to what I am missing or doing wrong here. Inputs are really appreciated.
Thanks,
Mike G
Perhaps:
for(NSString *key in [self.itemBrandImages allKeys])
I did an alloc init of the NSMutableDictianary right in my fetchImages method and it worked! Not sure why the alloc init in the init method did not work.
So here are my takeaways from this issue:
1) If you have an Array or dictionary #property that you are just getting and setting and not really adding or deleting objects to, then you don't need to explicitly alloc init them.
2) If you have an Array or dictionary #property that you are adding or deleting objects to ,you need to explicitly alloc init them.
Are my above statements true? Would love to hear your inputs on this.
Thanks,
Mike
New code:
- (void)fetchImages{
//Fetch Item Brand Images
self.itemBrandImages = [[NSMutableDictionary alloc] init];
for (NSDictionary *itemBrand in self.itemBrands){
NSString *currentItemId = [itemBrand objectForKey:#"ITEM_ID"];
NSLog(#"Current Item Id in ItemList: %#",currentItemId);
NSString *currentItemImageUrl = [[#"http://anythingtogo.freeiz.com/images/"stringByAppendingString:currentItemId] stringByAppendingString:#".png"];
NSLog(#"Current Image URL in ItemList: %#",currentItemImageUrl);
NSURL *url = [NSURL URLWithString:currentItemImageUrl];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
if (image == nil){
NSLog(#"Image not Present 1");
}else{
NSLog(#"Image Present 1");
[self.itemBrandImages setObject:#"Test" forKey:currentItemId];
}
}