Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IOS 5: How to return YES on contains: with 2 'identical' objects, but with different pointers

I'm quite new to StackOverflow, so apologies in advance if I forgot some critical element in my question. It's a long one, so bear with me!

Iterating over JSON-webservice result:

For an IOS5 iPad app, I collect JSON information from a webservice and via:

NSData *data = [NSData dataWithContentsOfURL:url];
NSDictionary *json = 
[NSJSONSerialization JSONObjectWithData:data options:kNilOptions      
error:&error];

I'll iterate over the returned NSDictionary and store the returned objects and their properties as a custom class.

The custom class contains several properties, including an AmountInShoppingCart-value.

I use this custom class to represent items that we sell in our store, so an NSMutableArray is made of all the returned objects and shown to the user. If the AmountInShoppingCart value > 0, then the object will be put in the ShoppingCart NSMutableArray (which is a singleton).

Checking for duplicates in ShoppingCart:

To make sure the ShoppingCart doesn't contain any duplicate entries, I've got a function like this in the ShoppingCartController (also borrowed from SO):

- (void)checkForDuplicates
{
    NSMutableSet *seen = [NSMutableSet set];
    NSUInteger i = 0;
    NSLog(@"ShoppingCartArray count: %u",[ShoppingCartArray count]);
    //Iterate over the Array to check for duplicates, and to sync the list with the   
    cart.
    while (i < [ShoppingCartArray count]) {
        id obj = [ShoppingCartArray objectAtIndex:i];
        if ([seen containsObject:obj]) {
            if ([obj isKindOfClass:[MyCust class]]){
                MyCust *cust = [ShoppingCartArray objectAtIndex:i];
                MyCust *seenCust = [seen member:obj];
                seenCust.AmountInShoppingCart = cust.AmountInShoppingCart;
                [ShoppingCartArray removeObjectAtIndex:i];
            }} else {seen addObject:obj];
            i++;}}}

Equality & Hashing

To accomplish this, I've overridden the isEqual:-function of the MyCust-class so that it'll look at the externalItemID-property, which is the only property that is definitely unique for each class object.

I picked this function up from StackOverflow:

- (BOOL)isEqual:(id)otherObject;
{
    if ([otherObject isKindOfClass:[MyCust class]]) {
        MyCust *otherCust= (MyCust *)otherObject;
        if (externalItemId != [otherCust externalItemId]) return NO;
        return YES;
    }
    return NO;
}

- (NSUInteger) hash;
{
    return [[self externalItemId] hash];
}

Sync the two arrays:

It can happen that people request the same result from the JSON-webservice, since it uses the MyCust's size, height and diameter to return a set of objects that adhere to those sizes.

So I use the following function to check and 'sync' the two NSMutableArrays with the new results:

In: RESULTLISTCONTROLLER.M

- (void) syncWithCart
{
    ShoppingCart *sc = [ShoppingCart sharedCart];
    [sc checkForDuplicates];
    NSMutableSet *listSet = [NSMutableSet setWithArray:resultList];
    NSUInteger i = 0;

    while (i < [sc.ShoppingCartArray count]) {
        id ShopObject = [sc.ShoppingCartArray objectAtIndex:i];
        if ([listSet containsObject:ShopObject])
        {
            MyCust *listCust = [listSet member:ShopObject];
            MyCust *shopCust = ShopObject;
            listCust.AmountInShoppingCart = shopCust.AmountInShoppingCart;
            [listSet removeObject:ShopObject];
        } else {i++;}}}

This all works pretty well until...

The 2nd webservice!

Now here comes the good part: There's a 2nd JSON-WebService the app uses to retrieve external supplier stock. It sometimes contains the same MyCust objects, but contains extra information that has to be parsed into the existing objects.

I basically get the first WebService's result into the getMyCustArray and the other in the getExternalCustArray. I then check if getMyCustArray contains an object in the getExternalCustArray and update the additional information on the objects and delete the similar object in getExternalCustArray. Any leftover objects only available from the supplier will be added using:

mergedCustArray = [NSMutableArray arrayWithArray:[getMyCustArray arrayByAddingObjectsFromArray:getExternalCustArray]];

Now the problem is, that when the user checks the same sizes twice, the App doesn't seem to consider the new results as the same.

When I do an NSLog of the ShoppingCartArray, the following shows: (original item names obfuscated)

First Search:

MyCust: SideShowBobsEarWarmers
 Hash: **2082010619**
 AmountInShoppingCart: 4
 **<MyCust: 0x81cee50>**",
    "
 MyCust: SolidSnakesCardBoardBox
 Hash: **4174540990**
 AmountInShoppingCart: 4
 **<MyCust: 0x8190d10>**" 

Second Search: the ResultList doesn't retain the MyCust's AmountInShoppingCart values, so I add them to the ShoppingCart again from the ResultList.

 MyCust: SideShowBobsEarWarmers
 Hash: **2082010619**
 AmountInShoppingCart: 4
 **<MyCust: 0x81cee50>**",

 MyCust: SolidSnakesCardBoardBox
 Hash: **4174540990**
 AmountInShoppingCart: 4
 **<MyCust: 0x8190d10>**" 
    "
// Different Pointers, although they should be considered Equal.

 MyCust: SideShowBobsEarWarmers
 Hash: **2082010619**
 AantalInWinkelWagen: 3
 **<MyCust: 0x74bc570>**",      
    "
 MyCust: SolidSnakesCardBoardBox
 Hash: **4174540990**
 AantalInWinkelWagen: 2
 **<MyCust: 0x74bc7b0>**"

So somehow the compiler allocates those objects in a different memory address/pointer, and then breaks the isEqual: function, as far as I can tell.

My guts tell me there's something to do with NSCopy(able), but I'm not quite sure how I'd implement that. If anyone got some good tips for a Shopping Cart system, that's always welcome too. I've got half a mind converting all this to Core Data but would like to have the core functionality in place before doing that.

like image 969
blaa Avatar asked Nov 14 '12 10:11

blaa


1 Answers

In isEqual: method, the line "if (externalItemId != [otherCust externalItemId]) return NO;" is wrong. I think it should be if (![externalItemId:isEqual:[otherCust externalItemId]]) return NO;

like image 156
Pitiphong Phongpattranont Avatar answered Sep 30 '22 06:09

Pitiphong Phongpattranont