Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Pragma Mark: Simple Concurrent Programming

Pragma Mark: Simple Concurrent Programming

Chris Eidhof | @chriseidhof

October 26, 2013
Tweet

Transcript

  1. double delayInSeconds = 0.1; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds *

    NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // make sure this gets triggered after a layout pass. }); Rethink your code.
  2. • Your scrolling is too slow • When doing network

    requests, your app gets unresponsive • When importing data, your app gets unresponsive
  3. • GPU • CPU • Areas in memory (objects, numbers,

    ...) • Files It's all about shared resources
  4. Before you even consider redesigning your code to support concurrency,

    you should ask yourself whether doing so is necessary. — Concurrency Programming Guide
  5. 1. Determine what is slow: analyze and measure 2. If

    neccessary: factor out units of work 3. Isolate the units in a separate queue 4. Measure again Recipe Improving the responsiveness of your app
  6. We should free up the main thread so that drawing

    and user input gets handled immediately How to solve this?
  7. 1. Take drawRect: code and isolate it into an operation

    2. Replace original view by UIImageView 3. Update UIImageView on main thread Async drawing
  8. [queue addOperationWithBlock:^{ UIGraphicsBeginImageContextWithOptions(size, NO, 0); // expensive // drawing //

    code UIImage *i = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = i; }]; }]; After:
  9. dispatch_async(backgroundQueue, ^{ NSData* imageData = [NSData dataWithContentsOfURL:url]; UIImage* image =

    [[UIImage alloc] initWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView = image; }); }); Warning: please don't use this code Problem Loading an image over the network
  10. dispatch_async(backgroundQueue, ^{ NSData* imageData = [NSData dataWithContentsOfURL:url]; UIImage* image =

    [[UIImage alloc] initWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView = image; }); }); How to cancel this? Problem Loading an image over the network
  11. dispatch_async(backgroundQueue, ^{ NSData* imageData = [NSData dataWithContentsOfURL:url]; UIImage* image =

    [[UIImage alloc] initWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView = image; }); }); What happens if the request times out? Problem Loading an image over the network
  12. GCD Thread Pool Main Thread High Priority Queue Serial Queue

    Parallel Queue Serial Queue Main Queue Serial Queue Concurrent Queue Serial Queue Default Priority Queue Low Priority Queue Background Priority Queue Custom Queues GCD Queues Threads Aside GCD
  13. GCD Thread Pool Main Thread High Priority Queue Serial Queue

    Parallel Queue Serial Queue Main Queue Serial Queue Concurrent Queue Serial Queue Default Priority Queue Low Priority Queue Background Priority Queue Custom Queues GCD Queues Threads GCD adds more threads. This is expensive.
  14. NSOperationQueue* downloadQueue; [downloadQueue addOperationWithBlock:^{ NSData* imageData = [NSData dataWithContentsOfURL:url]; UIImage*

    image = [UIImage imageWithData:imageData]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; }]; How to cancel this? Step 1: Use NSOperation
  15. NSBlockOperation* downloadOperation = [NSBlockOperation blockOperationWithBlock:^{ NSData* imageData = [NSData dataWithContentsOfURL:url];

    UIImage* image = [UIImage imageWithData:imageData]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; }]; [downloadQueue addOperation:downloadOperation]; // Sometime later [downloadOperation cancel]; Step 2: Use NSBlockOperation
  16. NSOperation* downloadOperation = [NSBlockOperation blockOperationWithBlock:^{ NSData* imageData = [NSData dataWithContentsOfURL:url];

    self.downloadedImage = [UIImage imageWithData:imageData]; }]; downloadOperation.completionBlock = ^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = self.downloadedImage; }]; }; Step 3: Pull out the completion handler
  17. NSURLSession* session = [NSURLSession sharedSession]; [session downloadTaskWithURL:url completionHandler: ^(NSURL *location,

    NSURLResponse *response, NSError *err) { // Process downloaded data. }]; (or use NSURLSession)
  18. [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) { NSManagedObjectContext *moc

    = self.mainMOC; if (note.object != moc) { [moc performBlock:^(){ [moc mergeChangesFromContextDidSaveNotification:note]; }]; }; }];
  19. You might want to consider using a different concurrency style,

    and this time you have two persistent store coordinators, two almost completely separate Core Data stacks. Source: http://asciiwwdc.com/2013/sessions/211
  20. SQLite ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! !

    ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! ! !
  21. - (void)transfer:(double)euros to:(Account*)other { self.balance = self.balance - euros; other.balance

    = other.balance + euros; } What happens if two methods call this method at the same time? From different threads?
  22. - (void)transfer:(double)euros to:(Account*)other { double currentBalance = self.balance; self.balance =

    currentBalance - euros; double otherBalance = other.balance; other.balance = otherBalance + euros; } The same code, how the compiler sees it
  23. a b [a.transfer:20 to:b] [a.transfer:30 to:b] 100 0 currentBalance =

    100 currentBalance = 100 100 0 a.balance = 100 - 20 80 0 b.balance = b.balance + 20 80 20 a.balance = 100 - 30 70 20
  24. a b [a.transfer:20 to:b] [a.transfer:30 to:b] 100 0 currentBalance =

    100 currentBalance = 100 100 0 a.balance = 100 - 20 80 0 b.balance = b.balance + 20 80 20 a.balance = 100 - 30 70 20
  25. - (void)transfer:(double)euros to:(Account*)other { @synchronized(self) { @synchronized(other) { self.balance =

    self.balance - euros; other.balance = other.balance + euros; } } } Problem: deadlock.
  26. - (void)transfer:(double)euros to:(Account*)other { @synchronized(self.class) { self.balance = self.balance -

    euros; other.balance = other.balance + euros; } } Working, but possibly slow
  27. - (void)transfer:(double)euros to:(Account*)other { objc_sync_enter(self.class) objc_exception_try_enter setjmp objc_exception_extract self.balance =

    self.balance - euros; other.balance = other.balance + euros; objc_exception_try_exit objc_sync_exit(self.class) ... objc_exception_throw ... } }
  28. Account* account = [Account new]; Account* other = [Account new];

    dispatch_queue_t accountOperations = dispatch_queue_create("accounting", DISPATCH_QUEUE_SERIAL); dispatch_async(accountOperations, ^{ [account transfer:200 to:other]; }); dispatch_async will never block. Do it the GCD way
  29. + (id)sharedInstance { static id sharedInstance = nil; @synchronized(self) {

    if (sharedInstance == nil) { sharedInstance = [[self alloc] init]; } } return sharedInstance; } Singletons
  30. + (id)sharedInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance =

    [[self alloc] init]; }); } Faster than synchronized, but still blocking.
  31. • Lots of subtle problems that don't show up during

    testing. • Keep it very simple • Use the main thread when possible Conclusion
  32. The ideal software structure is one having components that are

    small and loosely coupled; this ideal structure is called ravioli code. In ravioli code, each of the components, or objects, is a package containing some meat or other nourishment for the system; any component can be modified or replaced without significantly affecting other components. — http://www.gnu.org/fun/jokes/pasta.code.html Ravioli Code
  33. • Concurrency Programming Guide • http://googlemac.blogspot.de/2006/10/ synchronized-swimming.html • NSOperationQueue class

    reference • http://www.objc.io/issue-2/ • http://www.gnu.org/fun/jokes/pasta.code.html • https://developer.apple.com/library/ios/ documentation/Cocoa/Conceptual/Multithreading/ ThreadSafetySummary/ • http://www.opensource.apple.com/source/objc4/ objc4-551.1/runtime/objc-sync.mm