5

First of all let me quote a chapter from Apple Threading Programming Guide:

Be Aware of Threats to Code Correctness

When using locks and memory barriers, you should always give careful thought to their placement in your code. Even locks that seem well placed can actually lull you into a false sense of security. The following series of examples attempt to illustrate this problem by pointing out the flaws in seemingly innocuous code. The basic premise is that you have a mutable array containing a set of immutable objects. Suppose you want to invoke a method of the first object in the array. You might do so using the following code:

NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;

[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[arrayLock unlock];

[anObject doSomething];

Because the array is mutable, the lock around the array prevents other threads from modifying the array until you get the desired object. And because the object you retrieve is itself immutable, a lock is not needed around the call to the doSomething method.

There is a problem with the preceding example, though. What happens if you release the lock and another thread comes in and removes all objects from the array before you have a chance to execute the doSomething method? In an application without garbage collection, the object your code is holding could be released, leaving anObject pointing to an invalid memory address. To fix the problem, you might decide to simply rearrange your existing code and release the lock after your call to doSomething, as shown here:

NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;

[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[anObject doSomething];
[arrayLock unlock];

By moving the doSomething call inside the lock, your code guarantees that the object is still valid when the method is called. Unfortunately, if the doSomething method takes a long time to execute, this could cause your code to hold the lock for a long time, which could create a performance bottleneck.

The problem with the code is not that the critical region was poorly defined, but that the actual problem was not understood. The real problem is a memory management issue that is triggered only by the presence of other threads. Because it can be released by another thread, a better solution would be to retain anObject before releasing the lock. This solution addresses the real problem of the object being released and does so without introducing a potential performance penalty.

NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;

[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[anObject retain];
[arrayLock unlock];

[anObject doSomething];
[anObject release];

And the question is: Is there any way to solve the problem while using ARC?

kas-kad
  • 3,736
  • 1
  • 26
  • 45
  • 1
    BJ Homer provides very good information here. Note, however, that for most modern code you should avoid `NSLock` and manual thread management. GCD provides much better tools, and avoids many of these problems as well through better queue-based design. See the Concurrency Programming Guide for more on how to switch to queue-based design rather than thread-based design. https://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html – Rob Napier May 30 '13 at 13:21

1 Answers1

5

ARC solves this problem for you automatically; by default every pointer is a strong pointer, which means that the object is guaranteed to be retained until you are done using that pointer.

This means that whenever you get an object out of an array, ARC always retains that object. This guarantees its lifetime, even if the object is later removed from the array.

BJ Homer
  • 48,806
  • 11
  • 116
  • 129
  • I want to add that this kind of problems, which are solved with ARC, can be build without threads: The problem with threads is, that the order of statements is not defined. To "simulate" the problem without threads you just have to arrange the statements in a bad way (the bad case with threads). In the described case you can simply add a [anArray removeAllObjects] after retrieving the object to crash in manual reference counting. (Or use a nonatomic getter on an object, which is released.) No threads needed. I bet that more than 30 % of developers will not see the problem on the first view. – Amin Negm-Awad May 30 '13 at 20:49