Share
Embed
Concurrency in Swift
Lightning Talk going over the Swift Concurrency Manifesto, and possible applications
Teddy Ku
December 07, 2017
Transcript
29 slides
-
It’s almost 2018! • iPhone X: 6 cores, 3 GB
RAM • Multi-core, multi-thread apps • Scalable, Distributed applications https://farm8.staticflickr.com/7403/16384510489_1cda4743b5_o.jpg -
Concurrency in Swift 4 func processImageData1(completionBlock: (result: Image) -> Void)
{ loadWebResource("dataprofile.txt") { dataResource in loadWebResource("imagedata.dat") { imageResource in decodeImage(dataResource, imageResource) { imageTmp in dewarpAndCleanupImage(imageTmp) { imageResult in completionBlock(imageResult) } } } } } -
Concurrency in Swift 4 • Completion blocks • Queues, Threads
• Mutex, Semaphore • GCD: dispatch_barrier -
Problems • Ambiguous queue • Hard to read • Deadlocks
• Race conditions Bugs -
async/await func loadWebResource(_ path: String) async -> Resource func decodeImage(_
r1: Resource, _ r2: Resource) async -> Image func dewarpAndCleanupImage(_ i : Image) async -> Image func processImageData1() async -> Image { let dataResource = await loadWebResource("dataprofile.txt") let imageResource = await loadWebResource("imagedata.dat") let imageTmp = await decodeImage(dataResource, imageResource) let imageResult = await dewarpAndCleanupImage(imageTmp) return imageResult } -
async/await func loadWebResource(_ path: String) async -> Resource func processImageData1()
async -> Image { let dataResource = await loadWebResource("dataprofile.txt") // ... } • async: the func can suspend before returning a value (is a coroutine) • await: noop, but shows coder that non-local control flow can happen -
Actors: in Swift 1. DispatchQueue 2. Data that queue protects
3. Messages that can be run on that queue `actor`: reference type, like `class`. Can conform to protocols -
Do not communicate by sharing memory; instead, share memory by
communicating - Effective Go -
actors actor TableActor { let mainActor : TheMainActor var theList
: [String] = [] { didSet { mainActor.updateTableView(theList) } } init(mainActor: TheMainActor) { self.mainActor = mainActor } // this checks to see if all the entries in the list are capitalized: // if so, it capitalize the string before returning it to encourage // capitalization consistency in the list. func prettify(_ x : String) -> String { // Details omitted: it inspects theList, adjusting the // string before returning it if necessary. } actor func add(entry: String) { theList.append(prettify(entry)) } } -
actors actor TableActor { let mainActor : TheMainActor var theList
: [String] = [] { didSet { mainActor.updateTableView(theList) } } func prettify(_ x : String) -> String {} actor func add(entry: String) { theList.append(prettify(entry)) } } // 1. send message to Table actor tableActor.add(entry: “foo") // 2. Table actor modifies its own data // 3. Table actor sends message to Main actor with copied data // 4. Main actor refreshes table view -
actors • TableActor’s data: reference to mainActor, and theList •
actor can send messages to any other actor • actor methods are the messages that actors accept • actor methods are implicitly async, so they can freely call async methods and await their results • An actor method cannot return a value, throw an error, or have an inout parameter. • Actor method parameters must produce independent values when copied • Local state and non-actor methods may only be accessed by methods defined lexically on the actor or in an extension to it (whether they are marked actor or otherwise) actor func add(entry: String) mainActor.updateTableView(theList) -
• Compiler optimization • detect deadlocks • optimize ARC retain/release
calls • Better debugging • Spread knowledge First-class concurrency model benefits -
Before: completion block on DispatchQueue.main @objc final class RemoteImageManager: NSObject
{ static func downloadImage(with url: URL, mainQueueCompletion completion: @escaping PINRemoteImageManagerImageCompletion) -> UUID? { return PINRemoteImageManager.shared().downloadImage(with: url, options: [], completion: { (result: PINRemoteImageManagerResult) in DispatchQueue.main.async { completion(result) } }) } } -
After: RemoteImageActor actor RemoteImageActor { // implicitly private let cache:
ImageCache actor func downloadImage(with url: URL) -> RemoteImageManagerResult { // urlSession stuff // handle updating cache without mutex/locks because this actor has exclusive access } } class ViewController: UIViewContoller { private var remoteImageActor: RemoteImageActor! private var imageViewActor: ImageViewActor! private func updateBackground() { let dogeImage = await remoteImageActor.downloadImage(with: "doge.gif") let processedImage = await imageProcessorActor.scale(0.5, image: dogeImage) imageViewActor.updateImageView(with image: dogeImage) } } -
Actor benefits • clearly express which queue is running •
avoid mutex/locks • avoid programmer error -
Still to come • Reliability through fault isolation • fire
and forget actor messages • add error handling in Actor init, not scattered everywhere • handle failures by restarting actor, etc. • Improving system architecture • declarative, clean interprocess communication • `distributed` keyword