Failing to Release after Multiple Nib loads - iphone

I am using a Nib as a template for several buttons. It seemed to work fine, they each have their own independent state. However when I went to release the buttons I would crash in the dealloc. Here is the code...
mSoundBtns = new cSoundButton*[mNumSounds];
for(unsigned int i = 0 ; i < mNumSounds; ++i) {
mSoundBtns[i] = nil;
}
for(unsigned int s = 0; s < mNumSounds; ++s) {
[[NSBundle mainBundle] loadNibNamed:#"InstanceSoundButton" owner:self options:nil];
//Auto Loads via Outlet into 'soundNib'
mSoundBtns[s] = soundNib;
soundNib = nil;
uint32 count = mSoundBtns[s].retainCount;
NSLog(#"Last Count: %d", count);
}
for(unsigned int j = 0; j < mNumSounds; ++j) {
[mSoundBtns[j] release]; //**** Crash here on 7th (of 8) release
mSoundBtns[j] = nil;
}
Header:
#interface cLocationContext {
...
cSoundButton** mSoundBtns;
IBOutlet cSoundButton* soundNib;
}
#property (nonatomic, assign) IBOutlet cSoundButton* soundNib;
#end
The Nib is very simple, it just include a parent view and a child view of a custom view type.
cSoundButton simply keeps track of a name and a boolean state Mute or Not. Here is the dealloc
- (void)dealloc {
delete[] mSoundTag;
// Call the inherited implementation
[super dealloc]; //****Crashes in here
}
The crash is inside the call to super dealloc, in UIButton -> UIButtonContent dealloc. I assume I am doing something poor with my memory management like deallocing twice but I can't spot where.
Is what I am doing by loading the nib multiple times legal?

You have to retain the button as soon as you load it from the NIB. If you don't, you are not allowed to release it later, and you won't be able to access the button once your code returns control to the runloop (when the autorelease pool is drained).
PS: Wouldn't it be easier to use a Cocoa collection (NSMutableArray) to store the references to the buttons? Your code looks too complicated to me.

It will greatly simplify your memory management if you use your property and use an NSArray to store the button instances.
[[NSBundle mainBundle] loadNibNamed:#"InstanceSoundButton" owner:self options:nil];
//Auto Loads via Outlet into 'soundNib'
[mSoundBtns addObject:self.soundNib];
self.soundNib = nil;
Later, when it's time to release
[mSoundBtns release];
Keep in mind that when you're using properties you've got to reference them through self. The following two lines are exactly equivalent:
self.soundNib = something;
[self setSoundNib:something];
When you set soundNib = nil you are setting the variable soundNib to nothing, losing the reference to the button you loaded. If you hadn't added the pointer to an array and released it later you'd be leaking everything. Technically the way you're doing it might work... but don't do it that way. Using proper NSArrays and properties will make this whole process significantly easier and more maintainable.

Related

nsobject dealloc never called

I create an object in each iteration of a for loop. However the dealloc function is never called. Is it not supposed to be released at each iteration? I am using ARC and I have NSZombies deactivated. I don not see either any circular reference. Running the memory leak instruments from xcode it does not show any leaks, however the pointers memory of the class are never freed and the dealloc call never done. Any idea why this could happen?
Thank you!
for(int i=0; i<10; i++)
{
//calculate the hog features of the image
HogFeature *hogFeature = [self.image obtainHogFeatures];
if(i==0) self.imageFeatures = (double *) malloc(hogFeature.totalNumberOfFeatures*sizeof(double));
//copy the features
for(int j=0; j<hogFeature.totalNumberOfFeatures; j++)
self.imageFeatures[i*hogFeature.totalNumberOfFeatures + j] = hogFeature.features[j];
}
The HogFeature class declaration looks like this:
#interface HogFeature : NSObject
#property int totalNumberOfFeatures;
#property double *features; //pointer to the features
#property int *dimensionOfHogFeatures; //pointer with the dimensions of the features
#end
and the implementation:
#implementation HogFeature
#synthesize totalNumberOfFeatures = _totalNumberOfFeatures;
#synthesize features = _features;
#synthesize dimensionOfHogFeatures = _dimensionOfHogFeatures;
- (void) dealloc
{
free(self.features);
free(self.dimensionOfHogFeatures);
NSLog(#"HOG Deallocation!");
}
#end
Finally, the call to obtainHogFeatures inside the UIImage category looks like:
- (HogFeature *) obtainHogFeatures
{
HogFeature *hog = [[HogFeature alloc] init];
[...]
return hog;
}
You might want to enclose the inner loop with an #autoreleasepool { ... } which tells the compiler when to do the disposal, otherwise the pool will only be emptied when control returns to the main loop.
for (i = 0; i < 10; i++) {
#autoreleasepool {
...
}
}
As pointed out by CodeFi in the comments:
This will create a new autoreleasepool for each iteration of the loop, which would destroy each object after the iteration is completed, but would make the program do more work. If you don't mind all the objects hanging around until after the loop is completed, you would put the #autoreleasepool outside of the outer loop
#autoreleasepool {
for (i = 0; i < 10; i++) {
...
}
}
The reason the objects are sticking around is because they aren't being released. I don't see the declaration of self.imageFeatures - is this an array? If the features are being put in to an array, they won't be released as long as they remain in the array or the array itself isn't released.
I'm a little confused by the use of the C malloc and (attempted) free calls. There may very well be a motivation here I'm not aware of, but, given what you have provided, here is how I would write this, and I'd be surprised if the deallocs aren't triggered as expected:
NSMutableArray *features = [[NSMutableArray alloc] init];
for (int i = 0; i < 10; i++)
{
NSArray *hogFeatureArray = [[self image] obtainHogFeatures];
for (HogFeature *feature in hogFeatureArray)
{
[features addObject:hogFeature];
}
}
[self setImageFeatures:features];
The imageFeatures property is:
#property (nonatomic, retain) NSMutableArray *imageFeatures;
Assuming you've put all your hog feature instances into this imageFeatures array, they will be retained by that imageFeatures array. In order to observe your dealloc in action, one of two things needs to happen: You either need to remove a hog feature from the array, or you need to release the array itself (this would be done by setting the pointer to nil):
[self setImageFeatures:nil] // Previously assigned array now released
[[self imageFeatures] removeAllObjects]; // Works alternatively

objective c perform selector in background and autoreleasepool

I am developing an iphone application which has some data stored in a sqllite database. When my view loads i would like to load the data from the database on a background thread. The problem is the application keeps crashing and i dont know why.
The code:
-(id) init
{
if((self=[super init]))
{
[self performSelectorInBackground:#selector(loadList) withObject:nil];
}
}
-(void) loadList
{
#autoreleasepool
{
Loader * loader = [[Loader alloc] init];
NSMutableArray * array = [loader getItemList];
[array retain];
NSLog(#"Got %d items",[array count]);
[self performSelectorOnMainThread:#selector(createList:) withObject:array waitUntilDone:false];
[loader release];
}
}
-(void) createList: (NSMutableArray*) array
{
items = array;
int i;
Item * it;
for(i = 0; i < [items count]; i++)
{
it = [items objectAtIndex: i];
[it getName]; // crashes
// populate the list
}
}
Loader returns a NSMutableArray with Item objects. The application crashes when i call the item getName (which returns a NSString*). From what i understand it crashes because the item name properties is being released. What am i doing wrong?
Thanks!
It's likely to be a problem with whatever type of object you're using to populate array.
I'm unable to find finger-on-paper proof but I'm confident that performSelectorOnMainThread:withObject:waitUntilDone: retains its object. However if each of the items in array keeps a reference to loader then they need to take responsibility for retaining that object. It looks like you're attempting to keep it alive manually but — as Chuck alludes to — your call to performSelector... will return instantly and not wait for the call you've made to complete.
This particular bug appears to be that you're passing waitUntilDone:NO, so the array is being released immediately and consequently so are its items.
But in general, UIKit is not thread-safe, so this is just a touchy design. I would probably put the loading of this stuff in another class that handles the task for you instead of right in the view.
I'd put a breakpoint on the line:
it = [items objectAtIndex: i];
Then type
po it
in the debugger, and see what's in the name field. As a guess, I'd say one of two things: 1) the field that getName returns isn't initialized with an object (i.e. isn't a real NSString *) or that you're getting a C string from SQLite (which is what it usually returns) and you're trying to treat it as an NSString *. If it's the latter you can use [myCString stringWithUTF8String] to convert the C string into an NSString *

IPhone autoreleasing a returned NSMutableArray

I'm still relatively new to iPhone development but thought I understood the basic principals of memory management. I have a class method that returns an NSMutableArray, I'm calling alloc and init on the object therefore know I'm responsible for releasing it. However because I'm returning the array I assumed I was supposed to use autorelease when creating the object instead of releasing it.
+(NSMutableArray *)getStations:(int)stationType {
if(database == nil){
[self openDataBase];
}
// Create a temporary array to hold the returned objects
NSMutableArray *holder = [[[NSMutableArray alloc] init] autorelease];
// Check if the statement has been defined
if(select4 == nil) {
const char *sql = "SELECT station_id, station_name, AVG(test_percent) FROM stations LEFT JOIN tests USING (station_id) WHERE station_type = ? GROUP BY station_id ORDER BY station_name ASC";
if(sqlite3_prepare_v2(database, sql, -1, &select4, NULL) != SQLITE_OK){
NSLog(#"Error while creating detail view statement. '%s'", sqlite3_errmsg(database));
}
}
sqlite3_bind_int(select4, 1, stationType);
// Check if the statement executed correctly
while(sqlite3_step(select4) == SQLITE_ROW) {
NSInteger primaryKey = sqlite3_column_int(select4, 0);
Tests *station = [[Tests alloc] initWithPrimaryKey:primaryKey];
station.station_name = [NSString stringWithUTF8String:(char *)sqlite3_column_text(select4, 1)];
station.average_score = [NSNumber numberWithDouble:sqlite3_column_double(select4, 2)];
[holder addObject:station];
[station release];
}
// Reset the detail statement.
sqlite3_reset(select4);
// Return the holder array
return holder;
}
There's the basic code - XCode no longer indicates a potential memory leak but it crashes everytime that code executes saying message sent to deallocated instance. Any help would be appreciated I've spent ages googling and can't see what's wrong with the code. I did find this thread but it doesn't appear to be the answer to my question - crash happens when NSMutableArray is returned?
The code you've posted appears to be managing memory correctly – you've got a one-to-one relationship between retains and (auto)releases, and you're making a textbook use of autorelease – so the problem is probably that the code calling this method needs to retain the resulting array before the autorelease pool kicks in and yanks the rug out from under you.
If your code is assigning the NSMutableArray to an ivar you've declared with #property, that ivar needs to be declared as
#property (retain) NSMutableArray *myStations;
If you're doing something else to store the array, you may need to call [myStations retain]. Your table view code will also need to release the array, probably in its dealloc method.
If you want to use the returned NSMutableArray as a data source to fill in rows in a table view, then you're going to need to retain it in your UITableView class (or your UITableViewDataSource delegate class). It's going to be called repeatedly whenever the view is scrolled or otherwise needs updating.
Easiest thing to do is make it a retained property in that class.
#property (nonatomic, retain) Tests * stationArray;
Then, say, if you want to get your data in your viewDidLoad method:
self.stationArray = [self getStations: self.stationID]; // property retains
Access it in numberOfRowsInSection:
return self.stationArray.count;
Access it in cellForRowAtIndexPath:
Tests *station = [self.stationArray objectAtIndex:indexPath.row];
And, of course, in dealloc...
[stationArray release];
The autorelease in the method is correct (not an init or copy method), but the class will need to retain it if it wants to use it later on, after the current event.

Problem with allocating memory for an Objective-C data object

I've been programming objective-C for a few months now and have done pretty well so far without having to post any questions. This would be my first. The problem is that I'm getting a memory leak warning from a data object in one of it's methods. I can see that the problem is that I'm sending an alloc to it without releasing it, but I don't know how else to get it to retain the object in memory. If I take the alloc out, the program crashes. If I leave it in, it leaks memory. Here is the method in question:
+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure {
Feature *newFeature = [[self alloc] init];
newFeature.featureID = fID;
newFeature.featureName = fName;
newFeature.featureSecure = fSecure;
return [newFeature autorelease];
}
This method is called by another method in my view controller. This method is as follows:
+ (NSMutableArray*) createFeatureArray {
NSString *sqlString = #"select id, name, secure from features";
NSString *file = [[NSBundle mainBundle] pathForResource:#"productname" ofType:#"db"];
sqlite3 *database = NULL;
NSMutableArray *returnArray = [NSMutableArray array];
if(sqlite3_open([file UTF8String], &database) == SQLITE_OK) {
const char *sqlStatement = [sqlString UTF8String];
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
Feature *myFeature = [Feature featureWithID:sqlite3_column_int(compiledStatement,0)
name:[NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)]
secure:sqlite3_column_int(compiledStatement,2)];
[returnArray addObject:myFeature];
}
}
// Release the compiled statement from memory
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
return returnArray;
}
I have tried several things, such as creating a featureWithFeature class method, which would allow me to alloc init the feature in the calling method, but that crashed the program also.
Please let me know if you need any clarification or any other parts of the code. Thank you in advance for your help.
UPDATE: 4/14/2011
After reading the first two responses I implemented the suggestion and found that the program is now crashing. I am at a complete loss as to how to track down the culprit. Hoping this helps, I am posting the calling method from the view controller as well:
- (void)setUpNavigationButtons {
// get array of features from feature data controller object
NSArray *featureArray = [FeatureController createFeatureArray];
int i = 0;
for (i = 0; i < [featureArray count]; i++) {
Feature *myFeature = [featureArray objectAtIndex:i];
CGRect buttonRect = [self makeFeatureButtonFrame:[featureArray count] withMember:i];
UIButton *aButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[aButton setFrame:buttonRect];
[aButton addTarget:self action:#selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
[aButton setTitle:[NSString stringWithFormat:#"%#",myFeature.featureName] forState:UIControlStateNormal];
aButton.tag = myFeature.featureID;
[self.view addSubview:aButton];
}
}
NOTE: These methods are posted in reverse of the order they are invoked. This last method calls the second method, which in turn, calls the first.
UPDATE: I've updated these functions to show what is in there now: Below, I will post the header files for the object - maybe that will help
#interface Feature : NSObject {
int featureID;
int featureSecure;
NSString *featureName;
}
#property (nonatomic, assign) int featureID;
#property (nonatomic, assign) int featureSecure;
#property (nonatomic, retain) NSString *featureName;
- (id) init;
- (void) dealloc;
+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure;
#end
#interface FeatureController : NSObject {
}
- (id) init;
- (void) dealloc;
+ (NSMutableArray*) createFeatureArray;
+ (Feature*) getFeatureWithID:(int)fetchID;
#end
Convenience methods should follow the convention of returning autoreleased objects. Change this:
+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure {
Feature *newFeature = [[self alloc] init];
...
return newFeature;
}
to:
+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure {
Feature *newFeature = [[self alloc] init];
...
return [newFeature autorelease];
}
The name of your method - +featureWithID:name:secure: - indicates that it returns an object that the caller does not own. Instead, it is returning an object that has been retained, that the caller therefore owns and must release. To fix this (and your leak), simply replace return newFeature with return [newFeature autorelease].
There's nothing more you need to do, because your own code doesn't need a long-lasting ownership claim, and the array to which you're adding the object will manage its own ownership claim over it.
In +createFeatureArray, you’re over releasing the array:
+ (NSMutableArray*) createFeatureArray {
…
NSMutableArray *returnArray = [[[NSMutableArray alloc] init] autorelease];
…
return [returnArray autorelease];
}
In the first line, you used +alloc, so you own the array. Then you used -autorelease, so you do not own the array any more. This means that you shouldn’t send -release or -autorelease to it, which you are doing in the return line.
You can fix that by changing those lines to:
+ (NSMutableArray*) createFeatureArray {
…
NSMutableArray *returnArray = [NSMutableArray array];
…
return returnArray;
}
Also, unless it is relevant to callers that the array is mutable, you should change that method to return NSArray instead of NSMutableArray. You could keep your code as is, i.e., return a mutable array even though the method declaration states that the return type is NSArray.
As for your convenience constructor, there are essentially two choices depending on whether you want to return an owned or a non-owned object:
if you want to return an owned object, allocate it with +alloc or +new and return it without autoreleasing it. Your method name should contain new, e.g. +newFeatureWithId:…
if you want to return an object that’s not owned by the caller, allocate it with +alloc or new and autorelease it before/upon returning it to the caller. Your method name should not contain new, alloc, or copy.
In -setUpNavigationButtons, you obtain a non-owned array via +createFeatureArray, allocate a mutable array based on it, and release the mutable array without adding or removing elements from it. A mutable array makes sense when you need to add/remove elements. If you don’t have this need, you could change your method to:
- (void)setUpNavigationButtons {
// get array of features from feature data controller object
NSArray *featureArray = [FeatureController createFeatureArray];
…
// [featureArray release];
You’d remove that [featureArray release] since you do not own featureArray inside that method.
Edit: In -setUpNavigationButtons, you’re retaining the button you create and soon after you’re releasing it. In that particular method, those are idempotent operations — they aren’t wrong per se but are not necessary at all. You could replace that code with
UIButton *aButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
…
[self.view addSubview:aButton];
// [aButton release];
i.e., do not retain it and do not release it.

Can't add or remove object from NSMutableSet

Check it:
- (IBAction)toggleFavorite {
DataManager *data = [DataManager sharedDataManager];
NSMutableSet *favorites = data.favorites;
if (thisEvent.isFavorite == YES) {
NSLog(#"Toggling off");
thisEvent.isFavorite = NO;
[favorites removeObject:thisEvent.guid];
[favoriteIcon setImage:[UIImage imageNamed:#"notFavorite.png"] forState:UIControlStateNormal];
}
else {
NSLog(#"Toggling on, adding %#", thisEvent.guid);
thisEvent.isFavorite = YES;
[favorites addObject:thisEvent.guid];
[favoriteIcon setImage:[UIImage imageNamed:#"isFavorite.png"] forState:UIControlStateNormal];
}
NSLog(#"favorites array now contains %d members", [favorites count]);
}
This is fired from a custom UIButton. The UI part works great--toggles the image used for the button, and I can see from other stuff that the thisEvent.isFavorite BOOL is toggling happily. I can also see in the debugger that I'm getting my DataManager singleton.
But here's my NSLog:
2010-05-13 08:24:32.946 MyApp[924:207] Toggling on, adding 05db685f65e2
2010-05-13 08:24:32.947 MyApp[924:207] favorites array now contains 0 members
2010-05-13 08:24:33.666 MyApp[924:207] Toggling off
2010-05-13 08:24:33.666 MyApp[924:207] favorites array now contains 0 members
2010-05-13 08:24:34.060 MyApp[924:207] Toggling on, adding 05db685f65e2
2010-05-13 08:24:34.061 MyApp[924:207] favorites array now contains 0 members
2010-05-13 08:24:34.296 MyApp[924:207] Toggling off
2010-05-13 08:24:34.297 MyApp[924:207] favorites array now contains 0 members
Worst part is, this USED to work, and I don't know what I did to break it.
--EDIT: By request, my shared data singleton code:
.h
#import <Foundation/Foundation.h>
#interface DataManager : NSObject {
NSMutableArray *eventList;
NSMutableSet *favorites;
}
#property (nonatomic, retain) NSMutableArray *eventList;
#property (nonatomic, retain) NSMutableSet *favorites;
+(DataManager*)sharedDataManager;
#end
.m:
#import "DataManager.h"
static DataManager *singletonDataManager = nil;
#implementation DataManager
#synthesize eventList;
#synthesize favorites;
+(DataManager*)sharedDataManager {
#synchronized(self) {
if (!singletonDataManager) {
singletonDataManager = [[DataManager alloc] init];
}
}
return singletonDataManager;
}
- (DataManager*)init {
if (self = [super init]) {
eventList = [[NSMutableArray alloc] init];
favorites = [[NSMutableSet alloc] init];
}
return self;
}
------EDIT EDIT EDIT EDIT------
At #TechZen's suggestion, I moved my accessor methods into the data manager singleton. Here's what it now looks like:
#import "DataManager.h"
static DataManager *singletonDataManager = nil;
#implementation DataManager
#synthesize eventList;
#synthesize favorites;
+(DataManager*)sharedDataManager {
#synchronized(self) {
if (!singletonDataManager) {
singletonDataManager = [[DataManager alloc] init];
}
}
return singletonDataManager;
}
- (DataManager*)init {
if (self = [super init]) {
eventList = [[NSMutableArray alloc] init];
favorites = [[NSMutableSet alloc] init];
}
return self;
}
#pragma mark -
#pragma mark Data management functions
- (void)addToFavorites:(NSString *)guid
{
[self.favorites addObject:guid];
NSLog(#"Item added--we now have %d faves.", [favorites count]);
}
- (void)removeFromFavorites:(NSString *)guid
{
[favorites removeObject:guid];
NSLog(!"Item removed--we now have %d faves.", [self.favorites count]);
}
#end
I made my viewcontroller where this is happening call [[DataManager sharedManager] addToFavorites:Event.guid] instead of adding the item right to the favorites set itself, but I left the logging stuff that was there in place.
Here's my log:
2010-05-13 13:25:52.396 EverWondr[8895:207] Toggling on, adding 05db685f65e2
2010-05-13 13:25:52.397 EverWondr[8895:207] Item added--we now have 0 faves.
2010-05-13 13:25:52.398 EverWondr[8895:207] favorites array now contains 0 members
2010-05-13 13:25:53.578 EverWondr[8895:207] Toggling off
2010-05-13 13:25:53.579 EverWondr[8895:207] favorites array now contains 0 members
So.... the DataManager object can't even add anything to its own property! And it doesn't throw an exception like it would if it was a non-mutable type, it just silently fails!
Just for fun, I went through and changed it to an NSMutableArray, which I'm more familiar with. Same behavior.
As phellicks suggest above, you might not be returning a mutable set from data.favorites. Although, you should be getting a compiler warning if that is the case.
This 05db685f65e2 is not a real guid. It looks more like the address of an object. You should check the type on 'thisEvent.guid` to make sure your got an object and the correct type of object.
Unrelated to your main problem, I would add that (1) this:
NSMutableSet *favorites = data.favorites;
... is rather pointless and just adds another possible source of error. There is no reason not to just use data.favorites directly in the code. (see (3) below)
(2) When accessing an external object, even a singleton, it is good practice to make the reference to the external object a property of the class especially in the case of a critical object like a data model. This lets you control and track access to the external object.
(3) Don't treat singletons as naked global variables. This will lead to grief. Instead, wrap access to the data models internal data in specific methods. For example, instead of accessing the data.favorites directly create a method like:
- (void) addToFavoritesGuid:(id) aGuid;
or
- (void) addToFavoritesGuid:(GuidClass *) aGuid;
This will give your data model control over its internals and give it the ability to refuse to add objects that shouldn't belong there.
Edit
From comments:
Okay, re what I'm actually
returning... I just used debug to step
through my singleton's initializer.
Examining the ivars of my DataManager
object, I see that my favorites, which
is initialized in init with favorites
= [[NSMutableSet alloc] init]; is actually getting created as a NSCFSet,
and I don't know what that is nor what
to make of it..
NSSet like all the collections and strings is actually a class cluster i.e. a collection of subclasses that all share the same interface. When you create a set the actual class you get back maybe different depending on how it was created. In this case, you're getting back NS-Core-Foundation-Set which is the standard core class for NSSet.
Therefore, your problem is that favorites is initialized as a mutable set but is being assigned to a immutable set. This is why you can't add anything to it.
This initialization:
favorites = [[NSMutableSet alloc] init];
... is being disposed of by:
NSMutableSet *favorites = data.favorites;
If you have an instance variable and you create a local variable of the same name, the local symbol will dominate in the scope it was created in. This appears to work because as a subclass of NSSet, NSMutableSet responds to all the methods and attributes of NSSet.
However, you must be getting a spate of warnings from your linker when you build. You shouldn't ignore those errors. You should treat them as fatal errors because that's what they will be at runtime.
To resolve your problem:
(1) Declare data.favorites as a mutable array and just access it directly. Having another local variable assigned to the same address buys you nothing.
(2) Declare favorites as mutable array property of the current object. Initialize it from data.favorites like:
self.favorites=[NSMutableSet setWithCapacity:[data.favorites count]];
[self.favorites setSet:data.favorites];
// ... add or remove items
data.favorites = self.favorites;
(3) Move all the logic for adding or removing objects in data.favorites to custom methods in the data model object (see above)
Three is the best choice.
Edit02
It looks like the class clusters are hiding the true classes of all classes in the cluster. I ran the following test code:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSSet *s=[NSSet setWithObject:#"setWithObject"];
NSMutableSet *m=[NSMutableSet setWithCapacity:1];
[m addObject:#"Added String"];
NSMutableSet *n = [[NSMutableSet alloc] initWithCapacity:1];
[self showSuperClasses:s];
[self showSuperClasses:m];
[self showSuperClasses:n];
[self showSuperClasses:#"Steve"];
}
- (void) showSuperClasses:(id) anObject{
Class cl = [anObject class];
NSString *classDescription = [cl description];
while ([cl superclass])
{
cl = [cl superclass];
classDescription = [classDescription stringByAppendingFormat:#":%#", [cl description]];
}
NSLog(#"%# classes=%#",[anObject class], classDescription);
}
... and got this output:
NSCFSet classes=NSCFSet:NSMutableSet:NSSet:NSObject
NSCFSet classes=NSCFSet:NSMutableSet:NSSet:NSObject
NSCFSet classes=NSCFSet:NSMutableSet:NSSet:NSObject
NSCFString classes=NSCFString:NSMutableString:NSString:NSObject
Clearly, the report from the debugger and the class function are useless in figuring out the true class of any instance that belongs to cluster. It didn't used to be this way. This is a recent change. I presume its part of the "toll-free bridging" from Core Foundation.
You can add items to favorites because all definitions of favorites in both classes are NSMutableSet.
In any case, your problem is that you have two separate definitions of favorites in the same class. You are getting a warning from the linker saying:
Local declaration of "favorites" hides instance variable
I think the problem can be explained by the runtime confusing the two favorites. You add objects to one favorites but you log the other one.
The local redefinition of favorites serves absolutely no purpose. Remove it and see if the problem persist.