Slide 1

Slide 1 text

Practical Concurrent Programming Chris Eidhof

Slide 2

Slide 2 text

Merhabā

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Why is concurrency hard?

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

When to use concurrency? Always use the main thread

Slide 10

Slide 10 text

When to use concurrency? — Networking — Expensive stuff

Slide 11

Slide 11 text

Decision workflow 1. Measure 2. Change 3. Measure again

Slide 12

Slide 12 text

How to draw things in the background

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Recipe 1. Take drawRect: code 2. Put it in a background thread 3. Update the main thread

Slide 15

Slide 15 text

The drawRect: code - (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); // expensive // drawing // code }

Slide 16

Slide 16 text

Moving it to a different thread [queue addOperationWithBlock:^{ UIGraphicsBeginImageContextWithOptions(size, NO, 0); // expensive // drawing // code UIImage *i = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); // TODO: update the main thread }];

Slide 17

Slide 17 text

Updating the main thread [queue addOperationWithBlock:^{ UIGraphicsBeginImageContextWithOptions(size, NO, 0); // expensive // drawing // code UIImage *i = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = i; }]; }];

Slide 18

Slide 18 text

Deckset filters

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

[self.queue addOperationWithBlock:^{ CIImage *ciImage = [CIImage imageWithContentsOfURL:sourceURL]; CIFilter *depthOfFieldFilter = [CIFilter filterWithName:@"CIDepthOfField"]; ... CIImage *finalImage = [alphaFilter valueForKey:kCIOutputImageKey]; CGContextRef cgContext = CGBitmapContextCreate(NULL, size.width, size.height, 8, size.width * 4, colorSpace, kCGImageAlphaPremultipliedLast); CIContext *context = [CIContext contextWithCGContext:cgContext options:nil]; CGImageRef outputImage = [context createCGImage:finalImage fromRect:ciImage.extent]; ... CGImageDestinationAddImage(destination, outputImage, nil); CGImageDestinationFinalize(destination); }];

Slide 21

Slide 21 text

... the shared resource was the GPU!

Slide 22

Slide 22 text

Solution NSDictionary *options = @{kCIContextUseSoftwareRenderer: @YES}; CIContext *context = [CIContext contextWithCGContext:cgContext options:options]; ... and ... self.queue.maxConcurrentOperationCount = 1;

Slide 23

Slide 23 text

Solution, part 2 ... we removed the complicated filter

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

How to not load things from the network dispatch_async(backgroundQueue, ^{ NSData *data = [NSData dataWithContentsOfURL:url]; NSArray *graphItems = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; UIImage* image = [self drawGraph:graphItems]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); });

Slide 26

Slide 26 text

Grand Central Dispatch

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

Threading is hard GCD moves it to the system- level

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

— Simpler — Faster — Thread-pool management — Memory-efficient — Async means: no deadlock!

Slide 32

Slide 32 text

How to load things from the network Use NSURLSession

Slide 33

Slide 33 text

Operation Queues

Slide 34

Slide 34 text

dispatch_async(backgroundQueue, ^{ NSArray *graphData = [self generateDataPointsForGraph]; UIImage* image = [self drawGraph:graphData]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); });

Slide 35

Slide 35 text

Step 1: Use NSOperation NSOperationQueue* drawingQueue = [[NSOperationQueue alloc] init]; [drawingQueue addOperationWithBlock:^{ NSArray *graphData = [self generateDataPointsForGraph]; UIImage* image = [self drawGraph:graphData]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; }]; How to cancel this?

Slide 36

Slide 36 text

Step 2: Use NSBlockOperation NSBlockOperation* drawingOperation = [NSBlockOperation blockOperationWithBlock:^{ NSArray *graphData = [self generateDataPointsForGraph]; UIImage* image = [self drawGraph:graphData]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; }]; [drawingQueue addOperation:drawingOperation];

Slide 37

Slide 37 text

Step 3: Pull out the completion handler NSOperation* drawingOperation = [NSBlockOperation blockOperationWithBlock:^{ NSArray *graphData = [self generateDataPointsForGraph]; self.image = [self drawGraph:graphData]; }]; drawingOperation.completionBlock = ^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = self.image; }]; };

Slide 38

Slide 38 text

Step 4: Custom NSOperation subclass NSData *data = [self generateDataPointsForGraph]; NSOperation* drawingOperation = [DrawingOperation drawingOperationWithData:data]; drawingOperation.completionBlock = ^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; };

Slide 39

Slide 39 text

How to efficiently import data into Core Data

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

Efficiently Importing Data — Implement Find-or-Create Efficiently — Reduce Peak Memory Footprint https://developer.apple.com/library/ios/ documentation/Cocoa/Conceptual/CoreData/ Articles/cdImporting.html

Slide 42

Slide 42 text

Importing employees for (WebserviceEmployee *webserviceEmployee) { NSNumber *identifier = webserviceEmployee.identifier; Employee *employee = [self findEmployeeWithIdentifier:identifier]; if (employee == nil) { employee = [self createEmployeWithIdentifier:identifier]; } // Update data }

Slide 43

Slide 43 text

Importing more efficiently NSMutableArray *identifiers = [NSMutableArray array]; for (WebserviceEmployee *webserviceEmployee) { NSNumber *identifier = webserviceEmployee.identifier; [identifiers addObject:identifier]; } NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(employeeID IN %@)", identifiers];

Slide 44

Slide 44 text

Import in batches Use a separate context

Slide 45

Slide 45 text

Normal Core Data Stack

Slide 46

Slide 46 text

NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext SQLite NSPersistentStore NSPersistentStoreCoordinator Double MOC Stack

Slide 47

Slide 47 text

Small imports NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; context.persistentStoreCoordinator = self.ptStoreCoordinator; [self.context performBlock:^{ [self import]; }];

Slide 48

Slide 48 text

[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) { NSManagedObjectContext *moc = self.mainMOC; if (note.object != moc) { [moc performBlock:^(){ [moc mergeChangesFromContextDidSaveNotification:note]; }]; }; }];

Slide 49

Slide 49 text

NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext ! SQLite NSPersistentStore NSPersistentStoreCoordinator ! ! ! Double MOC Stack

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

SQLite ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! ! ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! ! !

Slide 52

Slide 52 text

SQLite ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! ! ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! ! ! NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext ! SQLite NSPersistentStore NSPersistentStoreCoordinator ! ! !

Slide 53

Slide 53 text

Importing Recap Didn't turn out to be a problem: we shipped the sqlite file for the initial import.

Slide 54

Slide 54 text

How to make objects play well when concurrent

Slide 55

Slide 55 text

@interface Account : NSObject @property (nonatomic) double balance; - (void)transfer:(double)euros to:(Account*)other; @end

Slide 56

Slide 56 text

- (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?

Slide 57

Slide 57 text

The same code, how the compiler sees it - (void)transfer:(double)euros to:(Account*)other { double currentBalance = self.balance; self.balance = currentBalance - euros; double otherBalance = other.balance; other.balance = otherBalance + euros; }

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

Solution: move concurrency to a different level

Slide 64

Slide 64 text

Do it the GCD way 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.

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult. — Tony Hoare

Slide 67

Slide 67 text

Thanks — @chriseidhof — http://www.objc.io — http://www.uikonf.com — http://www.decksetapp.com

Slide 68

Slide 68 text

Resources — Concurrency Programming Guide — Threading Programming Guide — NSOperationQueue class reference — http://www.objc.io/issue-2/ — http://www.opensource.apple.com/source/ objc4/objc4-551.1/runtime/objc-sync.mm — http://googlemac.blogspot.de/2006/10/ synchronized-swimming.html — WWDC12 #211: Concurrent User Interfaces on iOS

Slide 69

Slide 69 text

Icons are from the Noun Project: — Coffee Maker by Maureen Placente — Coffee by Julia Soderberg — Railroad Crossing by Edward Boatman — Database by Stefan Parnarov — Drawing by Daniel Shannon — Hammer by Alex AS — Lock by P.J. Onori — Photoshop by Joe Harrison — Register by Wilson Joseph