iOS Interview Prep 5: Swift Functions and closures.
Top 13 intermediate to advanced interview questions
Here are the top 13 intermediate to advanced interview questions about Swift functions, along with answers and examples:
1. What is a closure in Swift, and how is it different from a regular function? Provide examples of when you would use a closure.
Answer: A closure is a self-contained block of code that can be assigned to variables, passed as arguments to functions, and returned from functions. Closures are similar to inline functions or lambdas in other programming languages. They capture and store references to variables and constants from the surrounding context in which they are defined.
// Example of a closure
let greetClosure: (String) -> String = { name in
return "Hello, \(name)!"
}
let greeting = greetClosure("Alice")
print(greeting) // Output: Hello, Alice!
2. Explain the concept of function currying in Swift. How does it work, and what are its practical use cases? Provide a code example.
Answer: Function currying is the technique of breaking down a function that takes multiple arguments into a series of functions that each take a single argument. It’s useful for creating more specialized functions.
func add(_ x: Int) -> (Int) -> Int {
return { y in
return x + y
}
}
let addFive = add(5)
let result = addFive(3) // Result: 8
3. Discuss the concept of escaping and non-escaping closures in Swift. When would you mark a closure as escaping, and how does it impact memory management?
Answer: An escaping closure is a closure that can outlive the function that defined it, while a non-escaping closure must be executed within the scope of the defining function. Escaping closures are marked with the `@escaping` attribute and are often used in asynchronous scenarios like completion handlers.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completion: @escaping () -> Void) {
completionHandlers.append(completion)
}
4. What is a capturing cycle in Swift closures? How can you break a strong reference cycle to prevent memory leaks when using closures?
Answer: A capturing cycle occurs when closures capture and create strong reference cycles with objects, leading to memory leaks. You can break the cycle by using capture lists or capturing weak references to objects.
class Person {
var name: String
lazy var printName: () -> () = { [weak self] in
if let strongSelf = self {
print(strongSelf.name)
}
}
init(name: String) {
self.name = name
}
}
5. Explain the concept of function composition in Swift. Provide an example of composing two functions to create a new one.
Answer: Function composition is the act of combining two or more functions to produce a new function. It’s a fundamental concept in functional programming.
func square(_ x: Int) -> Int {
return x * x
}
func double(_ x: Int) -> Int {
return x * 2
}
let squareAndDouble = compose(square, double)
let result = squareAndDouble(5) // Result: 100 (5 * 5 * 2)
func compose<A, B, C>(_ f: @escaping (A) -> B, _ g: @escaping (B) -> C) -> (A) -> C {
return { x in g(f(x)) }
}
6. Discuss the @autoclosure attribute in Swift. How does it work, and when would you use it? Provide examples.
Answer: `@autoclosure` is an attribute used with function parameters to automatically convert an expression into a closure. It’s commonly used for lazy evaluation of parameters.
func logIfTrue(_ predicate: @autoclosure () -> Bool) {
if predicate() {
print("It's true!")
}
}
logIfTrue(2 + 2 == 4) // Output: It's true!
7. What are variadic parameters in Swift functions, and how do you use them effectively? Provide a use case where variadic parameters are useful.
Answer: Variadic parameters allow a function to accept a variable number of arguments of the same type. They are indicated by `…` after the parameter type.
func sum(_ numbers: Int...) -> Int {
return numbers.reduce(0, +)
}
let result = sum(1, 2, 3, 4, 5) // Result: 15
8. Describe the difference between synchronous and asynchronous functions in Swift. How do you work with asynchronous functions using async/await or completion handlers?
Answer: Synchronous functions block the current thread until they complete, while asynchronous functions allow the thread to continue execution and provide a way to handle the result later.
// Using async/await
async func fetchData() -> Data {
let data = try await URLSession.shared.data(from: url)
return data.0
}
// Using completion handlers
func fetchData(completion: @escaping (Data?, Error?) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
completion(data, error)
}.resume()
}
9. Explain how Swift supports first-class and higher-order functions. Provide examples of using functions as arguments and return values.
Answer: In Swift, functions are first-class citizens, which means they can be assigned to variables, passed as arguments to functions, and returned from functions.
func applyOperation(_ operation: (Int, Int) -> Int, a: Int, b: Int) -> Int {
return operation(a, b)
}
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
let result = applyOperation(add, a: 3, b: 5) // Result: 8
10. Discuss the concept of function pointers in Swift. How can you dynamically select and call functions based on conditions or user input? Provide a code sample.
Answer: Function pointers in Swift allow you to store references to functions and call them dynamically.
typealias MathOperation = (Int, Int) -> Int
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
func subtract(_ a: Int, _ b: Int) -> Int {
return a - b
}
let operation: MathOperation = add
let result = operation(5, 3) // Result: 8
11. Explain the concept of “escaping” and “non-escaping” closures in Swift and their implications on function parameters. Provide an example illustrating when you’d use each type.
Answer: In Swift, an “escaping” closure is one that can outlive the scope in which it is defined, typically because it’s stored or passed to another function and executed later. A “non-escaping” closure must be executed within the scope of the defining function. Escaping closures are marked with the `@escaping` attribute.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completion: @escaping () -> Void) {
completionHandlers.append(completion)
}
func someFunctionWithNonEscapingClosure(completion: () -> Void) {
completion() // Must be called within the function scope
}
12. Discuss the concept of function composition and how it can be used to create more complex functions. Provide a practical example of composing multiple functions.
Answer: Function composition is the process of combining two or more functions to produce a new function. It’s a powerful technique in functional programming.
func square(_ x: Int) -> Int {
return x * x
}
func double(_ x: Int) -> Int {
return x * 2
}
func compose<A, B, C>(_ f: @escaping (A) -> B, _ g: @escaping (B) -> C) -> (A) -> C {
return { x in g(f(x)) }
}
let squareAndDouble = compose(square, double)
let result = squareAndDouble(5) // Result: 100 (5 * 5 * 2)
13. Explain the concept of function currying in Swift. Provide an example of a curried function and explain its advantages.
Answer: Function currying is the process of transforming a function that takes multiple arguments into a series of functions, each taking a single argument. This technique allows for more flexible and specialized function creation.
func add(_ x: Int) -> (Int) -> Int {
return { y in
return x + y
}
}
let addFive = add(5)
let result = addFive(3) // Result: 8
These questions provide insight into Swift functions, closures, and advanced programming concepts.
Please checkout my old posts on the iOS interview prepatation series:
Thank you for reading!
I trust that this article will serve as a valuable asset in your journey towards iOS interview preparation.
If you found the article helpful, kindly share it and feel free to leave a comment below suggesting the next topic for which you’d like to receive similar question insights.