December 27, 2018
(Full source code for this post: https://github.com/joshbuddha/completionHandler.git)
Lets go beyond simply using a completion handler.
1 2 3 |
override func viewWillAppear(_ animated: Bool) { print("Standard completion handler") } |
In this post we will create our own completion handler. Along the way we’ll learn about closures, the @escaping keyword, typealias, and the capture list.
A completion handler relies on closures. Apple defines them like this: “Closures are self-contained blocks of functionality that can be passed around and used in your code.”
We are essentially defining functionality that will be passed to something that takes time so that it can be executed when that thing has completed its work. In our case we are telling the space ship where we want to go and then getting the details about our destination once we arrive.
Its time to help our hero Arthur travel the universe and keep his anxiety under control.
1. Define a typealias for the closure.
1 |
typealias HeartCompletion = (HeartOfGold)->() |
This structure accepts one HeartOfGold enum and returns nothing or Void.
2. Define the HeartOfGold enum.
Once the work has been completed the HeartOfGold enum will be sent. It defines the ship destinations and 2 nested enums that define inventory and anxiety level.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
enum HeartOfGold { case Earth(ArthurItem, PanicLevel) case VogonHold(ArthurItem, PanicLevel) case RestaurantAtEndOfUniverse(ArthurItem, PanicLevel) enum ArthurItem: String { case Towel case BabelFish case Peanuts } enum PanicLevel: String { case Panic = "Panic!" case DontPanic = "Don't Panic!" } var description: String { switch self { case .Earth(.Peanuts, .DontPanic): return "On earth with \(ArthurItem.Peanuts.rawValue). \(PanicLevel.DontPanic.rawValue)" case .RestaurantAtEndOfUniverse(.BabelFish,.DontPanic): return "At the restaurant talking to new friends with \(ArthurItem.BabelFish.rawValue). \(PanicLevel.DontPanic.rawValue)" case .VogonHold(.Towel,.Panic): return "In the vogon hold with trusty \(ArthurItem.Towel.rawValue). \(PanicLevel.Panic.rawValue)" default: return "" } } } |
Ok, now its time for Arthur to decide where he wants to go. The following passes the number associated with the destination as well as the closure block that will be executed upon arrival. Also note the capture list where [unowned self] is set. We used unowned or weak in this case to avoid retain cycles.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//choose your destination. 0: Earth, 1: Vogon Hold, 2: Restaurant //pass the the closure so that the data will be available after the work is complete travelTheGalaxy(location: 0) { [unowned self] (heart: HeartOfGold) -> () in self.heartOfGold = heart guard let heartData = self.heartOfGold else { return } print("Welcome Arthur: ", heartData) print("You are: ", heartData.description) } |
Now lets look at the method that does all the work that takes time. The following accepts the location integer as well as the closure we defined earlier. Once the work is done the completion closure will be called:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
//accept the location argument to choose destination. //set the @escaping attribute because the HeartCompletion closure is invoked AFTER the function returns. //the HeartCompletion closure structure is defined by the type alias above: typealias HeartCompletion = (HeartOfGold)->() func travelTheGalaxy(location: Int, completion: @escaping HeartCompletion) { DispatchQueue.main.asyncAfter(deadline: .now() + 2) { // It takes 2 seconds to travel the galaxy. // Now that time has passed you can now call the completion handler to let the original caller know the work is complete. print("Infinite Improbability Drive is fired up. Time to go.") switch location { case 0: self.heartOfGold = HeartOfGold.Earth(.Peanuts, .DontPanic) case 1: self.heartOfGold = HeartOfGold.VogonHold(.Towel, .Panic) case 2: self.heartOfGold = HeartOfGold.RestaurantAtEndOfUniverse(.BabelFish, .DontPanic) default: print("") } guard let heartData = self.heartOfGold else { return } completion(heartData) } } |
The @escaping attribute is required because the HeartCompletion closure is invoked AFTER the function has completed the work.
Here is the entire class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
class InfiniteDrive { //define class var to hold enum data var heartOfGold: HeartOfGold? func startDrive() { //choose your destination. 0: Earth, 1: Vogon Hold, 2: Restaurant //pass the the closure so that the data will be available after the work is complete travelTheGalaxy(location: 0) { [unowned self] (heart: HeartOfGold) -> () in self.heartOfGold = heart guard let heartData = self.heartOfGold else { return } print("Welcome Arthur: ", heartData) print("You are: ", heartData.description) } } //accept the location argument to choose destination. //set the @escaping attribute because the HeartCompletion closure is invoked AFTER the function returns. //the HeartCompletion closure structure is defined by the type alias above: typealias HeartCompletion = (HeartOfGold)->() func travelTheGalaxy(location: Int, completion: @escaping HeartCompletion) { DispatchQueue.main.asyncAfter(deadline: .now() + 2) { // change 2 to desired number of seconds // It takes 2 seconds to travel the galaxy. // Now that time has passed you can now call the completion handler to let the original caller know the work is complete. print("Infinite Improbability Drive is fired up. Time to go.") switch location { case 0: self.heartOfGold = HeartOfGold.Earth(.Peanuts, .DontPanic) case 1: self.heartOfGold = HeartOfGold.VogonHold(.Towel, .Panic) case 2: self.heartOfGold = HeartOfGold.RestaurantAtEndOfUniverse(.BabelFish, .DontPanic) default: print("") } guard let heartData = self.heartOfGold else { return } completion(heartData) } } } |
Again, the full source code is here: https://github.com/joshbuddha/completionHandler.git Hit me up in the comments with any questions.
Josh K
© 2023 jetstream
Recent Comments