Closures in Swift
Closures are self contained block of functionality can be used just like string and other datatypes. Closures can be used and passed around your code. Closure stores environment where they created, which means closures stores a copy of variables which are used inside and these variables will be deallocated after closure completes execution.
To know more about the code and flow please download the sample project from GitHub here
What are the closure forms?
Closures take one of three forms:
- Global functions: They have a name and don’t capture any value
- Nested functions: They have a name and can capture values from their enclosing function.
- Closure expressions: They don’t have name and can capture values from their surrounding context.
Why you need closures?
Closures are mainly used for two reasons:
- It can be used as Completion Block: Closures help you to notify when some task finishes it’s execution
- Higher order functions: In High order function instead of passing function as input it’s better to use closures. closures omits the func keyword and function name that makes the code more readable and short.
A higher order function is just a type of function that accepts function as an input and returns value of type function as output.
Closure Expressions:
Closure Expressions are the way to write closure i.e syntax of closures. There are 4 way you can write closure Expressions
- Shorthand Argument Names
- Implicit returns from single-expression closures
- Trailing closure syntax
- Inferring parameter and return value types from context
Shorthand Argument Names:
You can refer the closure parameter by their positions i.e $0,$1,$2 and so on.
Implicit returns from single-expression closures:
Single expression closures can implicitly return the result of their single expression.
Trailing closure syntax:
you need to pass closure as argument to the function as trailing parameter i.e as a last parameter.
Inferring parameter and return value types from context:
Swift can infer the types of its parameters and the type of the value it returns.
e.g: let sortedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
In above e.g names is an array of item. you no need to explicitly mention the type of parameter and return type.
Capturing Values:
A closure can capture constants and variables from the surrounding context in which it is defined. Closure can refer and modify the variable within it’s body even if original variable reference is no longer exist.
Closures are reference types.
All variables that closure references from the context where it was created will be removed after the closure completes execution.
Context defines the scope of the variable.
Non-escaping Closures:
Before swift 3, Closure parameters are escaping by default. From swift 3 and above, this is reversed. Closure parameters are non-escaping by default. if you want to escape the closure execution, you have to use @escaping
with the closure parameters. As the execution ends, the passed closure goes out of scope and have no more existence in memory.
Lifecycle of the non-escaping closure:
- Pass the closure as function argument
- Do some additional work with function.
- Function executes the closure
- Function returns the compiler back.
Non-Escaping Closures vs. Capture Lists
A non-escaping closure can’t create a retain cycle because non-escaping closure does not hold object instance strongly.
e.g: In above example, execution flow is mentioned with step number as step 1, 2,3,4
. Function executes the completion block and then return the compiler.
Output: It prints 6 then “Hi closure is executing”
Escaping Closures:
You need to mention @escaping
with closure parameters to make it as escaping closure. You need to refer self
explicitly within the escaping closure. Function execute these closure asynchronously. A good example of an escaping closure is a completion handler. These closures are mainly used in asynchronous call i.e api call.
Lifecycle of the escaping closure:
- Pass the closure as function argument
- Do some additional work with function.
- Function execute the closure asynchronously or stored.
- Function returns the compiler back.
Escaping Closures vs. Capture Lists
A escaping closure can create a retain cycle. Function execute the closure asynchronously so it will have the reference of object instance. Explicitly you need to inform closure to weakly hold on to the variable instance.
e.g: In above example, execution flow is mentioned with step number as step 1, 2,3,4
. Function executes the code to calculate product and then returns the compiler back. After some delay completion block executes.
Output: “Hi closure is executing” then It prints 6
Autoclosures:
It automatically created to wrap an expression that being passed as an argument to a function.
To use
@autoclosure
with@escaping
attribute as: @autoclosure @escaping () -> return type
Without @autoclosure
, you need to pass the complete closure as a parameter to the function.
Now with @autoclosure
you no need to pass the complete closure as argument to the function. It takes the closure as argument in the form of string, bool or any other datatype. The argument is automatically converted to a closure.
In above example, condition and message are passed as bool and string arguments to the function. These arguments are automatically converted to a closure, Because they mentioned as @autoclosure
.
Why closures are changed to non-escaping by default?
- For better performance and code optimisation by the compiler and it’s safer.
- A non-escaping closure can’t create a retain cycle.
- To change closure to escaping use
@escaping
explicitly and create a weak capture list.
Errors with Closure:
- Change the closure to escaping using
@escaping
keyword
2. Define closure argument and return type. e.g: closure: (Int) -> void
Closure Scope:
Example 1: closure use the same global variable p1 and prints the name of person. In above example closure executes after some delay.
Now function deallocate the person instance and prints name as nil. after some delay closure will execute, but closure is referring to same instance of person that’s why person object inside closure is nil
output: name is nil and name in closure is nil
Example 2: closure capture the argument and prints the name of person. Now function deallocate the person instance and prints name as nil. after some delay closure will execute. In this case Person instance will not be nil because closure holds the reference of person instance before deallocating.
output: name is nil and name in closure is varsha.
To know more about the code and flow please download the sample project from GitHub here
I hope you find this blog helpful. If you enjoyed it, feel free to hit the clap button below 👏 to help others find it! and follow me on Medium. Thanks for reading. ❤❤❤❤