To keep your iOS app running butter-smooth at 60 frames per second, Apple recommends doing as many tasks as possible asynchronously or “off the main thread.” Joe Keeley introduces you to some basic concepts of asynchronous programming in iOS. He discusses what threads and queues are, how they are related, and the special significance of the main queue to iOS. Look at what options are available in the iOS SDK to work asynchronously, including NSOperationQueues and Grand Central Dispatch. Take an in depth look at how to implement some common use cases for those options in Swift. Joe pays special attention to networking, one of the most common asynchronous use cases. Spend some time discussing common asynchronous programming pitfalls—and how to avoid them. Leave this session ready to try out asynchronous programming in your iOS app.
6. 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
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 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)
}
}
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?
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?
27. Create an OperationQueue
OperationQueue - Swift 3
var taskOperationQueue:OperationQueue = OperationQueue()
// for a serial queue, do this:
taskOperationQueue.maxConcurrentOperationCount = 1
28. Add an Operation
to the Queue
OperationQueue - Swift 3
for iteration in 1...iterations {
taskOperationQueue.addOperation({ [weak self] in
self?.longRunningTask(with: iteration)
})
}
29. 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
30. OperationQueue - Swift 3
OperationQueue.main.addOperation {
strongSelf.taskDelegate?.longRunningTaskDidComplete(with: results)
}
In action…
31.
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 - 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.
35. 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
36. 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
37. Networking - Swift 3
DispatchQueue.main.async {
self.taskDelegate?.longRunningTaskDidComplete(with: results)
}
Things to Avoid
44. 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…