iOS Developer: From Noob to Ninja in 30 days — Day 5, Three REAL use cases for Closures (Asynchronous, Synchronous, Property)

Tony Trejo
7 min readDec 8, 2020

In this post I will be applying three REAL use cases for Closures, these closures are used everywhere in any application.

  • Asynchronous Callback (URLSession, DispatchQueue, DispatchGroup)
  • Synchronous Callback (Append String, Sort)
  • Property Callback (Network, Append String, Sort)
Photo by Nicolas Lobos @lobosnico

What is a Closure?

Closures are self-contained blocks of functionality that can be passed around and used in your code.

A simple definition can be a closure is a function without the keyword func.

With that being said let’s start with some usual cases.

  • Case No 1 “Closure used in an Asynchronous Callback”

This is one of the most common use cases of a closure that is help you to be notified when some asynchronous task has finished its execution.

enum NewtorkError: Error {
case noData
}
struct AsyncOperation { func networkRequest(completion: @escaping (Result<[String], Error>) -> Void) { // 1 guard let url = URL(string: "https://api.mocki.io/v1/7759e394")
else { fatalError("Could not be created the url") }
let session = URLSession(configuration: .default) let task = session.dataTask(with: URLRequest(url: url)) { (data, response, error) in
guard
let data = data else {
return completion(.failure(NewtorkError.noData))
}

let decoder = JSONDecoder()

do {

let result = try decoder.decode([String].self, from: data)
DispatchQueue.main.async {
/// Back on the main queue.
completion(.success(result))
}
} catch {
completion(.failure(error))
}
}

task.resume()
}
func asyncSortArray(_ array: [Int], completion: @escaping (([Int]) -> Void)) { // 2 let concurrentQueue = DispatchQueue(label: "tonydev.concurrent.queue", attributes: .concurrent) concurrentQueue.async { let result = array.sorted()
DispatchQueue.main.async {
/// Back on the main queue.
completion(result)
}
}
}
func appendString(_ string: String, completion: @escaping ((String) -> Void)) { // 3 var result = [String]()
let group = DispatchGroup()

group.enter()
DispatchQueue.main.async {
result.append("Async")
group.leave()
}

group.enter()
DispatchQueue.main.async {
result.append("Hello")
group.leave()
}
group.enter()
DispatchQueue.main.async {
result.append(string)
group.leave()
}
/// Back on the main queue.
group.notify(queue: DispatchQueue.main) {
completion(result.joined(separator: " "))
}
}
}

Let’s explain that code.

We created a struct called AsyncOperation, that struct provides some async methods.

  1. This method uses an URLSession instance, that class provides the common way to make requests to the backend.

The closure used in this method is this:

 completion: @escaping (Result<[String], Error>) -> Void
  • completion is the name used for this closure.
  • The keyword @escaping means that this closure would be used in another context, in this case in another thread, when you use a closure in another thread the closure needs to be marked as escaping.

This is a good article about @escaping and @nonescaping.

Then the callback for this method would return a Result type, for learn more about Result type you can take a look on the link below, in this case Result type is returning in the success an array of string ([String]) and in the failure case an error (Error).

I show you how to use that closure below.

let asyncOperation = AsyncOperation()
asyncOperation.networkRequest { (response) in
switch
response {
case .success(let result):
print(result) // ["Hello","World!!","Service"]
case .failure(let error):
print(error)
}
}

2. This method is sorting an array of integers into an async task.

The closure used in this method is this:

completion: @escaping (([Int]) -> Void)

The async task used in this method is a DispatchQueue, if you want do know more about this topic you can learn more about this in the link below.

As you can see that closure is returning a sorted array of integers ([Int]).

I show you how to use that closure below.

let asyncOperation = AsyncOperation()
asyncOperation.asyncSortArray([90, 5, 1, 87, 54]) { (result) in
print(result) // [1, 5, 54, 87, 90]
}

3. This method is making an async append string.

The closure used in this method is this:

completion: @escaping ((String) -> Void)

The async task used in this method is a DispatchGroup, if you want to learn more about this topic you can follow the link below.

As you can see is returning a String value.

I show you how to use that closure below.

let asyncOperation = AsyncOperation()
asyncOperation.appendString("World!!") { (result) in
print(result) // Hello World!!
}

Those are common use cases in asynchronous tasks, let’s continue with synchronous tasks.

  • Case No 2 “Callback used in a Synchronous Callback”

This case is used when you want to apply some work, but it’s done synchronously (main queue).

struct Operation {

func syncTask(completion:() -> Void) { // 1
// Some work done here
completion() // Callback execution
}

func sortArray(_ array: [Int], completion:([Int]) -> Void) { // 2
// Sorted here
let result = array.sorted()
completion(result) // Callback execution
}
func appendString(_ string: String, completion:(String) -> Void) { // 3
var result = [String]()
result.append("Sync")
result.append("Hello")
result.append(string)
completion(result.joined(separator: " ")) // Callback execution
}
}

Let’s explain that code.

We created a struct called Operation, that struct provides some synchronous methods.

  1. This method is making a simple closure.

The closure used in this method is this:

completion:() -> Void

As you can see is not returning any value.

I show you how to use that closure below.

let operation = Operation()
operation.syncTask(completion: {
print("Sync callback done") // Any returned value
})

2. This method is sorting an array of integers into a sync task.

The closure used in this method is this:

completion:([Int]) -> Void

As you can see is not returning an array of integers.

I show you how to use that closure below.

let operation = Operation()
operation.sortArray([90, 5, 1, 87, 54]) { (intArray) in
print(intArray) // [1, 5, 54, 87, 90]
}

3. This method is making an append string.

The closure used in this method is this:

completion:(String) -> Void

As you can see is returning a String value.

I show you how to use that closure below.

let operation = Operation()
operation.appendString("World!!") { (result) in
print(result) // Hello World!!
}
  • Case No 3 used as a Property
struct OperationHandler {

private let operation = AsyncOperation() // 1

var completionString: ((String) -> Void)? // 2
var completionResult: ((Result<[String], Error>) -> Void)? // 3 var completionSort: (([Int]) -> Void)? // 4 func networkRequest() {
operation.networkRequest { (result) in

completionResult
?(result)
}
}
func sortArray(_ array: [Int]) {
operation.asyncSortArray(array) { (result) in
completionSort?(result)
}
}
func appendString(_ string: String) {
operation.appendString("World!!") { (result) in
completionString?(result)
}
}
}

Let’s explain that code.

We created a struct called OperationHandler, that struct provides closures used as a properties, those will be used when a method was called.

  1. A private instance of AsyncOperation, something important here is always use access control in properties and methods, the access control by default is internal.

If you want to know more about access control used in swift you can see the link below.

2. This closure used for callback a String.

var completionString: ((String) -> Void)?

That closure will be used when the OperationHandler instance call the appendString method.

func appendString(_ string: String) { 
operation.appendString("World!!") { (result) in
completionString?(result)
// here
}
}

I show you how to use that closure below.

var handler = OperationHandler()
handler.completionString = { result in
print(result) // Hello World!!
}
handler.appendString("World!!")

Be careful, you need to set the completionString reference first and then you can use it.

3. This closure is used to callback a Result type.

var completionResult: ((Result<[String], Error>) -> Void)?

I show you how to use that closure below.

var handler = OperationHandler()
operation.networkRequest { (result) in
completionResult
?(result) // ["Hello","World!!","Service"]
}
handler.networkRequest()

4. This closure is used to callback a sorted array.

var completionSort: (([Int]) -> Void)?

I show you how to use that closure below.

var handler = OperationHandler()handler.completionSort = { result in
print(result) // [1, 5, 54, 87, 90]
}

handler.sortArray([90, 5, 1, 87, 54])

Now let’s apply all the previous closures in a ViewController, I show you how to do it below.

final class ViewController: UIViewController {  override func viewDidLoad() { // 1
super.viewDidLoad()

setAsyncClosures()
setSyncClosures()
setClosuresUsingProperties()
}
func setAsyncClosures() { // 2

let asyncOperation = AsyncOperation()
asyncOperation.appendString("World!!") { (result) in
print(result) // Hello World!!
}
asyncOperation.asyncSortArray([90, 5, 1, 87, 54]) { (result) in
print(result) // [1, 5, 54, 87, 90]
}
asyncOperation.networkRequest { (response) in

switch
response {
case .success(let result):
print(result) // ["Hello","World!!","Service"]
case .failure(let error):
print(error)
}
}
}
func setSyncClosures() { // 3

let operation = Operation()
operation.syncTask(completion: {
print("Sync callback done")
})
operation.sortArray([90, 5, 1, 87, 54]) { (intArray) in
print(intArray) // [1, 5, 54, 87, 90]
}
operation.appendString("World!!") { (result) in
print(result) // Hello World!!
}
}
func setClosuresUsingProperties() { // 4

var handler = OperationHandler()

handler.completionResult = { result in
print(result) // ["Hello","World!!","Service"]
}

handler.networkRequest()
handler.completionSort = { result in
print(result) // [1, 5, 54, 87, 90]
}

handler.sortArray([90, 5, 1, 87, 54])
handler.completionString = { result in
print(result) // Hello World!!
}
handler.appendString("World!!")
}
}

Something important to know about the ViewController is the life cycle, we are using the viewDidLoad method to set all the closures.

  1. The setAsyncClosures method is setting the AsyncOperation closures.
  2. The setSyncClosures method is setting the Operation closures.
  3. The setClosuresUsingProperties method is setting the OperationHandler closures.

If you want to know more about life cycle you can see the link below.

Additional Resources

  • Apple’s documentation
  • URLSession documentation
  • More about Async and Sync processes
  • Live Playgrounds in Xcode
  • Closures Playground

Steve Jobs: “Don’t let the noise of others’ opinions drown out your own inner voice.”

I think is enough for this post. I’ll be working on the next post don’t forget to follow me and if you are interested in personal training let me know.

--

--