KEMBAR78
Threads, Queues, and More: Async Programming in iOS | PDF
THREADS, QUEUES,
AND MORE: ASYNC
PROGRAMMING
IN IOS
Why Async?
Long running is:
>= 0.01666 seconds
Perform long running tasks
asynchronously
Threads vs Queues
QueueThread
QueueThreads
Main Queue
Main Queue
UI
{
code…
}
UI UI
{
code…
}
{
…
}
Main Queue manages the main thread
Main Queue
UI
{
code…
}
UI UI
{
code…
}
{
…
}
User Interface / UIKit all happens on the main queue
Main Queue
UI
{
code…
}
UI UI
{
code…
}
{
…
}
Any custom code in view controllers (for example, viewDidLoad)
executes on main queue
Main Queue
How do we get off
the main queue?
NSObject / NSThread
Grand Central Dispatch
NSOperation
Networking
NSObject / NSThread
NSObject methods
- performSelector:withObject:afterDelay:
- performSelector:withObject:afterDelay:inModes:
- performSelectorOnMainThread:withObject:waitUntilDone:
- performSelectorOnMainThread:withObject:waitUntilDone:modes:
- performSelector:onThread:withObject:waitUntilDone:
- performSelector:onThread:withObject:waitUntilDone:modes:
- performSelectorInBackground:withObject:
…this approach uses threads,
not queues.
NOT recommended.
Grand Central Dispatch
Grand Central Dispatch
• Dispatch Queues
• Global
• Concurrent
• Serial
• Dispatch Groups
Dispatch Queues
First, get a reference
to the queue
Dispatch Async - Swift 3
let queue = DispatchQueue.global(qos: .background)
for iteration in 1...iterations {
queue.async { [weak self] in
self?.longRunningTask(with: iteration)
}
}
Dispatch Queue Types - Swift 3
let queue:DispatchQueue
switch approach {
case .dispatchConcurrent:
queue = DispatchQueue.global(qos: .default)
case .dispatchSerial:
queue = DispatchQueue(label: "SerialQueue")
default:
queue = DispatchQueue(label: "ConcurrentQueue",
qos: .background,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: nil)
}
Add work
to the queue
Dispatch Async - Swift 3
let queue = DispatchQueue.global(qos: .background)
for iteration in 1...iterations {
queue.async { [weak self] in
self?.longRunningTask(with: iteration)
}
}
Dispatch Work Item - Swift 3
let workItem = DispatchWorkItem(qos: .default,
flags: .assignCurrentContext) {
…
}
queue.async(execute: workItem)
Go back to the main queue
to update UI
Dispatch Async - Swift 3
…
DispatchQueue.main.async {
strongSelf.taskDelegate?.longRunningTaskDidComplete(with: results)
}
How does it look?
Group tasks
First, create a group
Dispatch Group - Swift 3
func performLongRunningTask(with iterations:Int) {
let queue:DispatchQueue = DispatchQueue.global(qos: .default)
let workGroup = DispatchGroup()
for iteration in 1...iterations {
print("In iteration (iteration)")
queue.async { [weak self] in
workGroup.enter()
self?.longRunningTask(with: iteration)
workGroup.leave()
}
}
workGroup.notify(queue: queue) {
DispatchQueue.main.async {
self.taskDelegate?.longRunningTaskDidComplete(with: self.results)
print("Called delegate for group")
}
}
}
Enter the group
Dispatch Group - Swift 3
func performLongRunningTask(with iterations:Int) {
let queue:DispatchQueue = DispatchQueue.global(qos: .default)
let workGroup = DispatchGroup()
for iteration in 1...iterations {
print("In iteration (iteration)")
queue.async { [weak self] in
workGroup.enter()
self?.longRunningTask(with: iteration)
workGroup.leave()
}
}
workGroup.notify(queue: queue) {
DispatchQueue.main.async {
self.taskDelegate?.longRunningTaskDidComplete(with: self.results)
print("Called delegate for group")
}
}
}
Do your work
Dispatch Group - Swift 3
func longRunningTask(with iteration:Int) {
var iterationResults:[String] = []
for counter in 1...10 {
Thread.sleep(forTimeInterval: 0.1)
iterationResults.append("Iteration (iteration) - Item (counter)")
}
if self.approach == .dispatchGroup {
self.results.append(contentsOf: iterationResults)
} else {
self.resultsQueue.sync {
self.results.append(contentsOf: iterationResults)
}
}
}
Leave the group
Dispatch Group - Swift 3
func performLongRunningTask(with iterations:Int) {
let queue:DispatchQueue = DispatchQueue.global(qos: .default)
let workGroup = DispatchGroup()
for iteration in 1...iterations {
print("In iteration (iteration)")
queue.async { [weak self] in
workGroup.enter()
self?.longRunningTask(with: iteration)
workGroup.leave()
}
}
workGroup.notify(queue: queue) {
DispatchQueue.main.async {
self.taskDelegate?.longRunningTaskDidComplete(with: self.results)
print("Called delegate for group")
}
}
}
Notify - group is “done”
Dispatch Group - Swift 3
func performLongRunningTask(with iterations:Int) {
let queue:DispatchQueue = DispatchQueue.global(qos: .default)
let workGroup = DispatchGroup()
for iteration in 1...iterations {
print("In iteration (iteration)")
queue.async { [weak self] in
workGroup.enter()
self?.longRunningTask(with: iteration)
workGroup.leave()
}
}
workGroup.notify(queue: queue) {
DispatchQueue.main.async {
self.taskDelegate?.longRunningTaskDidComplete(with: self.results)
print("Called delegate for group")
}
}
}
How does it look?
Broken! What’s wrong?
Arrays are *not*
thread safe
Try with sync approach
Dispatch Group - Swift 3
func longRunningTask(with iteration:Int) {
var iterationResults:[String] = []
for counter in 1...10 {
Thread.sleep(forTimeInterval: 0.1)
iterationResults.append("Iteration (iteration) - Item (counter)")
}
if self.approach == .dispatchGroup {
self.results.append(contentsOf: iterationResults)
} else {
self.resultsQueue.sync {
self.results.append(contentsOf: iterationResults)
}
}
}
Better?
OperationQueue
Create an OperationQueue
OperationQueue - Swift 3
var taskOperationQueue:OperationQueue = OperationQueue()
// for a serial queue, do this:
taskOperationQueue.maxConcurrentOperationCount = 1
Add an Operation
to the Queue
OperationQueue - Swift 3
for iteration in 1...iterations {
taskOperationQueue.addOperation({ [weak self] in
self?.longRunningTask(with: iteration)
})
}
OperationQueue - Swift 3
var operationsToAdd:[Operation] = []
var previousOperation:Operation?
for iteration in 1...iterations {
let newOperation = CustomOperation(iteration: iteration,
delegate: taskDelegate)
if let actualPreviousOperation = previousOperation {
newOperation.addDependency(actualPreviousOperation)
}
operationsToAdd.append(newOperation)
previousOperation = newOperation
}
taskOperationQueue.addOperations(operationsToAdd, waitUntilFinished: false)
Go back to the main queue
to update UI
OperationQueue - Swift 3
OperationQueue.main.addOperation {
strongSelf.taskDelegate?.longRunningTaskDidComplete(with: results)
}
In action…
OperationQueue
• InvocationOperation, BlockOperation, or Operation subclass
• OperationQueues and Operations support cancellation
• OperationQueues and Operations support dependencies, even across queues
• Can suspend and resume a queue; clear all operations out of a queue
Networking
Use URLSession &
URLSessionTasks
Networking - Swift 3
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 5
config.httpAdditionalHeaders =
["X-Token":"F06427CB-00AE-422C-992F-854689B5419E"]
self.urlSession = URLSession(configuration: config)
Networking - Swift 3
var iterationComponents:URLComponents = URLComponents()
iterationComponents.scheme = "http"
iterationComponents.host = "joes-macbook-air.local"
iterationComponents.port = 8080
iterationComponents.path = "/iteration(iteration).json"
guard let iterationURL = iterationComponents.url else {
return nil
}
let request = URLRequest(url: iterationURL)
let task = urlSession?.dataTask(with: request, completionHandler:
{ (data, response, error) in
…
}
NOTE: “error” means cannot
connect to host
Response may also be an error.
Check response status code too.
Networking - Swift 3
if let actualError = error {
print("Error encountered with data task: (actualError.localizedDescription)")
return
}
guard let actualResponse = response as? HTTPURLResponse,
actualResponse.statusCode == 200 else {
print("Unexpected response received")
return
}
Completion handler is on
background queue -
do your “heavy lifting” there
Networking - Swift 3
guard let actualData = data else {
print("No data received...")
return
}
let json = try? JSONSerialization.jsonObject(with: actualData,
options: [])
guard let info = json as? [String:Any],
let results = info["iterations"] as? [String] else {
print("Data received was not in the expected format")
return
}
Dispatch to main queue to
update UI with results
Networking - Swift 3
DispatchQueue.main.async {
self.taskDelegate?.longRunningTaskDidComplete(with: results)
}
Things to Avoid
General Async Risks
Simultaneous Updates
Race Conditions /
Out of Sequence Updates
Deadlocks
Thread Explosion
Updating UI from
background thread
Unexpected queue from
block-based SDK method
Unexpected Queue
In iOS SDK, watch out for:
• Completion Handlers
• Error Handlers
• any method parameter titled “withBlock:”
Over-dispatching
Over-dispatching
dispatch_queue_t workQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(workQueue, ^{
[[self managedObjectContext] performBlock:^{
[testObject setValue:@"test" forKey:@"testKey"];
}];
});
Retain Cycles
Retain Cycles
• Happens when the caller retains the block or closure, and the block or closure retains the
caller.
• Different semantics for preventing in Objective-C, Swift 3.0
• Gist is: Avoid a strong reference to the caller from the block / closure unless absolutely
necessary, and make sure the reference gets released
Learn more?
MARTIANCRAFT TRAINING…
@jwkeeley
THANK YOU
martiancraft.com

Threads, Queues, and More: Async Programming in iOS

  • 1.
    THREADS, QUEUES, AND MORE:ASYNC PROGRAMMING IN IOS Why Async?
  • 2.
    Long running is: >=0.01666 seconds
  • 3.
    Perform long runningtasks asynchronously Threads vs Queues
  • 4.
  • 5.
    Main Queue Main Queue UI { code… } UIUI { code… } { … } Main Queue manages the main thread
  • 6.
    Main Queue UI { code… } UI UI { code… } { … } UserInterface / UIKit all happens on the main queue Main Queue UI { code… } UI UI { code… } { … } Any custom code in view controllers (for example, viewDidLoad) executes on main queue
  • 7.
    Main Queue How dowe get off the main queue?
  • 8.
    NSObject / NSThread GrandCentral Dispatch NSOperation Networking NSObject / NSThread
  • 9.
    NSObject methods - performSelector:withObject:afterDelay: -performSelector:withObject:afterDelay:inModes: - performSelectorOnMainThread:withObject:waitUntilDone: - performSelectorOnMainThread:withObject:waitUntilDone:modes: - performSelector:onThread:withObject:waitUntilDone: - performSelector:onThread:withObject:waitUntilDone:modes: - performSelectorInBackground:withObject: …this approach uses threads, not queues. NOT recommended.
  • 10.
    Grand Central Dispatch GrandCentral Dispatch • Dispatch Queues • Global • Concurrent • Serial • Dispatch Groups
  • 11.
    Dispatch Queues First, geta reference to the queue
  • 12.
    Dispatch Async -Swift 3 let queue = DispatchQueue.global(qos: .background) for iteration in 1...iterations { queue.async { [weak self] in self?.longRunningTask(with: iteration) } } Dispatch Queue Types - Swift 3 let queue:DispatchQueue switch approach { case .dispatchConcurrent: queue = DispatchQueue.global(qos: .default) case .dispatchSerial: queue = DispatchQueue(label: "SerialQueue") default: queue = DispatchQueue(label: "ConcurrentQueue", qos: .background, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) }
  • 13.
    Add work to thequeue Dispatch Async - Swift 3 let queue = DispatchQueue.global(qos: .background) for iteration in 1...iterations { queue.async { [weak self] in self?.longRunningTask(with: iteration) } }
  • 14.
    Dispatch Work Item- Swift 3 let workItem = DispatchWorkItem(qos: .default, flags: .assignCurrentContext) { … } queue.async(execute: workItem) Go back to the main queue to update UI
  • 15.
    Dispatch Async -Swift 3 … DispatchQueue.main.async { strongSelf.taskDelegate?.longRunningTaskDidComplete(with: results) } How does it look?
  • 17.
  • 18.
    Dispatch Group -Swift 3 func performLongRunningTask(with iterations:Int) { let queue:DispatchQueue = DispatchQueue.global(qos: .default) let workGroup = DispatchGroup() for iteration in 1...iterations { print("In iteration (iteration)") queue.async { [weak self] in workGroup.enter() self?.longRunningTask(with: iteration) workGroup.leave() } } workGroup.notify(queue: queue) { DispatchQueue.main.async { self.taskDelegate?.longRunningTaskDidComplete(with: self.results) print("Called delegate for group") } } } Enter the group
  • 19.
    Dispatch Group -Swift 3 func performLongRunningTask(with iterations:Int) { let queue:DispatchQueue = DispatchQueue.global(qos: .default) let workGroup = DispatchGroup() for iteration in 1...iterations { print("In iteration (iteration)") queue.async { [weak self] in workGroup.enter() self?.longRunningTask(with: iteration) workGroup.leave() } } workGroup.notify(queue: queue) { DispatchQueue.main.async { self.taskDelegate?.longRunningTaskDidComplete(with: self.results) print("Called delegate for group") } } } Do your work
  • 20.
    Dispatch Group -Swift 3 func longRunningTask(with iteration:Int) { var iterationResults:[String] = [] for counter in 1...10 { Thread.sleep(forTimeInterval: 0.1) iterationResults.append("Iteration (iteration) - Item (counter)") } if self.approach == .dispatchGroup { self.results.append(contentsOf: iterationResults) } else { self.resultsQueue.sync { self.results.append(contentsOf: iterationResults) } } } Leave the group
  • 21.
    Dispatch Group -Swift 3 func performLongRunningTask(with iterations:Int) { let queue:DispatchQueue = DispatchQueue.global(qos: .default) let workGroup = DispatchGroup() for iteration in 1...iterations { print("In iteration (iteration)") queue.async { [weak self] in workGroup.enter() self?.longRunningTask(with: iteration) workGroup.leave() } } workGroup.notify(queue: queue) { DispatchQueue.main.async { self.taskDelegate?.longRunningTaskDidComplete(with: self.results) print("Called delegate for group") } } } Notify - group is “done”
  • 22.
    Dispatch Group -Swift 3 func performLongRunningTask(with iterations:Int) { let queue:DispatchQueue = DispatchQueue.global(qos: .default) let workGroup = DispatchGroup() for iteration in 1...iterations { print("In iteration (iteration)") queue.async { [weak self] in workGroup.enter() self?.longRunningTask(with: iteration) workGroup.leave() } } workGroup.notify(queue: queue) { DispatchQueue.main.async { self.taskDelegate?.longRunningTaskDidComplete(with: self.results) print("Called delegate for group") } } } How does it look?
  • 23.
  • 24.
    Arrays are *not* threadsafe Try with sync approach
  • 25.
    Dispatch Group -Swift 3 func longRunningTask(with iteration:Int) { var iterationResults:[String] = [] for counter in 1...10 { Thread.sleep(forTimeInterval: 0.1) iterationResults.append("Iteration (iteration) - Item (counter)") } if self.approach == .dispatchGroup { self.results.append(contentsOf: iterationResults) } else { self.resultsQueue.sync { self.results.append(contentsOf: iterationResults) } } } Better?
  • 26.
  • 27.
    Create an OperationQueue OperationQueue- Swift 3 var taskOperationQueue:OperationQueue = OperationQueue() // for a serial queue, do this: taskOperationQueue.maxConcurrentOperationCount = 1
  • 28.
    Add an Operation tothe Queue OperationQueue - Swift 3 for iteration in 1...iterations { taskOperationQueue.addOperation({ [weak self] in self?.longRunningTask(with: iteration) }) }
  • 29.
    OperationQueue - Swift3 var operationsToAdd:[Operation] = [] var previousOperation:Operation? for iteration in 1...iterations { let newOperation = CustomOperation(iteration: iteration, delegate: taskDelegate) if let actualPreviousOperation = previousOperation { newOperation.addDependency(actualPreviousOperation) } operationsToAdd.append(newOperation) previousOperation = newOperation } taskOperationQueue.addOperations(operationsToAdd, waitUntilFinished: false) Go back to the main queue to update UI
  • 30.
    OperationQueue - Swift3 OperationQueue.main.addOperation { strongSelf.taskDelegate?.longRunningTaskDidComplete(with: results) } In action…
  • 32.
    OperationQueue • InvocationOperation, BlockOperation,or Operation subclass • OperationQueues and Operations support cancellation • OperationQueues and Operations support dependencies, even across queues • Can suspend and resume a queue; clear all operations out of a queue Networking
  • 33.
    Use URLSession & URLSessionTasks Networking- Swift 3 let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 5 config.httpAdditionalHeaders = ["X-Token":"F06427CB-00AE-422C-992F-854689B5419E"] self.urlSession = URLSession(configuration: config)
  • 34.
    Networking - Swift3 var iterationComponents:URLComponents = URLComponents() iterationComponents.scheme = "http" iterationComponents.host = "joes-macbook-air.local" iterationComponents.port = 8080 iterationComponents.path = "/iteration(iteration).json" guard let iterationURL = iterationComponents.url else { return nil } let request = URLRequest(url: iterationURL) let task = urlSession?.dataTask(with: request, completionHandler: { (data, response, error) in … } NOTE: “error” means cannot connect to host Response may also be an error. Check response status code too.
  • 35.
    Networking - Swift3 if let actualError = error { print("Error encountered with data task: (actualError.localizedDescription)") return } guard let actualResponse = response as? HTTPURLResponse, actualResponse.statusCode == 200 else { print("Unexpected response received") return } Completion handler is on background queue - do your “heavy lifting” there
  • 36.
    Networking - Swift3 guard let actualData = data else { print("No data received...") return } let json = try? JSONSerialization.jsonObject(with: actualData, options: []) guard let info = json as? [String:Any], let results = info["iterations"] as? [String] else { print("Data received was not in the expected format") return } Dispatch to main queue to update UI with results
  • 37.
    Networking - Swift3 DispatchQueue.main.async { self.taskDelegate?.longRunningTaskDidComplete(with: results) } Things to Avoid
  • 38.
  • 39.
    Race Conditions / Outof Sequence Updates Deadlocks
  • 40.
    Thread Explosion Updating UIfrom background thread
  • 41.
  • 42.
    Unexpected Queue In iOSSDK, watch out for: • Completion Handlers • Error Handlers • any method parameter titled “withBlock:” Over-dispatching
  • 43.
    Over-dispatching dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0); dispatch_async(workQueue, ^{ [[self managedObjectContext] performBlock:^{ [testObject setValue:@"test" forKey:@"testKey"]; }]; }); Retain Cycles
  • 44.
    Retain Cycles • Happenswhen the caller retains the block or closure, and the block or closure retains the caller. • Different semantics for preventing in Objective-C, Swift 3.0 • Gist is: Avoid a strong reference to the caller from the block / closure unless absolutely necessary, and make sure the reference gets released Learn more? MARTIANCRAFT TRAINING…
  • 45.