NSMutableArray containsObject returns true even the address and data is different.
I've seen this post NSMutableArray containsObject returns true, but it shouldnt
already but still I'm not finding my solution:
Below is my scenario:
NSMutableArray *destClasses = [NSMutableArray array];
id sourceClasses = [dict objectForKey:#"Classes"];
if ([sourceClasses isKindOfClass:[NSArray class]]) {
for (NSDictionary *class in sourceClasses) {
MyClass *a = [[MyClass alloc] init];
[a arrangeClassWithDictionary:classDict]; //this methods assigns value to a from classDict
if (![destClasses containsObject:a]) {
[destClasses addObject:a];
}
}
}
In the first iteration destClasses adds an MyClass object and on the second iteration [destClasses containsObject:a] returns true even though the a has different address and different values assigned.
What I'm doing wrong here. Please help.
I got the answer.
containsObject: which sends the isEqual: message to every object it
contains with your object as the argument. It does not use == unless
the implementation of isEqual: relies on ==.
I've to override the isEqual: method to provide equality checking for my object fields like below,
- (BOOL)isEqual:(id)object
{
BOOL result = NO;
if ([class isKindOfClass:[self class]]) {
MyClass *otherObject = object;
result = [self.name isEqualToString:[otherObject name]];
}
return result;
}
I am getting data from Dictionary. It works well and stores data in NSMutableArray I want that before adding object into need to make sure that Array does not contain same object with Same Name and Type. Please see below.
Before inserting object we should check that it does not contain object with Type and Name if contains no need to insert.
NSArray *resultDic = [result1 objectForKey:#"results"];
for (int i = 0; i<[resultDic count]; i++) {
id item = [resultDic objectAtIndex:i];
NSDictionary *jsonDict = (NSDictionary *) item;
GetData *theObject =[[GetData alloc] init];
NSString*error = [jsonDict valueForKey:#"error"];
if(![error isEqualToString:#"No Record Found."])
{
[theObject setVaccineID:[jsonDict valueForKey:#"ID"]];
[theObject setVaccineName:[jsonDict valueForKey:#"Name"]];
[theObject setVaccinationType:[jsonDict valueForKey:#"Type"]];
[theObject setVaccineType:[jsonDict valueForKey:#"VType"]];
[theObject setFarmName:[jsonDict valueForKey:#"FName"]];
[theObject setDay:[jsonDict valueForKey:#"Day"]];
[theObject setAddedDateTime:[jsonDict valueForKey:#"DateTime"]];
[appDelegate.dataArray addObject:theObject];
}
}
A general purpose solution is to teach your GetData object how to compare itself to others. If they can be compared, then it will be easy to determine if a match is in any collection (and you might want to compare them in other contexts, too). Do this by implementing isEqual:. That might look something like this:
// in GetData.m
- (BOOL)isEqual:(id)object {
if ([object isKindOfClass:[GetData self]]) {
// assuming that the object is fully characterized by it's ID
return [self.vaccineId isEqual:((GetData *)object).vaccineId];
}
else {
return NO;
}
}
// have the hash value operate on the same characteristics as isEqual
- (NSUInteger)hash {
return [self.vaccineId hash];
}
With that done, you can take advantage of NSArray's containsObject:.
// ...
if(![appDelegate.dataArray containsObject:theObject] && ![error isEqualToString:#"No Record Found."])
// ...
I have the following code in various parts of my app:
MyPFObjectSubclass *instance = [MyPFObjectSubclass object];
instance.myMutableArray = [NSMutableArray array];
instance.myMutableDictionary = [NSMutableDictionary array];
I am constantly forgetting to initialize these types, and running into problems later when I attempt setValue:forKey or addObject methods.
It's a nice-to-have, I admit, but I would like to play it safe and roll these initializations into +object if possible.
My PFObject subclasses all look roughly like this. I call [MyPFObject tlObject] to create a new instance.
#implementation MyPFObject
#dynamic objectUUID;
// Other fields
+ (MyPFObject*) tlObject
{
MyPFObject* obj = [self object];
[obj tlObjectInit];
// Other initializations
return obj;
}
+ (NSString*) parseClassName
{
return #"MyPFObject";
}
+ (PFQuery*) query
{
PFQuery* query = [PFQuery queryWithClassName: [self parseClassName]];
// Add includeKeys here
return query;
}
#end
I have a category on PFObject that includes tlObjectInit. The field objectUUID is there so that I have a value that can uniquely identify the object BEFORE IT IS SAVED. This is necessary because I sometimes create sets of objects that refer to one another. The Parse objectID is not set until it is saved.
#implementation PFObject (TL)
- (void) tlObjectInit
{
NSString* format = [[self parseClassName] stringByAppendingString: #"-%#"];
[self setObject: [NSUUID uuidStringInFormat: format]
forKey: #"objectUUID"];
}
// Add other initializations here
#end
In your MyPFObjectSubclass, override the init method:
-(instancetype) init
{
if (self = [super init])
{
_myMutableArray = [NSMutableArray array];
_myMutableDictionary = [NSMutableDictionary dictionary];
}
return self;
}
I guess your object method call one way or the other the init method.
Edit:
It looks like you use the Parse framework. As told in the reference, the PFObject init method shouldn't be overridden by subclasses.
I have an NSMutableArray where objects can be added to. The objects added are SUPDataValue objects containing a bunch of SUP data.
In an other view, I take this object array and divide it into an NSMutableArray containing an NSMutableArray for every section in the tableview.
When add another SUPDataValue object to my initial Object array, and I switch back to my table view, I want to re-read all the object from the object array and see if those objects exist in my layered array.
I am using the "objectExists" and it works great... however... if I add the same object twice to my object array, it will always assume it exists in a layer.
This is my code:
- (void)setInitialComponents:(NSMutableArray*)components
{
if (self.componentLayer)
{
for (SUPDataValueList *val in components)
{
BOOL found = NO;
for (NSMutableArray *layer in self.componentLayer)
{
if ([layer containsObject:val])
{
found = YES;
}
}
if (!found)
{
[[self.componentLayer objectAtIndex:0] addObject:val];
}
}
}
else {
self.componentLayer = [NSMutableArray array];
// Add the no-layer layer (section 0)
[self.componentLayer addObject:[NSMutableArray array]];
if (self.addMode)
{
[[self.componentLayer objectAtIndex:0] addObjectsFromArray:components];
}
else {
for (SUPDataValueList * val in components)
{
int layer = [[NSString stringWithFormat:#"%#", [val item:38]] intValue];
if (self.componentLayer.count < layer)
{
[self.componentLayer insertObject:[NSMutableArray array] atIndex:layer-1];
}
[[self.componentLayer objectAtIndex:layer-1] addObject:val];
}
}
}
[self.tableView reloadData];
}
As you might have guessed, my problem is here:
if ([layer containsObject:val])
{
found = YES;
}
I would like to check if an unique object exist in that array (using memory allocation ID or something?)
How do I do that?
The containsObject method will invoke isEqual: on the underlying objects being compared.
Unless you implement isEqual: in the SUPDataValueList object, it will simply do a pointer comparison which is the default behavior of isEqual in NSObject.
You're looking for -[NSArray indexOfObjectIdenticalTo:], which uses the objects' addresses to determine a match.
found = [layer indexOfObjectIdenticalTo:val] != NSNotFound;
I'm writing some code for an iPhone app, and I'm having issues getting default data to load in correctly. I am basing my code off some example from the "Learning Cocos2d" book by Ray Wenderlich.
It seems that even when I delete the app outright and try to start from fresh data that the app inconsistently either doesn't try to load the data, or incorrectly thinks that there is data, and loads null.
I'm using containsValueForKey to check if a value exists and then load it or load some default value, but even on a fresh installation the containsValueForKey finds data and doesn't load the defaults. In xcode's organizer I checked my device's file structure and the Documents folder, where I specified to save, doesn't look like it contains any files, so I'm not sure what it's grabbing.
My guess is that the problem is something to do with the initWithCoder function. It seems to mysteriously go through the function sometimes, but not all the time. Another weird thing is that I call [[GameManager sharedGameManager] save] when the player gets a highscore (not shown here, but the code is the exact same as this objectiveList, only an int) and it appears to save it correctly.
And now the code:
GCDatabase.h
#import <Foundation/Foundation.h>
id loadData(NSString * filename);
void saveData(id theData, NSString *filename);
GCDatabase.m
#import "GCDatabase.h"
NSString * pathForFile(NSString *filename) {
// 1
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
// 2
NSString *documentsDirectory = [paths objectAtIndex:0];
// 3
return [documentsDirectory stringByAppendingPathComponent:filename];
}
id loadData(NSString * filename) {
NSString *filePath = pathForFile(filename);
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSData *data = [[[NSData alloc] initWithContentsOfFile:filePath] autorelease];
NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
id retval = [unarchiver decodeObjectForKey:#"Data"];
[unarchiver finishDecoding];
return retval;
}
return nil;
}
void saveData(id theData, NSString *filename) {
NSMutableData *data = [[[NSMutableData alloc] init] autorelease];
NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
[archiver encodeObject:theData forKey:#"Data"];
[archiver finishEncoding];
[data writeToFile:pathForFile(filename) atomically:YES];
}
GameManager.h
#interface GameManager : NSObject <NSCoding>{
NSMutableArray *objectiveDescriptions;
}
#property (nonatomic, retain) NSMutableArray * objectiveDescriptions;
+(GameManager*)sharedGameManager;
-(void)save;
-(void)load;
-(void)encodeWithCoder:(NSCoder *)encoder;
-(id)initWithCoder:(NSCoder *)decoder;
#end
GameManager.m (I added the load function, in an attempt to force it to load, but it doesn't seem to work)
+(GameManager*)sharedGameManager {
#synchronized([GameManager class])
{
if(!sharedGameManager) {
sharedGameManager = [loadData(#"GameManager") retain];
if (!sharedGameManager) {
[[self alloc] init];
}
}
return sharedGameManager;
}
return nil;
}
+(id)alloc {
#synchronized([GameManager class]){
NSAssert(sharedGameManager == nil, #"Attempted to allocate a second instance of the Game Manager singleton");
sharedGameManager = [super alloc];
return sharedGameManager;
}
return nil;
}
- (void)dealloc {
[objectiveList release];
[super dealloc];
}
- (void)save {
saveData(self, #"GameManager");
}
-(void)load {
loadData(#"GameManager");
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:objectiveList forKey:#"objectiveList"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self != nil) {
if ([decoder containsValueForKey:#"objectiveList"]) {
objectiveList = [decoder decodeObjectForKey:#"objectiveList"];
} else {
[objectiveList addObjectsFromArray:[NSArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5", nil]];
}
}
return self;
}
#end
I have not read your full code.. But I found a problem in code....
You have not allocated memory to objectiveList array.. Unless and until you allocate memory to array, objects will not be added...
I think go for
objectiveList = [[NSMutableArray alloc] initWithArray:[NSArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5", nil]];
instead of
[objectiveList addObjectsFromArray:[NSArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5", nil]];
Check for the syntax.. Hope this may help as it troubled me also in the past where I forgot to allocate memory to the array.. And kept on adding objects resulting in null... :)
In case it doesn't solve your problem, I'll look for code later completely.. :)
I seem to see the problem. When the constructor is called the first time, the objectiveList is not even created as the "initWithCoder" is never called. You have to override the init method as well in order for the objectiveList array to be constructed. Basically, the code that is calling the init method is in here:
+(GameManager*)sharedGameManager {
#synchronized([GameManager class])
{
if(!sharedGameManager) {
sharedGameManager = [loadData(#"GameManager") retain];
if (!sharedGameManager) {
[[self alloc] init]; // GOES INTO INIT METHOD, NOT INITWITHCODER!
}
}
return sharedGameManager;
}
return nil;
}
On a side note, that singleton implementation gave me a headache. Just saying. :)
There is (as far as I can see from the code you have provided) a logic flaw in your code. Consider what would happen if decoder did not contain an objectiveList key; the else clause would execute, but you never allocated objectiveList so the addObjectsFromArray: call will silently fail.
To test this theory, alter your code as show below, and rerun. If the assertion fires then the above theory is correct, if not you need to hunt a bit more!
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self != nil)
{
if ([decoder containsValueForKey:#"objectiveList"])
{
objectiveList = [decoder decodeObjectForKey:#"objectiveList"];
}
else
{
NSAssert(objectiveList, #"objectiveList must be non-nil to add objects.");
[objectiveList addObjectsFromArray[NSArrayarrayWithObjects:#"1",#"2",#"3",#"4",#"5", nil]];
}
}
return self;
}
By the way, objectiveList is never declared as an ivar... I am sort of assuming that objectiveList and objectiveDescriptions are meant to be the same.
The method in GameManager.m should look like this:
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self != nil) {
if ([decoder containsValueForKey:#"objectiveList"]) {
objectiveList = [[decoder decodeObjectForKey:#"objectiveList"] retain];
} else {
objectiveList = [[NSMutableArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5", nil];
}
}
You have two cases: either objectiveList is present, in which case you have previously saved some data, or it is not present and you need to create the default data (1, 2, 3, 4, 5). In the code above, I have changed the first case to retain the array returned by decodeObjectForKey, since Apple's docs state that this method returns an autorelease object. You need to retain it here to prevent the memory from being reused for some other objects that are created later in your app. By not retaining objectiveList, when accessing it later you were probably accessing garbage results (i.e. random memory) rather than what you had just decoded.
On a similar note, in the second case where objectiveList was not already present - i.e. for a new install of the app where there is no saved data present - you are not allocating objectiveList before trying to add objects to it. I have changed this line to actually alloc the object (and therefore the memory required), and then init with the default values you want. Since you were previously trying to add items to an array that had not been created, you would again get garbage data when trying to access the values from it. Note that I assume you are using an NSMutableArray here, but you might also be using an NSMutableSet.