Singleton shared data source in Objective-C - iphone

Hey folks - I'm writing a pretty simple iPhone application. The data comes from a plist file (NSDictionary basically), that I'm trying to load into a singleton class and use across my various view controllers to access the data.
Here's the implementation for my singleton (heavily modeled after this thread)
#implementation SearchData
#synthesize searchDict;
#synthesize searchArray;
- (id)init {
if (self = [super init]) {
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"searches.plist"];
searchDict = [NSDictionary dictionaryWithContentsOfFile:finalPath];
searchArray = [searchDict allKeys];
}
return self;
}
- (void)dealloc {
[searchDict release];
[searchArray release];
[super dealloc];
}
static SearchData *sharedSingleton = NULL;
+ (SearchData *)sharedSearchData {
#synchronized(self) {
if (sharedSingleton == NULL)
sharedSingleton = [[self alloc] init];
}
return(sharedSingleton);
}
#end
So whenever I try to access the searchDict or searchArray properties elsewhere in my application (like a TableView delegate) like so:
[[[SearchData sharedSearchData] searchArray] objectAtIndex:indexPath.row]
I get an exception stating *** -[NSCFSet objectAtIndex:]: unrecognized selector sent to instance 0x5551f0
I'm not really sure why the objectAtIndex message is being sent to an NSCFSet object, I feel like my singleton is implemented wrong or something. I also tried a more complex singleton implementation like the one recommended by apple in the aforementioned thread and had the same problem. Thanks for any insight you can provide.

In your -init method you are directly accessing your instance variables and you are not retaining them. They're getting deallocated and their memory is being used up by other objects later on in your application's lifetime.
Either retain your objects that you're creating there or use the non-convenience methods to generate them.
searchDict = [[NSDictionary alloc] initWithContentsOfFile:finalPath];
searchArray = [[searchDict allKeys] retain];

Whenever you assign synthesized variables, do it through 'self', so:
- (id)init {
if (self = [super init]) {
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"searches.plist"];
self.searchDict = [NSDictionary dictionaryWithContentsOfFile:finalPath];
self.searchArray = [searchDict allKeys];
}
return self;
}
Also make sure you've set up those variables to be 'retain'ed in the header file.

Hi, Can you tell me what is the advantage, when we assign synthesized variables through 'self'? Thank you shiva
the values are set through the setter; it releases the previous value and retains the one you assign.

Related

Memory leak when using NSString inside for loop

I have 100 images in my resource bundle named like image1.jpg,image2.jpg.
Basically what i am trying to do is create path names to those images dynamically inside a for loop.
While testing in simulator,the images loaded fine and the app did not crash.But while testing the app with instruments i was shocked to see the heavy memory leak that was happening while i was creating the path1 object.
I am pasting the entire method here for reference
- (id)init {
self = [super init];
if (self) {
self.arrayImages = [[[NSMutableArray alloc] init] autorelease];
for(int i=1 ; i<100 ; i++){
NSString *str = [NSString stringWithFormat:#"Century%d",i];
NSString *path1 = [[NSBundle mainBundle] pathForResource:str ofType:#"jpg"];
[self.arrayImages addObject:path1];
}
}
return self;
}
As i have not made use of any alloc inside the loop i dont have any ownership and hence no right to release the object.What is the reason for this memory leak??
Kindly explain the problem and provide the necessary solution in order to fix it..
As always,any help is highly appreciated..
arrayImages is retaining path1, and so if you do not release arrayImages it will leak. How are you creating arrayImages, and are you releasing it anywhere?
Edited based on comments:
Make sure you release arrayImages in your -dealloc method like so: [arrayImages release]; (note the lack of self).
There is no leak in the code you've shown.
There are (at least) two possibilities:
You have a leak in code you didn't paste into your question
Everything is fine and Instruments gave you a false-positive
Your loop will create a lot of autoreleased variables. These won't be deallocated until after the loop has finished, but that's how it's supposed to work.
The reason for the leak would be this line right here:
NSString *str = [NSString stringWithFormat:#"Century%d",i];
By using convenience methods in Objective-C, what happens in the background is the following:
NSString *str = [[[NSString alloc] initWithFormat:#"Century%d", i] autorelease];
Not using alloc/init to create a weak reference is a misconception. You are always the owner of a created object, no matter how you create it. The convenience method simply does the alloc/init and autoreleases it for you.
Here's what I would suggest you do to avoid leaking memory:
- (id)init {
self = [super init];
if (self) {
self.arrayImages = [[[NSMutableArray alloc] init] autorelease];
NSAutoreleasePool *tmpPool = [[NSAutoreleasePool alloc] init];
for(int i = 1 ; i < 100 ; i++) {
NSString *str = [NSString stringWithFormat:#"Century%d",i];
NSString *path1 = [[NSString alloc] initWithString:[[NSBundle mainBundle] pathForResource:str ofType:#"jpg"]];
[self.arrayImages addObject:path1];
[path1 release];
}
[tmpPool drain];
}
return self;
}
Let me know if this works better for you.
-EDIT- Allocating the path1 object and releasing it after adding to arrayImages.

iOS - I'm confused how memory is being handled here?

UIImage API Reference Document:-
initWithContentsOfFile:
Initializes and returns the image object with the contents of the specified file.
- (id)initWithContentsOfFile:(NSString *)path
Parameters
path
The path to the file. This path should include the filename extension that identifies the type of the image data.
Return Value
An initialized UIImage object, or nil if the method could not find the file or initialize the image from its contents.
Considering this scenario, suppose I have a class, it could be extension of any class. Just took UIImage for example.
#interface myImage : UIImage
{
BOOL isDefaultSet;
}
-(id)initWithDefaultImage;
#end
#implementation myImage
-(id)initWithDefaultImage
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"someInvalidImage" ofType:#"png"];
idDefaultSet = YES;
return [self initWithContentsOfFile:path];
}
#end
//somewhere in other class:
NSString *path = [[NSBundle mainBundle] pathForResource:#"someInvalidImage" ofType:#"png"];
myImage *myObject = [[myImage alloc] initWithDefaultImage];
UIImage *yourObject = [[UIImage alloc] initWithContentsOfFile:path];
now here in both cases,
"alloc" gives "retainCount+1"
and if
initWithDefaultImage/initWithContentsOfFile
returned nil due to some issue - lets say (invalid file path), this memory will be leaked as
myObject/yourObject
will be set to nil even though the allocation was made before init.
I have seen many implementations for extended classes/interfaces in this manner. I'm confused how memory is being handled here? can anyone share view on this?
if [super init] returns nil, nil is returned. so the control returns from method and if (someInitializingFailed) block will never be executed and memory will be leaked as alloc is already executed before calling "initWithFoo"
if [super init] returns nil, super's init has already cleaned after itself and released the memory allocated by alloc.
From Handling Initialization Failure:
You should call the release method on self only at the point of failure. If you get nil back from an invocation of the superclass’s initializer, you should not also call release.
Usually the corresponding initializer releases self (the new object) before returning nil, as in:
- (id)initWithFoo
{
self = [super init];
if (!self) return nil;
if (someInitializingFailed) {
[self release];
return nil;
}
return self;
}
You can assume that -[UIImage initWithContentsOfFile:] is implementing the same pattern. So unless Instruments does tell you there's a leak you don't need to do any special handling in your case.
You are right, sometimes people forget to handle this leak. The allocated memory needs to be released if we cannot proceed with the initialisation.
-(id)initWithDefaultImage
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"someInvalidImage" ofType:#"png"];
if (path != nil)
{
self = [super initWithContentsOfFile:path];
}
else // cannot proceed with init
{
[self release];
self = nil;
}
return self;
}

Struggling with memory management and create array method

I'm struggling to find the correct way to release an array after my method has been called. I wonder if there is a better way to achieve what I'm trying to acheive with my method:
- (NSArray *) setupDetailArray : (NSString *) selectedCategory {
// Load .plist file
NSString *path = [[NSBundle mainBundle] pathForResource:#"data" ofType:#"plist"];
// Load .plist into a new dictionary
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
// Drill down to next level
NSArray *faceSelection = [[NSArray alloc] initWithArray:[dict objectForKey:detailTitle]];
[dict release], dict = nil;
// Set up link to App Delegate
UltimateRageAppDelegate *dataCenter = (UltimateRageAppDelegate *) [[UIApplication sharedApplication] delegate];
dataCenter.faces = [[NSMutableArray alloc] init];
// Set app delegate faces to array
dataCenter.faces = faceSelection;
[dataCenter.faces release];
return faceSelection;
// [faceSelection release], faceSelection = nil; ??????
}
And I call my method in viewDidLoad
// If faceArray is empty, create it
if (faceArray == nil)
faceArray = [self setupDetailArray:detailTitle];
...
My application is leaking memory here, and I'm really looking for a way to release everything once I'm done.
Your method should return an autoreleased array which is then retained by the method that calls it if it wants/needs to keep it.
- (NSArray *) setupDetailArray : (NSString *) selectedCategory {
...
// Create the array, but don't own it
NSArray *faceSelection = [[[NSArray alloc] initWithArray:[dict objectForKey:detailTitle]] autorelease];
...
return facesSelected;
}
Now the code that calls this method should retain the object if it needs it. So, in your viewDidLoad
if (faceArray == nil)
faceArray = [[self setupDetailArray:detailTitle] retain];
...
If faceArray is an instance variable in your class, then you can just release it in your dealloc method.
You are also leaking memory here
// Set up link to App Delegate
UltimateRageAppDelegate *dataCenter = (UltimateRageAppDelegate *) [[UIApplication sharedApplication] delegate];
dataCenter.faces = [[NSMutableArray alloc] init];
// Set app delegate faces to array
dataCenter.faces = faceSelection;
[dataCenter.faces release];
This should be
// Set up link to App Delegate
UltimateRageAppDelegate *dataCenter = (UltimateRageAppDelegate *) [[UIApplication sharedApplication] delegate];
dataCenter.faces = faceSelection;
I'd suggest you read (and re-read and re-read) the docs on memory management and read up on properties, setters and the dot notation.
Apple Objective-C Memory Management
dataCenter.faces = [[NSMutableArray alloc] init];
You allocate a non-autoreleased array and assign it to the property faces (I bet it has the retain modifier).
dataCenter.faces = faceSelection;
Now you assign as new array to the faces property, but you haven't properly release the previous NSMutableArray.
[dataCenter.faces release];
You now indirectly release your faceSelection array.
You leak at least one NSMutableArray every time you run that method. You should do it like this instead:
// Drill down to next level
NSArray *faceSelection = [[dict objectForKey:detailTitle] copy];
[dict release], dict = nil;
// Set up link to App Delegate
UltimateRageAppDelegate *dataCenter = (UltimateRageAppDelegate *) [[UIApplication sharedApplication] delegate];
// Set app delegate faces to array
dataCenter.faces = faceSelection;
return [faceSelection autorelease];
Your method should return an autoreleased object. The only methods that should return retained objects are methods whose name:
starts with alloc
starts with new
contains copy
All other methods should return autoreleased objects.
Other way for doing this.
//Declare method as follows.
- (void) setupDetailArray : (NSString *) selectedCategory arrFaceArray:(NSArray *)faceArray
{
}
And I call my method in viewDidLoad
if (!faceArray)
{
faceArray = [[NSArray alloc] init]; //Alloc in ViewDidLoad and release in ViewDidUnload or dealloc.
faceArray = [self setupDetailArray:detailTitle arrFaceArray:faceArray];
}
Also consider the #DarkDust answer for maintaining autoreleased objects. Both are the possible ways.

Objective C NSMutableDictionary memory management

I have a model class that keeps track record being built by multiple views. It has a NSMutableDictionary that has the fields and values I eventually write to the database. It is saved to a plist and loaded back when needed. I thought that I was keeping track of my memory, but it throws a EXC_BAD_ACCESS when I try to release the Dictionary. Here is my interface:
#import <Foundation/Foundation.h>
#interface CurrentEntryModel : NSObject {
NSMutableDictionary *currentEntry;
}
#property (nonatomic, retain) NSMutableDictionary *currentEntry;
- (void) setValue: (NSString *)value;
- (NSString *) getValue;
#end
My understanding is that currentEntry should be retained and I would have to release it during dealloc.
Here is my implementation (this isn't the entire class just the relevant parts):
#import "CurrentEntryModel.h"
#implementation CurrentEntryModel
#synthesize currentEntry;
-(id) init {
if ( self = [super init] )
{
//check for file
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *file;
file = #"location.plist";
if ([fileManager fileExistsAtPath:file]){
NSLog(#"file exists");
currentEntry = [[NSMutableDictionary alloc] initWithContentsOfFile:file];
}else {
NSLog(#"file doesn't exist");
currentEntry = [[NSMutableDictionary alloc ] initWithCapacity:1];
NSDate *testDate = [NSDate date];
[currentEntry setObject:testDate forKey:#"created"];
[currentEntry writeToFile:file atomically:YES];
}
}
return self;
}
- (void) setValue: (NSString *)value {
[currentEntry setObject:value forKey:#"location"];
}
- (NSString *) getValue {
return [currentEntry objectForKey:#"location"];
}
- (void) dealloc{
[currentEntry release];
[super dealloc];
}
#end
If I init this class it will automatically create the dictionary and if I call one of the set or get methods it seems like the dictionary is retained as it will dealloc correctly. If the class is just initialized and then no methods are called it will throw the EXC_BAD_ACCESS errors. If I am not mistaken when the file doesn't exist I don't initialize the dictionary correctly because the method starts with dictionary and not init. Although every time I run this the file is there so it always uses the the file found logic and I thought that that will retain the variable.
Am I not initializing the dictionary correctly?
Edit - changed the code on the convenience method to reflect the proper way. Everyone take note of what Squeegy has to say.
This is bad bad bad.
else {
NSLog(#"file doesn't exist");
currentEntry = [[NSMutableDictionary alloc ] dictionaryWithCapacity:1];
dictionaryWithCapacity: is a class method on NSMutableDictionary which returns an autoreleased object, and you don't retain it. So the run loop ends, and the dictionary gets autoreleased. Then you run [currentEntry release] in your dealloc and it explodes because that object is deallocated already.
you probably wan't initWithCapacity: instead. Always pair alloc with a method that starts with init.
Also, when using retained properties like this, I usually let the property figure this out for me, and only work with autoreleased objects. You just have to remember less rules, and there are less gotchas.
- (id)init {
// ...
self.currentEntry = [NSMutableDictionary dictionWithContentsOfFile:file];
// ...
}
- (void)dealloc {
//...
self.currentEntry = nil;
//...
}
This way you never have to call retain or release directly on the object. In my experience, this results in less confusing bugs. But it's also point of style among many ObjC programmer that not everyone agrees with.
Joshua -
+ (id)dictionaryWithCapacity:(NSUInteger)numItems
is a class method of NSDictionary. So when you call it, it should be:
[NSMutableDictionary dictionaryWithCapacity:1];
Not:
[[NSMutableDictionary alloc] dictionaryWithCapacity:1];
Further, [NSMutableDictionary dictionaryWithCapacity:] returns an autoreleased object. If you want to keep the dictionary as an ivar and not have it autoreleased on the next cycle of the run loop, you should call:
[currentEntry retain];
So, basically, change it to:
currentEntry = [[NSMutableDictionary alloc] initWithCapacity:1];
or:
currentEntry = [[NSMutableDictionary dictionaryWithCapacity:1] retain];
The first one probably makes more sense, since the connivence class methods were designed to be used when you wanted an autoreleased instance.

iPhone - Objective-C - Memory Leak with initWithArray

I am using the code below to set my two NSArray ivars:
The issue is, I keep getting a memory leak on the following lines:
followingFriendsArray = [[NSArray alloc] initWithArray:friend.Following];
followerFriendsArray = [[NSArray alloc] initWithArray:friend.Followers];
Is this not the correct way to set ivars from an existing NSArray of items? Any help would be appreciated. I've also tried to autorelease the above two lines, but when I actually access them in another method I get an error that they've already been released.
I have included my Interface and Implementation code below:
Interface .h:
NSArray *followingFriendsArray;
NSArray *followerFriendsArray;
#property (nonatomic, retain) NSArray *followingFriendsArray;
#property (nonatomic, retain) NSArray *followerFriendsArray;
Implementation .m:
- (void)handlerGetFollowingInformation:(id)value {
BOOL success = [Utility checkWebServiceErrors:value controller:self.navigationController];
if (success) {
Friend *friend = (Friend *)value;
followingFriendsArray = [[NSArray alloc] initWithArray:friend.Following];
followerFriendsArray = [[NSArray alloc] initWithArray:friend.Followers];
}
}
This is how I need to access the arrays:
- (void)followersButtonTapped:(id)sender {
FollowingVC *fvc = [[FollowingVC alloc] initWithNibName:#"FollowingViewController" bundle:nil];
fvc.friends = followerFriendsArray;
[self.navigationController pushViewController:fvc animated:YES];
[fvc release];
}
I release my two ivars in the following way as per usual:
- (void)viewDidUnload {
self.followingFriendsArray = nil;
self.followerFriendsArray = nil;
[super viewDidUnload];
}
- (void)dealloc {
[followingFriendsArray release];
[followerFriendsArray release];
[super dealloc];
}
I mean the code works just fine, it's just that I'm concerned about said memory leaks when I run the "Leaks" performance tool.
OK
you should not use autorelease in this case, but you have to release the arrays by calling :
[followingFriendsArray release];
[followerFriendsArray release];
you can do it:
when you don't need to use them any more.
in the dealloc method in your .m file.
option 2looks like that -
- (void)dealloc {
[followingFriendsArray release];
[followerFriendsArray release];
[super dealloc];
}
BTW -
if you don't manipulate the arrays after creating them (add / remove objects) you should use an immutable array (NSArray).
Good Luck
Your method handlerGetFollowingInformation is assigning new values to followingFriendsArray and followerFriendsArray without releasing the previous contents. If you call this method more than once on the same instance you will leak.
CRD is right that the arrays are not released inside the handlerGeFollowingInformation method but the fix is maybe overkill. What you need to do is to use self. so that the setter method is called which does that automatically. You could should look like this:
- (void)handlerGetFollowingInformation:(id)value {
BOOL success = [Utility checkWebServiceErrors:value controller:self.navigationController];
if (success) {
Friend *friend = (Friend *)value;
self.followingFriendsArray = [[NSArray alloc] initWithArray:friend.Following];
self.followerFriendsArray = [[NSArray alloc] initWithArray:friend.Followers];
}
}
Easy fix but hard to spot and I ran into this issue over and over again especially when I started to dealloc are the properties.
-Andy