Danny Wong
Danny Wong

Reputation: 11

Weird behaviour with NSMutableArray with different objects with same index

I am encountering a weird issue that I couldn't find the bug of the testing code that I wrote. I am writing a piece of code to generate AttributedString and put it in NSMutableArray, but when I display the added objects from the NSMutableArray using the For loop, the output is showing the same object with same index 3 times, I am trying to debug it for 2 days now and couldn't find the bug, hopefully I can get some help/advice here.

The code to instantiate the NSMutableArray and adding the AttributedString to the array

-(instancetype)init
{
    self = [super init];

    if (self) {
        for (NSMutableAttributedString *attrString in [SetPlayingCard symbolArray]) {
            for (NSString *attrColor in [SetPlayingCard colorsArray]) {
                if ([attrColor isEqualToString:@"redColor"]) {
                    [attrString setAttributes:@{NSStrokeWidthAttributeName : @3,
                                            NSStrokeColorAttributeName : [UIColor redColor]}
                                    range:NSMakeRange(0, [attrString length])];
                    NSLog(@"%@ %@",attrColor, attrString);
                } else if ([attrColor isEqualToString:@"blueColor"]){
                    [attrString setAttributes:@{NSStrokeWidthAttributeName : @3,
                                            NSStrokeColorAttributeName : [UIColor blueColor]}
                                    range:NSMakeRange(0, [attrString length])];
                    NSLog(@"%@ %@",attrColor, attrString);
                } else if ([attrColor isEqualToString:@"purpleColor"]){
                    [attrString setAttributes:@{NSStrokeWidthAttributeName : @3,
                                            NSStrokeColorAttributeName : [UIColor purpleColor]}
                                    range:NSMakeRange(0, [attrString length])];
                    NSLog(@"%@ %@",attrColor, attrString);
                }
                [self addCard:attrString];
            }
        }
    }
    return self;
}

The Class method that is being called

+ (NSArray *)colorsArray
{
    return @[@"redColor",@"blueColor",@"purpleColor"];
}

+ (NSArray *)symbolArray
{
    NSMutableAttributedString *triangle = [[NSMutableAttributedString alloc] initWithString:@"Triangle"];
    NSMutableAttributedString *square = [[NSMutableAttributedString alloc] initWithString:@"Square"];
    NSMutableAttributedString *round = [[NSMutableAttributedString alloc] initWithString:@"Round"];

    return @[triangle,square,round];
}

The method to add and display the output

- (void)addCard:(NSAttributedString *)card
{
    NSLog(@"String to be added to array: %@", card);
    [self.cards addObject:card];
    NSLog(@"Index is %d for %@", [self.cards indexOfObject:card], card);
}


- (NSAttributedString *)printCard
{
    NSAttributedString *card;

    if ([self.cards count]) {
        for (card in self.cards) {
            NSLog(@"Count: %d, Array index: %d, Card from the deck is: %@, ", [self.cards count], [self.cards indexOfObject:card], card);
        }
    } 
    return card;
}

The output I get from the NSLog in addCard method is as below.

2014-04-01 22:40:56.184 UnitTest[1008:60b] Count: 9, Array index: 0, Card from the deck is: Triangle{
    NSStrokeColor = "UIDeviceRGBColorSpace 0.5 0 0.5 1";
    NSStrokeWidth = 3;
}, 
2014-04-01 22:40:56.185 UnitTest[1008:60b] Count: 9, Array index: 0, Card from the deck is: Triangle{
    NSStrokeColor = "UIDeviceRGBColorSpace 0.5 0 0.5 1";
    NSStrokeWidth = 3;
}, 
2014-04-01 22:40:56.186 UnitTest[1008:60b] Count: 9, Array index: 0, Card from the deck is: Triangle{
    NSStrokeColor = "UIDeviceRGBColorSpace 0.5 0 0.5 1";
    NSStrokeWidth = 3;
}, 
2014-04-01 22:40:56.187 UnitTest[1008:60b] Count: 9, Array index: 3, Card from the deck is: Square{
    NSStrokeColor = "UIDeviceRGBColorSpace 0.5 0 0.5 1";
    NSStrokeWidth = 3;
}, 
2014-04-01 22:40:56.188 UnitTest[1008:60b] Count: 9, Array index: 3, Card from the deck is: Square{
    NSStrokeColor = "UIDeviceRGBColorSpace 0.5 0 0.5 1";
    NSStrokeWidth = 3;
}, 
2014-04-01 22:40:56.189 UnitTest[1008:60b] Count: 9, Array index: 3, Card from the deck is: Square{
    NSStrokeColor = "UIDeviceRGBColorSpace 0.5 0 0.5 1";
    NSStrokeWidth = 3;
}, 
2014-04-01 22:40:56.190 UnitTest[1008:60b] Count: 9, Array index: 6, Card from the deck is: Round{
    NSStrokeColor = "UIDeviceRGBColorSpace 0.5 0 0.5 1";
    NSStrokeWidth = 3;
}, 
2014-04-01 22:40:56.191 UnitTest[1008:60b] Count: 9, Array index: 6, Card from the deck is: Round{
    NSStrokeColor = "UIDeviceRGBColorSpace 0.5 0 0.5 1";
    NSStrokeWidth = 3;
}, 
2014-04-01 22:40:56.192 UnitTest[1008:60b] Count: 9, Array index: 6, Card from the deck is: Round{
    NSStrokeColor = "UIDeviceRGBColorSpace 0.5 0 0.5 1";
    NSStrokeWidth = 3;
},

Upvotes: 0

Views: 157

Answers (3)

David Berry
David Berry

Reputation: 41246

if we examine the code doing the set up, you have (in summary):

foreach symbol:
    foreach color:
        symbol.color = color
        [array addObject:symbol];

What this is actually doing is creating a single NSMutableString for each symbol, and then changing its color and adding to the array 3 times. Since you don't ever create a copy of the string, but instead just change the attributes of a single string, you have the same object in the array 3 times.

Now, when you use indexOfObject: to look that object up, that you added 3 times, it only finds the first instance.

The fix is to create a copy of the string to add instead of adding the original string:

[self addCard:[attrString copy]];

A better solution might be to just return an array of strings:

return @[ @"triangle", @"square", @"round"];

and use a different constructor to create the attributed string:

attrString = [[NSAttributedString alloc] initWithString:shape attributes:color];

Upvotes: 0

Austin
Austin

Reputation: 5655

In your init method, the outer for loop iterates over each of your 3 strings, while the inner for loop iterates over each of your 3 colors, so a total of 9 calls to addCard:. Thus, three copies of each string in the array, but each with a different color.

The reason that the logs give the same index for each of those objects, is that indexOfObject: uses [NSObject isEqual:] to determine equality. Apparently, NSMutableString's implementation of isEqual: compares only the string content, not colors, so each copy of the string is "the same" as far as the array is concerned.

If you use enumerateObjectsUsingBlock:, you'll see that each object does in fact have a different index.

[self.cards enumerareObjectsUsingBlock:^(NSMutableAttributedString *card, NSUInteger idx, BOOL *stop) {
    NSLog(@"Count: %d, Array index: %d, Card from the deck is: %@, ", [self.cards count], idx, card);
}];

Upvotes: 2

Greg
Greg

Reputation: 25459

Your printCard method is wrong change it to:

- (NSAttributedString *)printCard
{
    if ([self.cards count] > 0) {
        for (NSAttributedString *card in self.cards) {
            NSLog(@"Count: %d, Card from the deck is: %@, ", [self.cards count], card);
        }
    } 
    return card;
}

If you want index you can use:

for (int i = 0; i < self.cards.count; i++) {
    NSAttributedString *card = self.cards[i];
    NSLog(@"Count: %d, Array index: %d, Card from the deck is: %@, ", [self.cards count], i, card);
}

You should review how for in loop works.

Upvotes: 2

Related Questions