I'm doing some lazy loading of images into an array when the app has loaded. I have tried using an NSMutableArray and an NSArray (I don't need to alter the array once it's been created) but the latter crashes on me.
...
[self performSelectorInBackground:#selector(loadImageArrays) withObject:nil];
...
- (void)loadImageArrays {
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
NSString *fileName;
imageArray = [[NSMutableArray alloc] init];
for(int i = 0; i <= x; i++) {
fileName = [NSString stringWithFormat:#"image_0000%d.png", i];
[imageArray addObject:[UIImage imageNamed:fileName]];
}
[pool drain];
}
vs
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
imageArray = [[NSArray alloc] initWithObjects:
[UIImage imageNamed:#"image_00000.png"],
[UIImage imageNamed:#"image_00001.png"],
[UIImage imageNamed:#"image_0000X.png"],
nil];
[pool drain];
NSZombieEnabled = YES tells me that [UIImage retain] was sent to deallocated instance when using the latter code-snippet.
Both arrays have (nonatomic, retain) property in my h-file. Why are the images not being retained by the NSArray?
UIImage is part of UIKit, which is not thread safe. For example, the method imageNamed: could corrupt the global name-image dictionary that the UIImage class uses for caching.
You have to use Core Graphics if you want to load images on a background thread.
Edit to answer your comment:
You can load PNG files with:
CGDataProviderRef source = CGDataProviderCreateWithURL((CFURLRef)url);
CGImageRef image = CGImageCreateWithPNGDataProvider(source, NULL, NO, kCGRenderingIntentDefault);
CFRelease(source);
A CGImage is a core foundation object and can be stored in an NSArray. You can then make a UIImage from it (on the main thread, of course):
[[UIImage alloc] initWithCGImage:image]
Although I know there is a big difference in mutable and immutable arrays, I'm in doubt myself. Something tells me this isn't purely a mutable vs immutable issue. Looks like your pool is drained prematurely (sounds nasty). Not that it should make a difference, but could you try to spawn a new thread by doing;
[NSThread detachNewThreadSelector:#selector(loadImageArrays) toTarget:self withObject:nil];
I'm simply curious of the result.. Also try both snippets in a clean environment (i.e: with no other code around). If your second code snippet works there, you should be looking elsewhere for the solution.
Related
I am developing application which required image caching. For doing this, I am using JMImageCache library. It is work fine for caching. But It can not release memory occupied by
following line.
[NSData dataWithContentsOfFile]
Here, is function which content code for cache image from disk.
- (UIImage *) imageFromDiskForURL:(NSString *)url {
NSData *data = [NSData dataWithContentsOfFile:cachePathForURL(url) options:0 error:NULL];
UIImage *i = [[[UIImage alloc] initWithData:data] autorelease];
data = nil;
[data release];
return i;
}
I have check it with instruments and it alloc 2.34 MB each time.
data = nil;
[data release];
Why do you expect this at all to work? Why should this release the original data? You're sending the release message to nil, which is a no-op.
Furthermore, if you don't create the object using alloc or copy, then it's autoreleased. That means if you release it once more, it will be overreleased and most likely your app is going to crash. What you need is:
One. Wrap the method call in an explicit autorelease pool:
- (UIImage *)imageFromDiskForURL:(NSString *)url
{
UIImage *i;
#autoreleasepool {
NSData *data = [NSData dataWithContentsOfFile:cachePathForURL(url) options:0 error:NULL];
i = [[UIImage alloc] initWithData:data];
}
return [i autorelease];
}
Two, alloc-init or manually release the data object:
- (UIImage *)imageFromDiskForURL:(NSString *)url
{
NSData *data = [[NSData alloc] initWithContentsOfFile:cachePathForURL(url) options:0 error:NULL];
UIImage *i = [[[UIImage alloc] initWithData:data] autorelease];
[data release];
return i;
}
alter the sequence nil and release
[data release];
data = nil;
and for clearing the cache use following delegate methods
[[JMImageCache sharedCache] removeAllObjects];
[[JMImageCache sharedCache] removeImageForURL:#"http://dundermifflin.com/i/MichaelScott.png"];
read the read me file of library
https://github.com/jakemarsh/JMImageCache/blob/master/README.markdown
What you are trying to do cannot work due to the way how UIImage uses a data you pass it. The data object is retained by the image, or more precisely by a CGImageSource that the UIImage has internally. From this data it is able to decompress and create the image any time. There is an option on CGImageSource to also keep around the decompressed data, but UIImage does not use that because it optimized for small UI graphics.
One thing that you can do to alleviate memory pressure is to not load the entire NSData into memory, but to memory-map it instead. This makes creation or recreation of the image a tad slower, but the created NSData object is very small in comparison.
I am trying to store a UIImage in an NSMutableArray but it doesn t work, here is the line I am using:
[images addObject:QRImage];
There are no compilation errors, but when I debug the app I see that the NSMutableArray has 0 objects. i.e. the image was not added.
I suspect your NSMutableArray is not properly initialized. Try this:
NSMutableArray *images = [[NSMutableArray alloc] init];
// ...
[images addObject:QRImage];
I have a method which should return a UIImage created from contentsOfFile (to avoid caching), but when it returns, i receive EXC_BAD_ACCESS. Running through Instruments doesn't reveal any results, as it just runs, without stopping on a zombie.
The image is correctly copied in the Bundle Resources phase...
- (UIImage *)imageForName:(NSString *)name {
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:#"png"];
return [UIImage imageWithContentsOfFile:path];
}
This method was adapted from the PhotoScroller sample, which works correctly.
Thanks
EDIT:
This is the code that uses imageForName, and you can see i added the retain, as per Luke/T's suggestion, but the EXC_BAD_ACCESS is on the return, not my addObject: call:
NSMutableArray *images;
for (NSDictionary *dict in imageListForPage){
[images addObject:[[self imageForName:[dict valueForKey:#"name"]]retain]];
}
ImageWithContentsOfFile will return an auto-released object. If you are not retaining it (on return [edit]) then you will get a bad access.
Edit:
Check the pointer of the NSarray. You need to init the Array either alloc as normal or use the arraywith
e.g.
NSMutableArray *images = [NSMutableArray arrayWithCapacity:ARRAY_CAPACITY];//autoreleased
or
NSMutableArray *images = [[NSMutableArray alloc] init];//release later
Adding an object to an NSMutableArray will implicitly send it a retain, so that's not necessary in your code.
Can you confirm (using NSLog() or a breakpoint) that
[UIImage imageWithContentsOfFile:path]
returns an object in your imageForName: method?
Finally, this code should be:
NSMutableArray *images = [NSMutableArray new]; // new is same as [[alloc] init]
for (NSDictionary *dict in imageListForPage) {
[images addObject:[self imageForName:[dict valueForKey:#"name"]]];
}
// ... do something with images
[images release];
I have noted several other threads on this topic and have tried wrapping my threaded code with:
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[pool release];
but the errors still come.
I am using a static method to instantiate a dictionary of words.
Here is some code:
-(id)init
[NSThread detachNewThreadSelector:#selector(loadDictionary:) toTarget:[IntroScreen class] withObject:nil];
[NSThread setThreadPriority:1.0];
return self;
}
+(void)loadDictionary:(id)param
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"click.wav"];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"pop.wav"];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"dink.wav"];
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:#"musicloop.wav"];
[WordDictionary configDictionary];
[pool release];
}
+(void)configDictionary
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Serializer * mySerializer = [[Serializer alloc] init];
[WordDictionary setDictionary:[mySerializer readApplicationPlist:#"x"]];
NSString * string;
NSString *filePath = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:#"x.txt"];
NSString *info = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
NSArray *arrayOfLines = [info componentsSeparatedByString:#"\r\n"];
[WordDictionary setDictionary:[[NSMutableDictionary alloc] init]];
[NSMutableDictionary dictionaryWithContentsOfFile:filePath];
int len = [arrayOfLines count];
for(int i = 0; i < len; i++)
{
string = [arrayOfLines objectAtIndex:i];
NSString * blankString = [NSString stringWithString:#""];
[[WordDictionary dictionary] setObject:blankString forKey:string];
double calc = ((double)i / (double)len) * 100.0;
[WordDictionary setProgress:(int)calc];
}
[mySerializer writeApplicationPlist:[WordDictionary dictionary] toFile:#"s"];
[WordDictionary setProgress:100];
[pool release];
}
Is there something I should know about using static class methods with new selector threads?
Thank you for your help
First, there are no static methods in Objective-C. There are class methods.
Secondly, your code shows both methods wrapped in autorelease pools. The warning must be coming from somewhere else.
Finally, your code leaks like a sieve. You aren't following the memory management rules. And there are some nonsense statements in there.
Specifically:
[WordDictionary setDictionary:[[NSMutableDictionary alloc] init]];
Unless +setDictionary: is breaking the memory management rules, the above leaks.
This statement [NSMutableDictionary dictionaryWithContentsOfFile:filePath]; effectively does nothing unless you do something with the return value.
Also, mySerializer is leaking.
Try running the analyzer over your code and fixing the problem. You should also read this and this.
Ah the [NSMutableDictionary dictionaryWithContentsOfFile:filePath]; was part of an experiment I was attempting to make the dictionary access faster. I should have removed it from this example.
I have just read the memory management rules, and understand that
[WordDictionary setDictionary:[[NSMutableDictionary alloc] init]]; appears to be poorly planned instantiation because I have no way to release it from within configDictionary as the reference is lost. But actually I don't ever want to release it, it lives for the entire lifetime of my application. Probably bad practice just the same.
mySerializer should definitely be released at the bottom.
I was just wondering if class methods had any special rules regarding autorelease pools and memory.
I will look over the documents you sent me and try to figure out the Analyzer, thank you for your help.
I have the following method which is spawned by a call for a new thread (using NSThread):
- (void) updateFMLs {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *temp = [[NSArray alloc] initWithArray:someArrayFromAnotherProcess];
[self performSelectorOnMainThread:#selector(doneLoading:) withObject:temp waitUntilDone:NO];
[pool release];
}
My doneLoading: method looks like this:
- (void) doneLoading:(NSArray *)obj {
myArray = [[NSArray alloc] initWithArray:obj copyItems:NO];
}
The contents of myArray become invalid. How can I preserve the contents of myArray so I can use them later in my app?
P.S. myArray is defined in the class header file.
If your background thread does some work and needs to 'pass' an NSArray to your main thread, then all doneLoading needs to do is:
-(void)doneLoading:(NSArray *)obj
{
[myArray release]; // release the previous array and its objects
myArray = [obj retain];
// now use myArray, refresh tables, etc.
}
There's (likely) no need to make another copy of the array, and that might be the underlying issue. You should also call [temp release] after your performSelector call, since arguments to that are retained already.
If the contents of myArray are becoming valid somehow, then they are being doubly released somewhere. myArray will retain any objects that are added to it. You mentioned that myArray itself is becoming invalid, so try rewriting your background thread and your doneLoading method with this pattern.
Finally you should use [pool drain] in place of [pool release].
The code you posted looks fine, apart from the memory leak in updateFMLs. You're probably over-releasing the objects somewhere else. I'm guessing it would be wherever someArrayFromAnotherProcess is made.
An alternative to the options above would be to declare myArray as an atomic property in the header
#property (atomic,retain) NSArray *myArray;
Then in updateFMLs you should be able to simply call the setter from the secondary thread. Obviously this only works if you are willing to pay the performance penalty for an atomic property.
- (void) updateFMLs {
NSAutoreleasePool *pool - [[NSAutoreleasePool alloc] init];
NSArray *temp = [[NSArray alloc] initWithArray:someArrayFromAnotherProcess];
[self setMyArray:temp];
[temp release];
[pool drain];
}