Closure SPM 📦
git clone git@github.com:0xLeif/Closure.git
Defining a Closure
“A Closure is a block of code that can be executed by reference of a variable or name. Closures in Swift are normally unnamed and passed in as a parameter variable or stored as a variable.”
🔗 source
Closure.swift
When we define a Closure we can assume that it will always have an Input
and an Output
. Either value could be Void
in the case that a value doesn’t exist. Knowing this we can expect that our object look something like, Closure<Input, Output>
. We will need an actual closure when we initialize our Closure
object. This value should look something like this, (Input) -> Output
.
/// A Struct that defines a Closure with a given Input and a given Output
public struct Closure<Input, Output> {
/// The Closure passed in during initialization
public let method: (Input) -> Output
/// Initialize a Closure without any scoped state
public init(
_ closure: @escaping (Input) -> Output
) {
method = closure
}
}
Notice that we must specify that the closure is @escaping
. This is becuase we will store it in a variable that we can keep for the lifetime of the object. In other words @escaping
means that the closure can leave the scope of the function it is passed into.
Running the Closure
Now that we have defined the basic Closure
object we can start running the closure! Currently we would need to reach into the object. You will also notice that we need to pass in ()
which is the Void type.
let sayHello = Closure {
print("Hello 👋")
}
sayHello.method(())
In this example, sayHello
is of type Closure<Void, Void>
.
Extending Closure
Now we will create a function to run the Closure
with the required Input
and potentially a completion handler. This will make it easier to call the method instead of having to reach into the object.
public extension Closure {
/// Run the Closure with Input and a Completion Handler
func run(
input: Input,
onCompletion onCompletionHandler: () -> Void = {}
) -> Output {
defer {
onCompletionHandler()
}
return method(input)
}
}
Void Input Closures
Now that we have a run function, we can add another extension for when the Input
is Void
. We will add another run function where it doesn’t require an Input
and defaults the value to ()
.
extension Closure where Input == Void {
func run(
onCompletion onCompletionHandler: () -> Void = {}
) -> Output {
run(
input: (),
onCompletion: onCompletionHandler
)
}
}
Chaining Closures
To be able to chain closures, we would need to receive some new closure to run. We can expect that this closure will look very similar to the closure we already have, but it could have some new output. The closure should look similar to this, @escaping (Output) -> NewOutput
.
extension Closure {
/// Chain another Closure with a NewOutput
func then<NewOutput>(
_ closure: @escaping (Output) -> NewOutput
) -> Closure<Input, NewOutput> {
Closure<Input, NewOutput> { input in
closure(method(input))
}
}
}
Looking at this function, note that we pass the Output
of method(input)
into the new closure. Using then
, we are able to make a new Closure
where we still pass in the same type of Input
, but now the NewOutput
could be the same or different than Output
.
Chaining Closures Example
Psuedo Code
Closure<Void, Void> -> Closure<Void, Int> -> Closure<Void, Void>
let sayHello: Closure<Void, Void> = Closure {
print("Hello")
}
.then {
Int.random(in: 0 ... 9)
}
.then { randomInt in
print("I got this random int: \(randomInt) 🤷♂️")
}
In this example, sayHello
is still of type Closure<Void, Void>
.
Stateful Closure
🤔 What is a stateful closure?
Could we have a Closure that has some state? For example, could we have a closure that returns a value and each time we run it it increments one to the value.
let statefulCount: Closure<Void, Int> = ...
for _ in 0 ... 9 {
print(statefulCount.run())
}
/** Output
0
1
2
3
4
5
6
7
8
9
*/
To do this we need a new init!
public struct Closure<Input, Output> {
// ...
/// Initialize a Closure with potential scoped state
public init(
_ closure: () -> ((Input) -> Output)
) {
method = closure()
}
}
- In the example above where we have
statefulCount
. TheClosure
was defined with the following code.
let statefulCount: Closure<Void, Int> = Closure {
var count = 0
return { _ in
defer {
count += 1
}
return count
}
}
Extra Examples
No State
let noStateCount = Closure<String, String> { text in
String(repeating: text, count: 4)
}
.then { string in
Int(string) ?? 0
}
XCTAssertEqual(noStateCount.method("5"), 5555)
XCTAssertEqual(noStateCount.method("5"), 5555)
XCTAssertEqual(noStateCount.method("5"), 5555)
State
let stateCount: Closure<String, Int> = Closure<String, String> {
var count = 1
return { text in
defer {
count += 1
}
return String(repeating: text, count: count)
}
}
.then { string in
Int(string) ?? 0
}
XCTAssertEqual(stateCount.method("5"), 5)
XCTAssertEqual(stateCount.method("5"), 55)
XCTAssertEqual(stateCount.method("5"), 555)