I created an "SDMutableGrid" class so that I could use a grid. It's just a child of NSMutableArray that contains a number for arrays equal to the number of rows in the grid.
Currently, the program quits before it really starts and it appears that it is because the methods defined for NSMutableArray somehow do not apply to SDMutableGrid, anyone know why?
Here is the .h :
#import <Foundation/Foundation.h>
#import "SDDimensions.h"
#interface SDMutableGrid : NSMutableArray {
SDDimensions dimensions;
}
#property (nonatomic) SDDimensions dimensions;
- (id)initWithDimensions:(SDDimensions)newDimensions;
- (void)addObject:(id)anObject toRow:(NSUInteger)row;
#end
Here is the .m :
#import "SDMutableGrid.h"
#implementation SDMutableGrid
#synthesize dimensions;
- (void)setDimensions:(SDDimensions)newDimensions {
if (newDimensions.width < dimensions.width) {
NSMutableArray *anArray;
NSRange aRange = NSMakeRange(newDimensions.width, dimensions.width - newDimensions.width);
for (NSUInteger i = 0; i < MIN(dimensions.height,newDimensions.height); i++) {
anArray = [self objectAtIndex:i];
[anArray removeObjectsInRange:aRange];
}
}
dimensions.width = newDimensions.width;
if (newDimensions.height > dimensions.height) {
for (NSUInteger i = dimensions.height; i < newDimensions.height; i++) {
[self addObject:[[NSMutableArray alloc] initWithCapacity:dimensions.width]];
}
} else if (newDimensions.height < dimensions.height) {
[self removeObjectsInRange:NSMakeRange(newDimensions.height, dimensions.height - newDimensions.height)];
}
dimensions.height = newDimensions.height;
}
- (id)initWithDimensions:(SDDimensions)newDimensions {
if (self = [super initWithCapacity:newDimensions.height]) {
NSMutableArray *anArray;
for (NSUInteger i = 0; i < newDimensions.height; i++) {
anArray = [[NSMutableArray alloc] initWithCapacity:newDimensions.width];
NSLog(#"Got this far");
[self addObject:anArray];
NSLog(#"woot");
[anArray release];
}
NSLog(#"Finished Initializing grid");
}
return self;
}
- (void)addObject:(id)anObject toRow:(NSUInteger)row {
[[self objectAtIndex:row] addObject:anObject];
}
#end
And here is what is appearing on the console:
2009-08-12 15:27:02.076 Flipswitch[1756:20b] Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: ' -[NSMutableArray initWithCapacity:]: method only defined for abstract class. Define -[SDMutableGrid initWithCapacity:]!'
2009-08-12 15:27:02.080 Flipswitch[1756:20b] Stack: (
807902715,
2536648251,
808283725,
808264737,
13690,
11018,
10185,
814713539,
814750709,
814739251,
814722434,
814748641,
839148405,
807687520,
807683624,
814715661,
814752238,
10052,
9906
)
The short, easy answer: Don't make a subclass of NSArray. It's better to make a category on NSArray or make an NSObject subclass that has an NSArray ivar that you talk to.
The long, technical answer: NSArray is a class cluster. This means that it isn't actually one class, but many classes operating under the NSArray abstract class interface that are each implemented in a different way (say, one implementation for small arrays, another for big arrays, etc.). To create a subclass of a class cluster, you have to implement all the primitive methods of the abstract class you are inheriting from, manage your own storage and basically reimplement all the stuff you were hoping to get for free by subclassing.
More simply, you could just create a category if you don't require additional ivars. If you want an object that behaves like an array with additional state, you can create a class that has an NSArray and use Objective-C message forwarding to forward everything except your custom behavior to that class.
This is due to the nature of 'Class Clusters' used for collection classes in Foundation.
See:
Class Clusters
Basically, NSMutableArray defines a public interface to 'mutable arrays', but is not the actual class you use when initialized. So 'initWithCapacity:' is defined, but not implemented in NSMutableArray. If you run:
NSMutableArary *foo = [[NSMutableArray alloc] init];
NSLog(#"%#", [foo className]);
you will print "_NSCFArray", which is a concrete subclass of NSMutableArray (and NSArray). To work around this, I would have a instance variable that is an NSMutableArray, or implement 'initWithCapacity:' with a suitable meaning (such as a capaciy of '3' means a 3x3 grid).
Ok, I found the answer from this question
Although the questions are different, the answer is the same and that is that due to the setup of NSArray (and therefore NSMutableArray), you cannot subclass it without implementing the methods yourself.
So I guess I'll just make SDMutableGrid have an NSMutableArray variable instead of actually being an NSMutableArray.
You problem is that you are not implementing abstract methods of NSMutableArray super class that need to be implemented, it says
-[NSMutableArray initWithCapacity:]: method only defined for abstract class. Define -[SDMutableGrid initWithCapacity:]!' 2009-08-12 15:27:02.080 Flipswitch[1756:20b]
So you need do define initWithCapacity in your subclass, I would recommend to not extend NSMutableArray, there is no need, just make a class that has a mutable array in it.
Related
this is a stupid question but it's driving me crazy!!
I have a simple ARC class that implements 2 NSMutableSet and 1 NSMutableArray. These 3 variables are private so they are not exposed using #property but I know that, by default, the object var of the class are defined as __strong.
Problem is that in a certain time of future a class method, -(id)drawObject, access internally to the NSMutableArray and it crashes as soon as the method access to the _outObjects var. So debugging seams that the NSMutableArray initialized inside the init is gone (I'm able to read it inside init, it's empty obviously, but I can't read it inside drawObject because is deallocated.)
The problem could be solved changing the init line from
_outObjects = [NSMutableArray arrayWithCapacity:_objects.count];
to
_outObjects = [[NSMutableArray alloc] initWithCapacity:_objects.count];
but I expected the same result using ARC!! Apple say that the + init methods and - init methods are the same using ARC! It should change only the way as the preprocessor completes the class.
Declaring NSMutableArray *_outObjects; as NSMutableArray __strong *_outObjects; is obviously useless because it already is by default.
Here the header
#interface GTGameBag : NSObject {
#protected
NSMutableSet *_objects;
NSMutableSet *_innerObjects;
NSMutableArray *_outObjects;
}
- (id)initWithObjects:(NSSet*)objects;
- (id)drawObject;
and here the implementation
- (id)initWithObjects:(NSSet *)objects {
self = [super init];
if (self) {
_objects = [objects mutableCopy];
_outObjects = [NSMutableArray arrayWithCapacity:_objects.count];
_innerObjects = [_objects mutableCopy];
}
return self;
}
- (id)drawObject {
id obj = [_innerObjects anyObject];
[_innerObjects removeObject:obj];
[_outObjects addObject:obj];
return obj;
}
How is possible this different behavior using ARC while allocating an empty array using the + and the - method?
Thank you in advance.
Gabriele.
I have a small function which I want to rewrite, so that function is valid for every class.
At the moment I have 10 of the same functions which all work same but every function is for another class.
I know, that I have to do it with reflections, but I am not so sure how to do it.
I already read this link:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html
The functions I am talking about are:
-(NSCountedSet *)MissionGetReferecedNested:(id)modelObject
{
setOfObjects = [[NSCountedSet alloc]initWithArray:modelObject.MissionSectionList];
return setOfObjects;
}
-(NSCountedSet *)MissionGetSectionReferecedNested:(id)modelObject
{
setOfObjects = [[NSCountedSet alloc]initWithArray:modelObject.DamageAccountList];
return setOfObjects;
}
MissionSectionList and DamageAccountList are both NSMutableArrays from two different classes.
Is it possible to see if a class consists a NSMutableArray and if yes then it should call the .... modelObject.MyMutableArray?
You can use reflection like this:
- (NSCountedSet *)MissionGet:(id)modelObject
{
SEL propertySelector = NULL;
if ([modelObject respondsToSelector:#selector(MissionSectionList)]) {
propertySelector = #selector(MissionSectionList);
} else if ([modelObject respondsToSelector:#selector(DamageAccountList)]) {
propertySelector = #selector(DamageAccountList);
}
if (!propertySelector) {
[NSException raise:#"Invalid modelObject value" format:#"Model object %# does not contain any recognised selectors", modelObject];
}
return [[NSCountedSet alloc] initWithArray:[modelObject performSelector:propertySelector]];
}
But a more common technique among cocoa programmers would be:
- (NSCountedSet *)MissionGet:(id <MyCustomProtocol>)modelObject
{
return [[NSCountedSet alloc] initWithArray:[modelObject missionArray]];
}
Where you would accept any object which confirms to the protocol MyCustomProtocol. The protocol is defined in a header files somewhere, using:
#protocol MyCustomProtocol
#property (readonly) NSArray *missionArray;
#end
And then in each of your classes, declare it as implementing the protocol:
#interface MissionSectionListClass <MyCustomProtocol>
And add a method implementation:
#implementation MissionSectionListClass <MyCustomProtocol>
- (NSArray *)missionArray
{
return self.MissionSectionList;
}
#end
Using protocols is a bit more code, but it's the "right" way to go. It allows you to add support for new classes, without any change to your MissiongGet... method.
More info about protocols: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProtocols.html
EDIT : Cleared all my answer to this :
I think it's not possible to check if a class has a member variable of specified type. You can only check if a class has a specified method.
So, in this case it will be best if you make all your NSMutableArray list the same name, and then create a declared property for this list, and then do a respondsToSelector in your ...GetReferencedNested method.
So, for example, in all of your class create this property :
#property (nonatomic, retain) NSMutableArray * list;
and then in the ..MissionGetReferencedNested method :
if ([modelObject respondsToSelector:#selector(list)])
...
Correct me if i'm wrong...
In terms of style I'd also follow Abhi's suggestion.
But if you really want to inspect a class that you are stuck with and, for example build a NSCountedSet with the first NSMutableArray variable you can find, you could do it like this:
#import "Utilities.h"
#import <Foundation/Foundation.h>
#import <objc/objc-runtime.h>
#implementation Utilities
+ (NSCountedSet*)initCountedSetWithFirstArrayinObject:(id)someObject {
unsigned int c;
Ivar *ivar_arr = class_copyIvarList([someObject class], &c);
for (unsigned int i = 0; i < c; i++) {
if ([#"#\"NSMutableArray\"" isEqualToString:
[NSString stringWithCString:ivar_getTypeEncoding(ivar_arr[i]) encoding:NSUTF8StringEncoding]
]) {
return [[NSCountedSet alloc] initWithArray:object_getIvar(someObject, ivar_arr[i])];
}
}
return nil;
}
#end
Of course this has very limited real world use because it depends on you knowing that the first array will be the one you're interested in.
I think I have to go with the runtime type editing.(http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html)
The idea with the protocols was good but there I have to change a lot of things in the classes.(which is not possible/allowed) for me. My intension was only to change the functions so that I have only one function for all classes.
I think with the runtime type editing I can check what classes and attributes I have (?) Am I right?
Did somebody already work with runtime type editing?
If I want to pass an object that was created on the main thread onto an NSOperation object, what's the standard way of doing to so that I'm not creating any memory management issues? Should I make my object's properties not have the 'nonatomic' attribute?
Right now, I allocate the objects via [[[AClass alloc] init] autorelease], keep a copy of the instance on my main thread and then pass another copy into the NSOperation as part of an NSArray. When I try to iterate through the array list objects inside NSOperation class and access one of the AClass's properties, the debugger reports that one of the member properties of AClass's instance object is already zombied while others are not. The error I'm seeing is:
-[CFString retain]: message sent to deallocated instance 0x5a8c6b0
*** -[CFString _cfTypeID]: message sent to deallocated instance 0x5a8c6b0
*** -[CFString _cfTypeID]: message sent to deallocated instance 0x5a8c6b0
I can't tell who is releasing my string properties too early but the entire object instance has not been released.
My class looks like:
#interface AClass
{
NSString *myTitle;
NSString *myDescription;
}
#property (nonatomic, retain, readonly) NSString *myTitle;
#property (nonatomic, retain, readonly) NSString *myDescription;
#end
#implementation AClass
#synthesize myTitle, myDescription;
- (void)dealloc
{
[myTitle release];
[myDescription release];
}
#end
Here's an updated snippet for an efficient, 'thread-safe' version of AClass:
/**
AClass is an immutable container:
- category methods must never change the state of AClass
*/
#interface AClass : NSObject < NSCopying >
{
#private
NSString * title;
NSString * description;
}
/**
subclassing notes:
- do not override properties: title, description
- implement #protocol NSCopying
*/
/*
1) document copy on entry here, even though the compiler has no
additional work to do.
2) nonatomic in this case - these ivars initialized and never mutate.
3) readonly because they are readonly
*/
#property (copy, readonly, nonatomic) NSString * title;
#property (copy, readonly, nonatomic) NSString * description;
/* prohibited: */
- (id)init;
/* designated initializer */
- (id)initWithTitle:(NSString *)inTitle description:(NSString *)inDescription;
#end
#implementation AClass
#synthesize title;
#synthesize description;
- (id)init
{
assert(0 && "use the designated initializer");
self = [super init];
[self release];
return 0;
}
- (id)initWithTitle:(NSString *)inTitle description:(NSString *)inDescription
{
self = [super init];
assert(self && "uh oh, NSObject returned 0");
if (0 != self) {
if (0 == inTitle || 0 == inDescription) {
assert(inTitle && inDescription && "AClass: invalid argument");
[self release];
return 0;
}
/* this would catch a zombie, if you were given one */
title = [inTitle copy];
description = [inDescription copy];
if (0 == title || 0 == description) {
assert(title && description && "string failed to copy");
[self release];
return 0;
}
}
return self;
}
- (void)dealloc
{
/* which could also happen when if your init fails, but the assertion in init will be hit first */
assert(title && description && "my ivars are not meant to be modified");
[title release], title = 0;
[description release], description = 0;
/* don't forget to call through super at the end */
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone
{
assert(self.title == title && self.description == description && "the subclasser should not override the accessors");
if ([self zone] == zone && [self class] == [AClass class]) {
/*
this is one possible (optional) optimization:
- avoid using this approach if you don't entirely understand
all the outlined concepts of immutable containers and low
level memory management in Cocoa and just use the
implementation in 'else'
*/
return [self retain];
}
else {
return [[[self class] allocWithZone:zone] initWithTitle:self.title description:self.description];
}
}
#end
Beyond that, avoid overusing autorelease calls so your memory issues are local to the callsite. This approach will solve many issues (although memory issues may still exist in your app).
Update in response to questions:
Justin Galzic: so basically, the copy
ensures that objects are local to the
caller and when the instance is shared
out to the thread on which
NSOperations is on, they're two
different instances?
actually, the copy call to an immutable string could perform a retain.
as an example: AClass could now implement #protocol NSCopying by simply retaining the 2 strings. also, if you know AClass is never subclassed, you could just return [self retain] when the objects are allocated in the same NSZone.
if a mutable string is passed to the initializer of AClass, then it will (of course) perform a concrete copy.
if you want objects to share these strings, then this approach is preferred (in most cases) because you (and all clients using AClass) now know the ivars will never change behind your back (what they point to, as well as the strings' contents). of course, you still have the ability to make the mistake of changing what title and description point to in the implementation of AClass - this would break the policy you've established. if you wanted to change what the members of AClass pointed to, you'd have to use locks, #synchronized directives (or something similar) - and then typically set up some observation callbacks so you could guarantee that your class works as expected... all that is unnecessary for most cases because the above immutable interface is perfectly simple for most cases.
to answer your question: the call to copy is not guaranteed to create a new allocation - it just allows several guarantees to propagate to clients, while avoiding all thread safety (and locking/synchronizing).
What if there are some cases where you
do want multiple classes (on the same
thread) to share this object? Would
you then make an implicit copy of this
object and then pass along to the
NSOperation?
now that i've detailed how copying immutable objects can be implemented. it should be obvious that properties of immutable objects (NSString, NSNumber, etc.) should be declared copy in many cases (but many Cocoa programmers don't declare them that way).
if you want to share a NSString which you know is immutable, you should just copy it from AClass.
if you want to share an instance of AClass you have 2 choices:
1) (best) implement #protocol NSCopying in AClass: - (id)copyWithZone: implementation added above.
now the client is free to copy and retain AClass as is most logical for their needs.
2) (BAD) expect that all clients will keep their code up to date with changes to AClass, and to use copy or retain as required. this is not realistic. it is a good way to introduce bugs if your implementation of AClass needs to change because clients will not always update their programs accordingly. some people consider this acceptable when the object is private in a package (e.g., only one class uses and sees its interface).
in short, it's best to keep the retain and copy semantics predictable - and just hide all the implementation details in your class so your clients' code never breaks (or is minimized).
if your object is truly shared and its state is mutable, then use retain and implement callbacks for state changes. otherwise, keep it simple and use immutable interfaces and concrete copying.
if an object has an immutable state, then this example is always a lock free thread safe implementation with many guarantees.
for an implementation of an NSOperation subclass, i find it best (in most cases) to:
- create an object which provides all the context it needs (e.g., an url to load)
- if the something needs to know about the operation's result or to use the data, then create a #protocol interface for the callbacks and add a member to the operation subclass which is retained by the NSOperation subclass, and which you've prohibited from pointing to another object during the lifetime of the NSOperation instance:
#protocol MONImageRenderCallbackProtocol
#required
/** ok, the operation succeeded */
- (void)imageRenderOperationSucceeded:(AClass *)inImageDescriptor image:(NSImage *)image;
#required
/** bummer. the image request failed. see the #a error */
- (void)imageRenderOperationFailed:(AClass *)inImageDescriptor withError:(NSError *)error;
#end
/* MONOperation: do not subclass, create one instance per render request */
#interface MONOperation : NSOperation
{
#private
AClass * imageDescriptor; /* never change outside initialization/dealloc */
NSObject<MONImageRenderCallbackProtocol>* callback; /* never change outside initialization/dealloc */
BOOL downloadSucceeded;
NSError * error;
}
/* designated initializer */
- (id)initWithImageDescriptor:(AClass *)inImageDescriptor callback:(NSObject<MONImageRenderCallbackProtocol>*)inCallback;
#end
#implementation MONOperation
- (id)initWithImageDescriptor:(AClass *)inImageDescriptor callback:(NSObject<MONImageRenderCallbackProtocol>*)inCallback
{
self = [super init];
assert(self);
if (0 != self) {
assert(inImageDescriptor);
imageDescriptor = [inImageDescriptor copy];
assert(inCallback);
callback = [inCallback retain];
downloadSucceeded = 0;
error = 0;
if (0 == imageDescriptor || 0 == callback) {
[self release];
return 0;
}
}
return self;
}
- (void)dealloc
{
[imageDescriptor release], imageDescriptor = 0;
[callback release], callback = 0;
[error release], error = 0;
[super dealloc];
}
/**
#return an newly rendered NSImage, created based on self.imageDescriptor
will set self.downloadSucceeded and self.error appropriately
*/
- (NSImage *)newImageFromImageDescriptor
{
NSImage * result = 0;
/* ... */
return result;
}
- (void)main
{
NSAutoreleasePool * pool = [NSAutoreleasePool new];
NSImage * image = [self newImageFromImageDescriptor];
if (downloadSucceeded) {
assert(image);
assert(0 == error);
[callback imageRenderOperationSucceeded:imageDescriptor image:image];
[image release], image = 0;
}
else {
assert(0 == image);
assert(error);
[callback imageRenderOperationFailed:imageDescriptor withError:error];
}
[pool release], pool = 0;
}
#end
If there is something I do not recommend, it is that you are keeping your reference unretained by the knowledge that it will be retained by someone else already. Because the reference is unretained, when it is removed from the array it will be released, retain count might drop to zero and object might be dealloc and now you are holding time bomb i.e. invalid reference.
I would suggest that do not autorelease the reference, and try to do break point at your dealloc and see the stack call to see who cause your object to be dealloc.
I'm new to Objective-C, but experienced in C++ and C.
I want to store some enum constants in an array. In C++ I would do something like this:
enum color {RED, BLUE, YELLOW, GREEN};
vector<color> supportedColors;
supportedColors.push_back(RED);
supportedColors.push_back(GREEN);
But the NSArray will only store object pointers (id's). So how should they be stored? I could possibly cast them to integers and store them in an NSNumber object, but this seems messy.
I wonder what experienced obj-c programmers do?
Cast them to integers and store them in NSNumbers. :)
Native C types are really second class citizens in the Cocoa collection classes, and are often verbose to work with if you want to intermingle. C says that enums have integral values, so it's safe to use them as ints in this way.
Depending on what you're doing of course, you can simplify the manipulation code by wrapping that enum in an actual object ("MyColor") that has the enum as a property on it. These objects could then be tossed around in the collection classes as desired, with some upfront and runtime overhead that are unlikely to matter from a perf standpoint, depending on what you're doing.
You are perhaps looking for a way to simply loop through all of the options? How about just a plan old normal array?
typedef enum {RED,BLUE,GREEN,YELLOW} color;
color colors[4]={RED,YELLOW,GREEN,BLUE};
for (int i=0;i<4;i++)
colors[i];
On the other hand if performance isn't an issue and you are just looking to clean up the code a little; how about creating a class ColorArray that encapsulates NSMutableArray and creates the relevant methods.
ColorArray.h:
#import <Foundation/Foundation.h>
typedef enum {RED,BLUE,GREEN,YELLOW} Color;
#interface ColorArray : NSObject {
NSMutableArray* _array;
}
- (id) initWithArray:(Color[])colors;
- (void) addColor:(Color)color;
- (Color) colorAtIndex:(int)i;
#end
ColorArray.c:
#import "ColorArray.h"
#implementation ColorArray
- (id) init {
if (self = [super init]) {
_array = [[NSMutableArray alloc] init];
}
return self;
}
- (id) initWithArray:(Color[])colors {
if (self = [super init]) {
_array = [[NSMutableArray alloc] init];
for (int i=0;colors[i]!=0;i++)
[_array addObject:[NSNumber numberWithInt:colors[i]]];
}
return self;
}
- (void) dealloc {
[_array release];
[super dealloc];
}
- (void) addColor:(Color)color {
[_array addObject:[NSNumber numberWithInt:color]];
}
- (Color) colorAtIndex:(int)i {
return [[_array objectAtIndex:i] intValue];
}
#end
i would make a new class define the enum, and then instantiate supported colours as
vector<colorEnumClass> supportedColours
you can then do things like : give the class methods for testing a colour to see if it is a member of the vector .. then you can use that method in IF statements rather than explicit testing using relational operators. Clearer coding :-)
notice that the name supportedColours suggests that this vector should be a constant, defined at program start and never changed. If this is the case then the "colours" in the vector should be set in the constructor and never changed. The class should be implemented as a Singleton and you might even override the vector operators of pushback() etc to block modifications.
This is in the nature of the "new" Java enumeration technology
i should also mention that i am too new to Objective C to provide a syntactically correct code example .. sorry.
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.