$30 off During Our Annual Pro Sale. View Details »

Multithreading on iOS

Multithreading on iOS

This talk gives an overview why and when it is useful to consider to do stuff concurrently in an iOS application. It then goes into detail about some of the concepts and useful patterns that exist in Grand Central Dispatch.

Michael Ochs

August 27, 2015
Tweet

More Decks by Michael Ochs

Other Decks in Programming

Transcript

  1. Multithreading on iOS

    View Slide

  2. Why Multithreading?

    View Slide

  3. Why Multithreading?
    • Keep the main thread free for UI rendering
    • Use all available CPU cores
    • Save energy and reduce battery consumption
    • Prioritise tasks to enable the system to optimise the workload

    View Slide

  4. How to do concurrent work?

    View Slide

  5. How to do concurrent work?
    • NSThread
    • NSRunLoop
    • NSOperation / NSOperationQueue
    • Grand Central Dispatch
    Available concepts

    View Slide

  6. How to do concurrent work?
    • NSThread
    • NSRunLoop
    • NSOperation / NSOperationQueue
    • Grand Central Dispatch
    Available concepts

    View Slide

  7. How to do concurrent work?
    • Queues are an abstraction on top of threads
    • Queues can have parent queues
    • Multiple queues can run on a single thread
    • A single queue can run on multiple threads
    • Multiple queues use a thread pool
    Threads vs. Queues

    View Slide

  8. How to do concurrent work?
    • Configurable number of parallel executions
    • Cancellable
    • Dependencies, priorities, QoS classes
    • Based on GCD
    • Main queue and custom queues
    NSOperation / NSOperationQueue

    View Slide

  9. How to do concurrent work?
    • Serial and concurrent queues
    • QoS classes
    • Main queue, default queues and custom queues
    Grand Central Dispatch

    View Slide

  10. How to do concurrent work?
    • NSOperationQueue can control maximum parallel execution
    • GCD has default queues
    • GCD has groups and semaphores
    NSOperationQueue vs. Grand Central Dispatch

    View Slide

  11. How to do concurrent work?
    • NSOperationQueue is more convenient
    • GCD has all the fancy shit
    • GCD is much more than just getting stuff off the main queue
    NSOperationQueue vs. Grand Central Dispatch

    View Slide

  12. Living in threaded environments

    View Slide

  13. Living in threaded environments
    • Make your models immutable
    • Have a single source of truth
    • Make your data source thread safe
    • Never rely on messages arriving on a certain queue unless explicitly
    documented
    • Explicitly document if you rely on being run on a certain queue
    Models

    View Slide

  14. Living in threaded environments
    • Design your APIs for asynchronous use
    • Ensure completion handler are called on a well defined queue
    • Never use mutable data types on public APIs
    • Ensure mutating methods are thread safe
    API Design

    View Slide

  15. Making use of queues

    View Slide

  16. Making use of queues
    __weak typeof(self) weakSelf = self;
    qos_class qosClass = QOS_CLASS_USER_INITIATED;
    dispatch_queue_t queue = dispatch_get_global_queue(qosClass, 0);
    dispatch_async(queue, ^{
    NSArray *locations = // do some heavy work here
    dispatch_async(dispatch_get_main_queue(), ^{
    typeof(weakSelf) self = weakSelf;
    [self.mapView addAnnotations:locations];
    [self.mapView showAnnotations:locations animated:YES];
    });
    });
    User initiated actions

    View Slide

  17. Making use of queues
    __weak typeof(self) weakSelf = self;
    qos_class qosClass = QOS_CLASS_USER_INITIATED;
    dispatch_queue_t queue = dispatch_get_global_queue(qosClass, 0);
    dispatch_async(queue, ^{
    NSArray *locations = // do some heavy work here
    dispatch_async(dispatch_get_main_queue(), ^{
    typeof(weakSelf) self = weakSelf;
    [self.mapView addAnnotations:locations];
    [self.mapView showAnnotations:locations animated:YES];
    });
    });
    User initiated actions

    View Slide

  18. Making use of queues
    __weak typeof(self) weakSelf = self;
    qos_class qosClass = QOS_CLASS_USER_INITIATED;
    dispatch_queue_t queue = dispatch_get_global_queue(qosClass, 0);
    dispatch_async(queue, ^{
    NSArray *locations = // do some heavy work here
    dispatch_async(dispatch_get_main_queue(), ^{
    typeof(weakSelf) self = weakSelf;
    [self.mapView addAnnotations:locations];
    [self.mapView showAnnotations:locations animated:YES];
    });
    });
    User initiated actions

    View Slide

  19. Making use of queues
    __weak typeof(self) weakSelf = self;
    qos_class qosClass = QOS_CLASS_USER_INITIATED;
    dispatch_queue_t queue = dispatch_get_global_queue(qosClass, 0);
    dispatch_async(queue, ^{
    NSArray *locations = // do some heavy work here
    dispatch_async(dispatch_get_main_queue(), ^{
    typeof(weakSelf) self = weakSelf;
    [self.mapView addAnnotations:locations];
    [self.mapView showAnnotations:locations animated:YES];
    });
    });
    User initiated actions

    View Slide

  20. Making use of queues
    __weak typeof(self) weakSelf = self;
    qos_class qosClass = QOS_CLASS_USER_INITIATED;
    dispatch_queue_t queue = dispatch_get_global_queue(qosClass, 0);
    dispatch_async(queue, ^{
    NSArray *locations = // do some heavy work here
    dispatch_async(dispatch_get_main_queue(), ^{
    typeof(weakSelf) self = weakSelf;
    [self.mapView addAnnotations:locations];
    [self.mapView showAnnotations:locations animated:YES];
    });
    });
    User initiated actions

    View Slide

  21. Making use of queues
    __weak typeof(self) weakSelf = self;
    qos_class qosClass = QOS_CLASS_USER_INITIATED;
    dispatch_queue_t queue = dispatch_get_global_queue(qosClass, 0);
    dispatch_async(queue, ^{
    NSArray *locations = // do some heavy work here
    dispatch_async(dispatch_get_main_queue(), ^{
    typeof(weakSelf) self = weakSelf;
    [self.mapView addAnnotations:locations];
    [self.mapView showAnnotations:locations animated:YES];
    });
    });
    User initiated actions

    View Slide

  22. Making use of queues
    __weak typeof(self) weakSelf = self;
    qos_class qosClass = QOS_CLASS_USER_INITIATED;
    dispatch_queue_t queue = dispatch_get_global_queue(qosClass, 0);
    dispatch_async(queue, ^{
    NSArray *locations = // do some heavy work here
    dispatch_async(dispatch_get_main_queue(), ^{
    typeof(weakSelf) self = weakSelf;
    [self.mapView addAnnotations:locations];
    [self.mapView showAnnotations:locations animated:YES];
    });
    });
    User initiated actions

    View Slide

  23. Making use of queues
    @interface Event : NSObject
    + (NSProgress *)getWithCoordinate:(CLLocationCoordinate2D)coordinate

    completionHandler:(void(^)(

    NSArray *events,

    NSError *error)
    )completionHandler;
    @end
    Data Source

    View Slide

  24. Being thread safe

    View Slide

  25. Being thread safe
    @interface Event : NSObject
    @property (nonatomic, strong, readonly) NSNumber *identifier;
    @property (nonatomic, strong, readonly) NSDate *startDate;
    @property (nonatomic, strong, readonly) NSString *name;
    @end
    @interface MutableEvent : Event
    @property (nonatomic, strong, readwrite) NSNumber *identifier;
    @property (nonatomic, strong, readwrite) NSDate *startDate;
    @property (nonatomic, strong, readwrite) NSString *name;
    @end
    Immutability

    View Slide

  26. Being thread safe
    @interface Event : NSObject
    @property (nonatomic, strong, readonly) NSNumber *identifier;
    @property (nonatomic, strong, readonly) NSDate *startDate;
    @property (nonatomic, strong, readonly) NSString *name;
    @end
    @interface MutableEvent : Event
    @property (nonatomic, strong, readwrite) NSNumber *identifier;
    @property (nonatomic, strong, readwrite) NSDate *startDate;
    @property (nonatomic, strong, readwrite) NSString *name;
    @end
    Immutability

    View Slide

  27. Being thread safe
    @interface Event : NSObject
    @property (nonatomic, strong, readonly) NSNumber *identifier;
    @property (nonatomic, strong, readonly) NSDate *startDate;
    @property (nonatomic, strong, readonly) NSString *name;
    @end
    @interface MutableEvent : Event
    @property (nonatomic, strong, readwrite) NSNumber *identifier;
    @property (nonatomic, strong, readwrite) NSDate *startDate;
    @property (nonatomic, strong, readwrite) NSString *name;
    @end
    Immutability

    View Slide

  28. Being thread safe
    @interface Event : NSObject
    @property (nonatomic, strong, readonly) NSNumber *identifier;
    @property (nonatomic, strong, readonly) NSDate *startDate;
    @property (nonatomic, strong, readonly) NSString *name;
    @end
    @interface MutableEvent : Event
    @property (nonatomic, strong, readwrite) NSNumber *identifier;
    @property (nonatomic, strong, readwrite) NSDate *startDate;
    @property (nonatomic, strong, readwrite) NSString *name;
    @end
    Immutability

    View Slide

  29. Being thread safe
    @interface Event : NSObject
    @property (nonatomic, strong, readonly) NSNumber *identifier;
    @property (nonatomic, strong, readonly) NSDate *startDate;
    @property (nonatomic, strong, readonly) NSString *name;
    @end
    @interface MutableEvent : Event
    @property (nonatomic, strong, readwrite) NSNumber *identifier;
    @property (nonatomic, strong, readwrite) NSDate *startDate;
    @property (nonatomic, strong, readwrite) NSString *name;
    @end
    Immutability

    View Slide

  30. Being thread safe
    @interface Event : NSObject
    @property (nonatomic, strong, readonly) NSNumber *identifier;
    @property (nonatomic, strong, readonly) NSDate *startDate;
    @property (nonatomic, strong, readonly) NSString *name;
    @end
    @interface MutableEvent : Event
    @property (nonatomic, strong, readwrite) NSNumber *identifier;
    @property (nonatomic, strong, readwrite) NSDate *startDate;
    @property (nonatomic, strong, readwrite) NSString *name;
    @end
    Immutability

    View Slide

  31. Being thread safe
    @interface Event : NSObject
    @property (nonatomic, strong, readonly) NSNumber *identifier;
    @property (nonatomic, strong, readonly) NSDate *startDate;
    @property (nonatomic, strong, readonly) NSString *name;
    @end
    @interface MutableEvent : Event
    @property (nonatomic, strong, readwrite) NSNumber *identifier;
    @property (nonatomic, strong, readwrite) NSDate *startDate;
    @property (nonatomic, strong, readwrite) NSString *name;
    @end
    Immutability

    View Slide

  32. Being thread safe
    @interface FavoriteEventsController : NSObject
    @property (nonatomic, strong, readonly) NSArray *events;
    - (void)addEvent:(Event *)event;
    - (void)removeEvent:(Event *)event;
    @end
    Thread safety

    View Slide

  33. Being thread safe
    @interface FavoriteEventsController : NSObject
    @property (nonatomic, strong, readonly) NSArray *events;
    - (void)addEvent:(Event *)event;
    - (void)removeEvent:(Event *)event;
    @end
    Thread safety

    View Slide

  34. Being thread safe
    @interface FavoriteEventsController : NSObject
    @property (nonatomic, strong, readonly) NSArray *events;
    - (void)addEvent:(Event *)event;
    - (void)removeEvent:(Event *)event;
    @end
    Thread safety

    View Slide

  35. Being thread safe
    - (instancetype)init {
    self = [super init];
    if (self) {
    dispatch_queue_attr_t attributes = DISPATCH_QUEUE_CONCURRENT;
    if (&dispatch_queue_attr_make_with_qos_class != NULL) {
    attr = dispatch_queue_attr_make_with_qos_class(attributes,

    QOS_CLASS_BACKGROUND, -1);
    }
    dispatch_queue_t workerQueue = dispatch_queue_create("fav", attr);
    _workerQueue = workerQueue;
    _events = @[];
    }
    return self;
    }
    Thread safety - FavoriteEventsController

    View Slide

  36. Being thread safe
    - (instancetype)init {
    self = [super init];
    if (self) {
    dispatch_queue_attr_t attributes = DISPATCH_QUEUE_CONCURRENT;
    if (&dispatch_queue_attr_make_with_qos_class != NULL) {
    attr = dispatch_queue_attr_make_with_qos_class(attributes,

    QOS_CLASS_BACKGROUND, -1);
    }
    dispatch_queue_t workerQueue = dispatch_queue_create("fav", attr);
    _workerQueue = workerQueue;
    _events = @[];
    }
    return self;
    }
    Thread safety - FavoriteEventsController

    View Slide

  37. Being thread safe
    - (instancetype)init {
    self = [super init];
    if (self) {
    dispatch_queue_attr_t attributes = DISPATCH_QUEUE_CONCURRENT;
    if (&dispatch_queue_attr_make_with_qos_class != NULL) {
    attr = dispatch_queue_attr_make_with_qos_class(attributes,

    QOS_CLASS_BACKGROUND, -1);
    }
    dispatch_queue_t workerQueue = dispatch_queue_create("fav", attr);
    _workerQueue = workerQueue;
    _events = @[];
    }
    return self;
    }
    Thread safety - FavoriteEventsController

    View Slide

  38. Being thread safe
    - (instancetype)init {
    self = [super init];
    if (self) {
    dispatch_queue_attr_t attributes = DISPATCH_QUEUE_CONCURRENT;
    if (&dispatch_queue_attr_make_with_qos_class != NULL) {
    attr = dispatch_queue_attr_make_with_qos_class(attributes,

    QOS_CLASS_BACKGROUND, -1);
    }
    dispatch_queue_t workerQueue = dispatch_queue_create("fav", attr);
    _workerQueue = workerQueue;
    _events = @[];
    }
    return self;
    }
    Thread safety - FavoriteEventsController

    View Slide

  39. Being thread safe
    - (instancetype)init {
    self = [super init];
    if (self) {
    dispatch_queue_attr_t attributes = DISPATCH_QUEUE_CONCURRENT;
    if (&dispatch_queue_attr_make_with_qos_class != NULL) {
    attr = dispatch_queue_attr_make_with_qos_class(attributes,

    QOS_CLASS_BACKGROUND, -1);
    }
    dispatch_queue_t workerQueue = dispatch_queue_create("fav", attr);
    _workerQueue = workerQueue;
    _events = @[];
    }
    return self;
    }
    Thread safety - FavoriteEventsController

    View Slide

  40. Being thread safe
    - (NSArray *)events {
    __block NSArray *events;
    dispatch_sync(self.workerQueue, ^{
    events = _events;
    });
    return events;
    }
    - (void)setEvents:(NSArray *)events {
    dispatch_barrier_async(self.workerQueue, ^{
    _events = events;
    });
    }
    Thread safety - FavoriteEventsController

    View Slide

  41. Being thread safe
    - (NSArray *)events {
    __block NSArray *events;
    dispatch_sync(self.workerQueue, ^{
    events = _events;
    });
    return events;
    }
    - (void)setEvents:(NSArray *)events {
    dispatch_barrier_async(self.workerQueue, ^{
    _events = events;
    });
    }
    Thread safety - FavoriteEventsController

    View Slide

  42. Being thread safe
    - (NSArray *)events {
    __block NSArray *events;
    dispatch_sync(self.workerQueue, ^{
    events = _events;
    });
    return events;
    }
    - (void)setEvents:(NSArray *)events {
    dispatch_barrier_async(self.workerQueue, ^{
    _events = events;
    });
    }
    Thread safety - FavoriteEventsController

    View Slide

  43. Being thread safe
    - (void)addEvent:(Event *)event {
    dispatch_barrier_async(self.workerQueue, ^{
    _events = [_events arrayByAddingObject:event];
    });
    }
    - (void)removeEvent:(Event *)event {
    dispatch_barrier_async(self.workerQueue, ^{
    NSPredicate *p = [NSPredicate

    predicateWithFormat:@"NOT (SELF == %@)", event];
    _events = [_events filteredArrayUsingPredicate:p];
    });
    }
    Thread safety - FavoriteEventsController

    View Slide

  44. Convenience

    View Slide

  45. Convenience
    dispatch_group_t fetchGroup = dispatch_group_create();
    for (int i = 0; i < 10; i++) {
    dispatch_group_enter(fetchGroup);
    [Fetcher doWithCompletionHandler:^(BOOL success) {
    // Handle completion
    dispatch_group_leave(fetchGroup);
    }];
    }
    dispatch_group_wait(fetchGroup, DISPATCH_TIME_FOREVER);
    // Do stuff after all completions finished
    Dispatch groups - Synchronously

    View Slide

  46. Convenience
    dispatch_group_t fetchGroup = dispatch_group_create();
    for (int i = 0; i < 10; i++) {
    dispatch_group_enter(fetchGroup);
    [Fetcher doWithCompletionHandler:^(BOOL success) {
    // Handle completion
    dispatch_group_leave(fetchGroup);
    }];
    }
    dispatch_group_notify(fetchGroup, dispatch_get_main_queue(), ^{
    // Do stuff after all completions finished
    });
    Dispatch groups - Asynchronously

    View Slide

  47. Thank you
    Michael Ochs
    @_mochs
    http://ios-coding.com

    View Slide