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.
Related
I have UIViewController with strong DataController containing and managing a list of items (itemsList). In the initialization method of the latter, I read items from a file, get additional information for each of these via web services (and ASIHTTPRequest) and put the items in a list which is then assigned to the itemsList property.
With synchronous requests everything works fine but I need an asynchronous implementation. I did it and now my items get deallocated (the data controller not), following the delegates for the requests etc disappear as well. How can I keep the items alive?
In my data controller:
-(id)initDataController
{
self = [super init];
if (self)
{
NSMutableArray *myList = [[NSMutableArray alloc] init];
// Read items from file ...
for (NSString *itemName in items) {
MyItem *item = [[MyItem alloc] initWithParam:itemName];
// Here the item has already been deallocated?!
if (item) {
[myList addObject:item];
}
}
_itemsList = myList;
return self;
}
return nil;
}
In MyItem class there is just standard request to the server and initialization. I think the problem should be in some missing strong/retain but I have no idea where I should put it.
EDIT:
The definition of the list:
#property (nonatomic, copy) NSMutableArray *itemsList;
What I am wondering is that the items are nil even before I can put them into the list... So I tried making the property strong instead of copy but nothing changes.
MyItem *item = ...
This local variable forms a strong reference. If initWithParam: returns an object (not nil), there'w no way it can be deallocated before being added to the list (which creates another strong reference).
Please take note that your property declaration is flawed:
#property (nonatomic, copy) NSMutableArray *itemsList;
The copy attribute does not go well with a mutable object type: When the property is assigned, the synthesized setter calls copy on it, which creates an immutable copy of the array and assigns that. This contradicts the type of the property.
Here's in fact a nice opportunity for another helpful clang compiler warning: Properties cannot be declared copy when their type conforms to NSMutableCopying. clang team, are you hearing?.
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 *
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'm new to objective-c and I searched and read several posts here on how to create "global variable" but I just can't get it to work right, so far I can create it and check it but the values are not persisting on another views, my global var is an array of a custom object called "profile", I would like to be able to read and write that array from any view of my iphone app (tabbarapplication delegate);
Helper.h
#interface Helper : NSObject {
int globalInteger;
NSMutableArray *profiles;
}
#property (nonatomic, retain) NSMutableArray *profiles;
// message from which our instance is obtained
+ (Helper *)sharedInstance;
Helper.m
#import "Helper.h"
#implementation Helper
#synthesize profiles, globalInteger;
+ (Helper *)sharedInstance
{
// the instance of this class is stored here
static Helper *myInstance = nil;
// check to see if an instance already exists
if (nil == myInstance) {
myInstance = [[[self class] alloc] init];
// initialize variables here
}
// return the instance of this class
return myInstance;
}
ACertainViewController.m
//Initialize Policies Array
NSMutableArray *profs = [[Helper instance] profiles];
profs = [[NSMutableArray alloc] init];
//Sample Data
Profile *prof1 = [[Profile alloc] init];
prof1.name = #"John";
//add
[profs addObject:prof1];
[[[Helper instance] profiles] addObject:prof1];
After this point if I check the global var "profiles" contents again it returns count == 0;
As of the globalInteger var I don't even know how to set its value to be able to read somewhere else in the app.
Any help is much appreciated!
Thanks!!!
You need to move "static Helper *myInstance = nil" outside the class method. Now, you're setting it to nil each time and so each time you access the sharedInstance it gets reallocated.
Declare your NSMutableArray in your AppDelegate (i.e. MyAppDelegate) class. Then from another class (like your view controller), you can do this:
#import "MyAppDelegate.h"
MyAppDelegate *aDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate;
aDelegate.profiles = .... // or do whatever you need to do with the profiles property.
hope that helps.
You need to alloc/initialize the profiles array. try this:
// the instance of this class is stored here: thanks #onnoweb for pointing this out
static Helper *myInstance = nil;
+ (Helper *)sharedInstance
{
// check to see if an instance already exists
if (nil == myInstance) {
myInstance = [[[self class] alloc] init];
// initialize variables here
profiles=[[NSMutableArray alloc] init];
}
// return the instance of this class
return myInstance;
}
Also take a look here: http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html
"instance variable 'profiles' accessed in class method"
Where the code you posted has the comment // initialize variables here, are you actually accessing the variable profiles? Instead use myInstance.profiles.
"warning: incomplete implementation of class 'Helper' warning: method definition for '+instance' not found"
There some code you're not showing us, or the code you posted is different from your real code. There is no method declared or defined in the code you posted called 'instance', but you are attempting to call a method called 'instance'. There is one by a different name called 'sharedInstance'. Most likely in your real code you mixed up the names and declared 'instance' but defined 'sharedInstance'. Pick one name and stick with it.
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.