Lost your password?

"I love this game! Great graphics and gameplay!" ***** 
- Bobo1976 (Wordology - Canadian App Store)

Enumeration of NSMutableArray's Follow Up

By Dave Wood on
Dave Wood
Owner/President of Cerebral Gardens
User is currently offline
Dec 17'11 Category iDevBlogADay 3 Comments

Today I'm going to follow up on some interesting comments to my previous tip regarding enumerating NSMutableArray's.

I had suggested to make a copy of the mutable array before enumerating, in order to prevent another thread from modifying the array you're working on and causing a crash.

It was mentioned in the comments that making that copy is itself an enumeration and so we were just moving the crash point. So, I did some digging.

// array is an NSMutableArray
- (IBAction)button1TouchUp:(id)sender
{
    DebugLog(@"Populating Array");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        for (int i = 0; i < 10000000; ++i)
        {
            [array addObject:[NSNumber numberWithInt:i]];
        }
        DebugLog(@"Done Populating Array: %d", [array count]);
    });
}

- (IBAction)button2TouchUp:(id)sender
{
    DebugLog(@"Enumerating Array");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        DebugLog(@"Copying Array");
        NSArray *copyOfArray = [array copy];
        DebugLog(@"Done Copying Array");

        long long x = 0;
        for (NSNumber *number in copyOfArray)
        {
            x += [number intValue];
        }
        DebugLog(@"Done Enumerating Array: %d", [copyOfArray count]);
    });
}

Note: if you test this, do so in the simulator, a device will generate a memory warning and shutdown the test app.

The idea here is that by touching button1, we create a large array in a thread, and touching button 2, we enumerate that array. The array is large enough that it gives you time to ensure you touch button 2 while button 1's thread is still populating the array. If you didn't create the copy of the array before enumerating it button 2, the app will crash. Using the copy of pattern I mentioned in the last post, prevents that crash as expected.

Taking this a little further, the test code shows that when you take a copy of the mutable array, you get a copy of it at in its current state. If something is altering the array when you make the copy, you could get a copy of the array in an unusable or unexpected state. If you were adding or removing elements for example, you might have half of the elements in your copy while expecting all of them, or none of them.

If you need to ensure changes to an array are completed in full before another thread accesses it, you can use the @synchronized directive. The modified code looks like:

- (IBAction)button1TouchUp:(id)sender
{
    DebugLog(@"Populating Array");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        @synchronized(array)
        {
            for (int i = 0; i < 10000000; ++i)
            {
                [array addObject:[NSNumber numberWithInt:i]];
            }
            DebugLog(@"Done Populating Array: %d", [array count]);
        }
    });
}

- (IBAction)button2TouchUp:(id)sender
{
    DebugLog(@"Enumerating Array");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        @synchronized(array)
        {
            DebugLog(@"Copying Array");
            NSArray *copyOfArray = [array copy];
            DebugLog(@"Done Copying Array");

            long long x = 0;
            for (NSNumber *number in copyOfArray)
            {
                x += [number intValue];
            }
            DebugLog(@"Done Enumerating Array: %d", [copyOfArray count]);
        }
    });
}

The @synchronized directive creates a lock on the object for you.

A few points to be aware of:

  1. you need to use @synchronized around all places where you need to lock the object.
  2. the code will block until the object can be accessed, so if thread A is using the object thread B will pause until thread A is done with it, so be sure not to create a block on the main thread.
  3. if you define a property on a class as atomic, by omitting the non-atomic keyword, the synthesized accessors will include synchronization code and simply the code for you, but you still need to be aware of the blocking issues.

As always, I love reading your comments. Especially when you point out my mistakes, since that's the fastest way to learn more.

My next post will be New Years Eve, and if the stars are aligned, I'll have a special treat for you!


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

Tags: Tips & Tricks, Objective-C, XCode, iOS, iDevBlogADay

Comments

Guest
Lee Armatrong Sunday, 18 December 2011 10:08 · Edit Reply

GCD

As you are using GCD & blocks anyway why not create your own queue and access it synchronously. The benefit is that any adds can be done asynchronously!

I also think keeping it all GCD is tidier too!

Guest
Martin Wickman Sunday, 18 December 2011 10:44 · Edit Reply

@synchronized not needed

One great thing with GCD is that it makes threading easier. It can easily be used to replace complex locks/synchronized usages: just ensure you run all code which is accessing the critical resource on the same serial dispatch queue.

In short, I would simply use a serial queue instead of recommending to use @synchronized.

Guest
Roger Sunday, 18 December 2011 13:09 · Edit Reply

@synchronized

Seems to me that using @synchronized would eliminate the need to copy the array. Copying an array is very wasteful, not only in memory but also performance. If you still want to copy the array, your 2nd @synchronized doesn't need to wrap the enumeration, just the copy.

Leave your comment

Guest
Guest Saturday, 19 May 2012 14:35

Follow us on Twitter

@ItsTheFitz Hi, I think you just left a review for Party Doodles on the App Store, if so, please get in touch with me http://t.co/Awm4aJcB


A @PartyDoodles review in French: http://t.co/jXaQJrDF Google translation: http://t.co/7btzy4DO Pretty funny if the translation is accurate


@_MattWelch_ Glad we finally released the game for you (been in the works since last August!). Please let me know how much fun you have!


Our first review! Thanks @iLounge - Party Doodles combines the iPad and Apple TV into a party game | iLounge News: http://t.co/v4axb5g7