iOS Interview Question (Part 3)
1. Does closures capture values?
Yes, closures in Swift can capture and store references to values from their surrounding context. This is known as capturing values or creating a closure’s capture list.
When a closure captures a value, it captures a reference to the value itself, rather than making a copy of it. This means that the closure holds a reference to the original value even if the original scope that defined the value goes out of scope.
Here’s an example to illustrate value capture in closures:
func makeIncrementer(incrementAmount: Int) -> () -> Int {
var total = 0
let incrementer: () -> Int = {
total += incrementAmount
return total
}
return incrementer
}
let incrementByTwo = makeIncrementer(incrementAmount: 2)
print(incrementByTwo()) // Output: 2
print(incrementByTwo()) // Output: 4
In this example, the makeIncrementer
function returns a closure that captures the incrementAmount
and total
variables. The closure increments the total
variable by the incrementAmount
and returns the updated total
value.
When makeIncrementer
is called, it creates a new instance of total
and returns the closure. Each time the closure is called (incrementByTwo()
), it captures and modifies the same total
variable, maintaining its state between calls.
The closure captures the values it references, in this case, incrementAmount
and total
, allowing it to access and modify them even after the makeIncrementer
function has finished executing.
2. What is Trailing Closure?
Write a closure as the last argument of a function or method call.
Trailing closures are particularly useful when the closure’s body is long or when the closure enhances the readability and clarity of the code.
Here’s an example to illustrate the usage of a trailing closure:
func performOperation(on number: Int, operation: (Int) -> Int) -> Int {
return operation(number)
}
let result = performOperation(on: 5) { (number) in
return number * 2
}
print(result) // Output: 10
In this example, the performOperation(on:operation:)
function takes an Int
argument number
and a closure operation
that takes an Int
and returns an Int
. The closure is used to perform some operation on the number
argument.
Instead of passing the closure as a separate argument inside the parentheses, you can use a trailing closure:
let result = performOperation(on: 5) { number in
return number * 2
}
By placing the closure after the function call and removing the argument label, the closure is treated as a trailing closure. This syntax enhances readability and separates the closure’s logic from the function call, making the code easier to read and understand.
Trailing closures are especially helpful when working with functions that take higher-order functions or when using functions like map
, filter
, or reduce
that accept closures as their last arguments.
3. What is Encapsulation? Example? Advantages?
Binding property and methods into a single class is called encapsulation.Encapsulation helps to achieve data abstraction, data hiding, and information security.
Example:
class BankAccount {
private var balance: Double = 0.0
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) {
if amount <= balance {
balance -= amount
} else {
print("Insufficient funds")
}
}
func getBalance() -> Double {
return balance
}
}
let account = BankAccount()
account.deposit(amount: 1000)
account.withdraw(amount: 500)
print(account.getBalance()) // Output: 500.0
In this example, the BankAccount
class encapsulates the balance
property and the methods deposit
, withdraw
, and getBalance
. The balance
property is marked as private, which means it can only be accessed within the class. External entities can interact with the object by using the public methods provided, such as deposit
and withdraw
, without directly accessing or modifying the internal state of the object.
4. Can we use var in optional binding or guard statement instead of let?
In Swift, you cannot use var
in optional binding (if let
or guard let
) statements. The reason for this is that optional binding is intended to provide a way to safely unwrap an optional value and create a new constant (using let
) with that unwrapped value if it exists.
The syntax for optional binding with if let
is as follows:
if let unwrappedValue = optionalValue {
// Code block to execute when optionalValue is not nil
}
Similarly, the syntax for optional binding with guard let
is as follows:
guard let unwrappedValue = optionalValue else {
// Code block to execute when optionalValue is nil
// Use return, break, continue, or throw to exit the scope if necessary
}
// Code block to execute when optionalValue is not nil
The idea behind using let
instead of var
is to ensure that the unwrapped value is immutable within the scope of the if-let or guard-let statement. This helps prevent accidental modifications to the unwrapped value, ensuring the safety of the optional binding operation.
5. What happens when you call a method over optional whose value is nil?
When you call a method on an optional value that is nil
, nothing happens. In Swift, calling a method or accessing a property on a nil
optional results in no operation or value returned.
Here’s an example to illustrate this behavior:
class MyClass {
func myMethod() {
print("Method called")
}
}
var myInstance: MyClass? = nil
myInstance?.myMethod() // No output, nothing happens
In the code above, myInstance
is an optional variable of type MyClass
that is assigned nil
. When you call myMethod()
on myInstance
using optional chaining (myInstance?.myMethod()
), the method is not executed because the optional value is nil
. It simply returns nil
as the result without performing any operation.
Optional chaining allows you to call methods, access properties, or subscript on an optional value without having to explicitly unwrap it. If the optional value is nil
, the chain is short-circuited, and the overall expression evaluates to nil
without executing any further code.
However, it’s important to note that if the method you’re calling has a return type other than Void
(i.e., it returns a value), calling it on a nil
optional will result in nil
being returned as well. For example:
class MyClass {
func myMethod() -> String {
return "Method called"
}
}
var myInstance: MyClass? = nil
let result = myInstance?.myMethod() // result will be nil
In this case, result
will be nil
because the method call returns nil
due to the optional value being nil
.