Wednesday, 7 May 2014

Making a Nonatomic Method, Atomic [Twice As Nice]

That takes care of two bugs—making -setImage:existingKey: and the imageKey property atomic—but you still have one more bug, which you’ve had since you added document support to MyStuff.
You’ll only encounter it if you selectan image at the same instant the document object is auto-saving itself. And this underscores just how subtle, and hard to find, concurrency bugs can be. You could
have tested MyStuff for days, shipped it to customers, andnever run into it. Heres how you’d find it.

Running MyStuff under the control of Xcode, repeatedly add items and select images for those items from your photo library. The more items you add quickly, the longer it will take UIDocument to update the files during its auto-save, and the more likely you are to encounter the bug. When it happens,
it appears in the console pane of Xcode, like the incident shown in Figure 24-5.


Figure 24-5. A concurrenc bug

The message is an exception, a kind of software “abort” when something goes wrong. When you run your app under Xcodes control, exceptions are caught and logged to the console.
Looking in the stack pane, you can see that the exception was caught in thread 7, which is labeled UIDocument File Access. This is a pretty good clue that the problem occurred while the document was trying to read or write from thefilesystem.

The description of the exception (“Collection was mutated while being enumerated”) tells you what happened, but not why. The code that threw the exception is no longer running, so you cant consult the stack view. Instead, you have to investigate the stack trace recorded by the exception. This, unfortunately, is just a string of numbers.


I’ll save you from looking this up on the Internet (lldb.llvm.org). The exception message is followed by a list of
the return code addresses that were on the stack when the exception occurred. If you know what code thoseaddresses refer to, you’ll have a pretty good idea of whatwas going on when it happened. 
That (lldb) prompt in the console window is the debuggers command prompt. You can send commands directly to the debuggejust by typing them in. In this situationthe imag looku --address command is extremely useful. Ittries to identify the method/function name of any code address.
If you plug in the addresses from the stack trace, you quickly get a picture of what the code was doing when the exception was thrown, as shown in Figure 24-5.

Working backwards up the stack, you quickly uncover the method responsible was
writeContents: toURL:forSaveOperation:originalContentsURL:error:. It writes the docWrapper (NSFileWrapper) object, which blows up because it was modified in the middle of being written. Now you know where its choking, youhave to figure out why. Its obvious that some other code is
modifying the docWrapper while its being written. Looking around that stack pane, you cant see any other code that is modifying docWrapper. Its likely that whatever modified it did so and has exited already, sotheres notrace of it in the debugger. You’ll have to look through your code and find all of the places that
modify docWrapper and determine, empirically, if one of them could be the problem.

It doesnt take too long to zero in on a suspect; the -setImage:existingKey: method changes (mutates) the docWrapper by adding and removing regular file wrappersAt the same time, the UIDocument is periodically auto-savingthe document, and it writes the docWrapper to persistent storage on a background thread. The problem is that a collection object (like docWrapper) cannot be changed while its being enumerated. You might remember thisobscure rule from the “Collections” section in Chapter 20.

With the detective work out of the way, your approach is obvious. You need to keep docWrapper from being modified while its being written. That sounds like a mutex. But where do you put it? The method thats running onthe background thread belongs to UIDocument.

The solution is to wrap the UIDocument method in a mutex. Select the MSThingsDocument.m
implementation file, and add this method:

-  (BOOL)writeContents:(id)contents toURL:(NSURL  *)url
forSaveOperation:(UIDocumentSaveOperation)saveOperation originalContentsURL:(NSURL *)originalContentsURL
error:(NSError *    autoreleasing  *)outError
{
@synchronized  (docWrapper)
{
return [super writeContents:contents toURL:url
forSaveOperation:saveOperation originalContentsURL:originalContentsURL
error:outError];
}
}

This method overrides UIDocuments -writeContents:toURL:forSaveOperation:originalContentURL:error: method and prevents it from running if some other thread is currently modifying docWrapper (specifically-setImage:existingKey:). Once itis running, it will block any other thread from modifying docWrapper until the -writeContents:... method is finished.

As you can see, writing thread-safe code isnt always obvious. It takes a fair degree of planning, testing, and analysis. Which is why the first rule is still the best; avoid it when you can.

I’ll now wrap up with a brief tour of some other concurrency tools at your disposal.

Concurrency Roundup

Heres a loose collection of tips, concepts, and tools that will help you get started using multitasking, and hopefully keep you out of trouble.

The Thread-Safe Landscape

Making even a single property of an object (like MyWhatsits image property) thread-safe is often a non-trivial task. Consequentlymost classes and properties are not thread safe. Read that again. The classes, or specificmethods, that are thread-safe are usually documented. If you want a list, look up the Thread Safety Summary in Xcodes Documentation and API Reference window. It lists all of the Cocoa classes that are thread-safe; its not avery long list.

Of particular note, the UI classes are not thread-safe. Not only are they not thread-safe, the only thread that should use them is the main thread. In other words, you must not do anything with the user interface objects—not even telling aUIView object it needs to be redrawn—from any thread except your main thread. One of the very few exceptions is off-screen drawing. You can create an off-screen drawing context (UIGraphicsBeginImageContext) and draw in abackground thread. The resulting UIImage object is not, ironically, thread-safe, and must be passed back to the main thread before it can be used.

To use any object, message, or property that isnt thread-safe in a thread-safe manner requires that you provide your own thread synchronization and atomicity.

Sending Messages To Main

Earlier I described how UIWebView would “pass an object to the main thread.” There are a handful of methods that will send a message, or perform a code block, on a specific thread. The thread in question must be running anevent loop. This excludes any threads used by your operation objects, but it does include the main thread, and thats where this trick is the most useful.

So many thread safety issues disappear when code is run on the main thread. You can avoid a lot of problems if you can schedule a method or block of code to execute there. This works, not surprisingly, through the main threads run loop. Messages are added to the event queue and are executed in their turn. The two most useful techniques are:

n     Send the -performSelectorOnMainThread:withObject:waitUntilDone: message to any object. It will arrange for that object to receive the Objective-C message (with an optional object parameter). The message is dispatched to the object by the main threads run loop. If waitUntilDone is YES, the sending thread will sleep until the main thread has executed the message.



n     The +[NSOperationQueue  mainQueue] object is a special operation queue that runs its operations on the main thread. If you need a code block, or anything more complicated than a single message, to run on the main thread, turn that into an operation object and add it to this queue.

You already know that you cant send any UI objects messages from another thread. So how does your waveform analysis thread tell its custom UIView object to redraw itself once its finished with the calculations? Heres how:

[waveView performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil
waitUntilDone:NO];

Results of background tasks can, similarly, be delivered to your main thread through its run loop, avoiding numerous thread safety issues:

[[NSOperationQueue mainQueue]  addOperationWithBlock:^{ [xrayViewController addImage:image sequence:n  forPatient:patientID];
}];


Lock Objects

You’ve created mutex objects using the @synchronized directive, but its sometimes morconvenient, efficient, and flexible to create the mutex objects yourself. The workhorse mutex class is NSLock. Heres an example:

NSLock *lock   = [[NSLock  alloc] init];

...

[lock lock];
// do  thread-safe stuff  here [lock unlock];

This is equivalent to the @synchronized blocks you used earlier, but just a tad faster because you’re not asking Objective-C to dynamically create and keep track of the mutex objects for you.

Lock objects are also more flexible. The -tryLock and -lockBeforeDate: messages will attempt to acquire the lock and return NO if they were unsuccessful. Instead of being forced to go to sleep, your code can use this information to do something else if the lock is currently locked by another (possibly long-running)task.

There are some subtle hazards that you need to avoid. If the code protected by your lock can throw an exception, or your method needs to return a value, steps have to be taken to ensure the lock is unlocked before returning.Heres an atomic method using the @synchronized directive:

-  (BOOL)doItSafely
{
@synchronized  (self)
{
return  [obj doSomething];
}
}

Heres a functionally identical method, written using an NSLock object:

-  (BOOL)doItSafely
{
BOOL  result;
@try {
[lock lock];
result = [obj  doSomething];
}
@finally  {
[lock unlock];
}
return result;
}

The @finally block intercepts any software exceptions that -doSomething  might throw and ensures that the NSLock is unlocked before exiting the method. If lock  wasnt unlocked, the next time
-doItSafely is received the program will seize up, which brings you to the next hazard of using mutual exclusion semaphores.


Deadlocks

A deadlock occurs when code tries to lock a mutex that will never be unlocked. The code stops executing—forever. If this happens to your main thread, your app just died. This can occur in the same thread or betweenthreads, where its known by the more colorful term “deadly embrace.”

When most programmers first start writing thread-safe code, the inclination is to “lock everything.” This leads to code like this real-time order processing system:

NSLock *lock   = [[NSLock  alloc] init];

...

-  (NSUInteger)availableProduct:(int)productID
{
NSUInteger  count   = 0; [lock lock];
for ( Item  *item  in inventory )

if (item.productID==productID) count++;
[lock unlock]; return count;
}

-  (void)orderProduct:(int)productID  count:(NSUInteger)count
{
[lock lock];
// Customer  can't  order more than   what's in stock if (count>[self availableProduct:productID])
count   = [self availableProduct:productID]; if (count!=0)
{
// Create an  item  and  add  it to the   order OrderItem *item  = [[OrderItem alloc]  init]; item.productID = productID;
item.count = count; item.taxExempt = NO; [order addObject:item];
}
[lock unlock];
}

The first receipt of -orderProduct:count: will cause the program to seize. It will never execute another line of code. Can you see why?

The -orderProduct:count: method acquires lock  and sends itself an -availableProduct: message. That method tries to acquire lock. It cant, and the thread goes to sleep waiting for lock  to be unlocked, which will never happen.

Recursive Locks

There are two ways to solve this kind of problem. The first is to use a recursive lock. Replace the
NSLock with an NSRecursiveLock, like this:

NSRecursiveLock *lock;

A recursive lock allows a single thread to acquire the lock multiple times. Other threads treat the
lock like any other mutex. Now -orderProduct:count: and -availableProduct: can both acquire the mutex (in the same thread), do their work, and return. You must still balance every -lock message with an -unlock message, or themutex will remain locked (to other threads) for all eternity.

Multiple Locks

Another solution is to use two locks, like this:

NSLock *inventoryLock = [[NSLock  alloc] init]; NSLock *orderLock = [[NSLock  alloc] init];

...

-  (NSUInteger)availableProduct:(int)productID
{
NSUInteger  count   = 0; [inventoryLock lock];
...
[inventoryLock unlock]; return count;
}

-  (void)orderProduct:(int)productID  count:(NSUInteger)count
{
[orderLock lock];
if (count>[self availableProduct:productID]) count   = [self availableProduct:productID];
...
[orderLock unlock];
}

Once again, -orderProduct:count: runs smoothly. This technique can, however, easily lead to a deadly embrace between two threads:

1.       Thread A locks inventoryLock

2.       Thread B locks orderLock

3.       Thread A tries to lock orderLock cant, and goes to sleep.

4.       Thread B tries to lock inventoryLock, cant, and goes to sleep.

Now both threads are suspended and will never wake up. If you use multiple mutex objects to protect different data objects, try to always lock and unlock them in the same order.


Spin Locks

Mutex objects are great, but they’re also slow (relatively speaking). If a mutex object gets locked and unlocked a few times, thats no
big deal. But it you use a mutex object in code that gets executed hundreds of thousands oftimes, it can hurtyour apps performance. When you run your Time Profile in Instruments, you’ll see that a significant amount ofyour apps CPU time is spent locking and unlocking the mutex.

A spin lock is a high-performance mutex that doesnt put the losing thread to sleep.
If the thread cant obtain the mutex (because another thread already locked it), the thread “spins” waiting for the mutex to be unlocked again.Spin locks are useful for protecting small sections of code that:

n     Are called thousands and thousands of times

n     Execute very quickly

n     Have a very low probability of competing with a second thread

The process of “spinning” is a huge waste of CPU resources, but spin locks make up for that by being extremely fast when locking and unlocking a mutex thats not locked by any other
thread. These are called optimisticlocks.

Spin locks are opaque C variables and you use C functions to lock and unlock them. Other than that, you use them exactly the way you’d use an NSLock object:

static OSSpinLock spinner;

...

OSSpinLockLock(&spinner);
// do  something   really quick   here OSSpinLockUnlock(&spinner);

As an example, iOSs memory management functions (the ones that allocate and return memory blocks to the heap) use spin locks. Think about it; all memory functions have to be thread-safe, since any thread can create anddestroy objects. They also have to be extremely fast, and the probability
of two threads allocating an object at exactly the same moment is really small. For functions like this, spin locks are perfect.


Further Reading

When you’re ready to wade into the deep end of concurrency and thread safety, here are two good places to start, both of which can be found in Xcodes Documentation and API Reference window:

n     Start with the Concurrency Programming Guide. This is your roadmap to
all things concurrent. It describes asynchronous app design, Grand Central Dispatch, and how to use operation queues.

n     The Thread Programming Guide contains a thorough discussion of threads and thread synchronization. Here you’ll find descriptions of all of the low-level tools—exclusion semaphores, locks, spin locks,signals, conditions, atomic functions, and memory barriers—to synchronize your threads and data.


Summary

Multithreading is definitely an advanced app development technique. Master it, and you can create apps that do an amazing amount of work, while staying responsive and smooth. I wont declare you a concurrency guru justyet, but you’ve got all of the basics and you know where to find out more.

You’ve learned a tremendous amount about iOS app development since Chapter 1. Its been an exciting journey, and one thats only just begun. With the foundation you have now, you can explore many of the technologies Ididnt cover in this book, and go deeper into the ones I did.

I hope you’ve enjoyed reading this book as much as I enjoyed writing it. Use your imagination, applwhat you’ve learned, and promise to write (james@learniosappdev.com) otweet (@LearniOSAppDev) me when you’ve writtensomething great. Good luck!