iOS Interview Prep 3 — Blocks and Closures
The purpose of this interview preparation series is to assist you in quickly refining your interview skills and thoroughly preparing for the typical questions asked during an iOS interview. If you find it useful, please leave a comment or tap the like button.
Overview
Closure or block is a unit of code that can be passed around and executed at a later time. They behave very similar to functions, but they are more flexible and powerful. Because they can be passed as arguments to functions, stored in variables, and even used as return values.
Interview Questions
- Can blocks capture variables from their surrounding scope? Explain with an example.
- How can you avoid creating a strong reference cycle when using a closure or block?
- In what situations would you use the
weakkeyword, and in what situations would you use theunownedkeyword? - What is the purpose of the
__blockmodifier in Objective-C blocks? Explain with an example.
How block works
Blocks are implemented as Objective-C objects of type NSBlock they are just structs with an isa pointer. Literals are directly translated into structs by the compiler. Each block object contains a pointer to the executable code, as well as any captured variables that were referenced in the block. When a block is created, it captures a copy of values of any variables inside the block. These captured values are retained until the block is deallocated.
^ { printf("hello world\\n"); }
struct __block_literal_1 {
void *isa;
int flags;
int reserved;
void (*invoke)(struct __block_literal_1 *);
struct __block_descriptor_1 *descriptor;
};
void __block_invoke_1(struct __block_literal_1 *_block) {
printf("hello world\\n");
}
static struct __block_descriptor_1 {
unsigned long int reserved;
unsigned long int Block_size;
} __block_descriptor_1 = { 0, sizeof(struct __block_literal_1), __block_invoke_1 }Closure/Block Capture
A closure/block can capture and store references to any constants and variables from the surrounding context in which it is defined. Capture allows them to “package” up and take a “snapshot” of the current enclosing scope state.
This is powerful because it enables a closure to access and modify values in the context in which it is defined. This can be particularly useful when working with asynchronous code, because it allows a closure to access and modify values that may have changed by the time the closure is executed.
let url = URL(string: "<https://www.example.com>")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
DispatchQueue.main.async {
self.updateUI(with: data)
}
}
task.resume()sValue and Reference Capture
Swift provide a syntax feature called the capture list that allows you to specify how a closure captures the values of variables that are used within the closure.
let val = 5
// Create a closure that increments the value of val
let myClosure = { [val] in
// The closure captures the original value of val
print("Original value of val: \\(val)")
}
val += 10
myClosure()__block in Objective-C
In Objective-C, primitive types are captured as immutable const types. To update the values of captured variables, you can use the __block keyword. For reference types, block will maintain a strong reference to those objects, which prevents them from being deallocated while the block is still in use.
When working with mutable objects, for example a NSMutableArray
- If you need to mutate the content of the variable, you don’t need to use __block
- I fyou need to change the variable itself, then you need to use __block
int val = 5;
__block int mutableVal = 5;
// Create a block that increments the value of val
void (^myBlock)(void) = ^{
val++;
mutableVal++;
};
// Call the block, which will increment the value of val
myBlock();
// Print the value of val to the console
NSLog(@"Value of val inside block: %d", val); // This prints 5
NSLog(@"Value of val inside block: %d", mutableVal); // This prints 6Avoid Reference Cycle
A reference cycle can occur when a closure capstures a strong reference to an object, and the object also maintains a strong reference to the closure. This leads to a cycle of strong reference that can’t be broken, and can cause the objects to be retained in memory and ultimately lead to app crash. In Swift, you can use [weak self] or [unowned self] capture list to declare a weak or unowned reference to self.
class MyClass {
var myClosure: (() -> Void)?
func someMethod() {
myClosure = { [weak self]
guard let strongSelf = self else { return }
strongSelf.doSomething()
}
}
}To avoid reference cycle in Objective-C, you can use the __weak keyword to declare a weak reference to self. This will allow you to access self from within the block without creating a strong reference cycle. This is especially important when you are working with blocks that are stored in property or variables.
And when the block is invoked, we create a strong reference to the weak self reference. This allows you to access self from within the block, without worrying about it being deallocated before the block completes.
__weak typeof(self) weakSelf = self;
void (^myBlock)(void) = ^{
// Maintain a strong reference to self to keep it in memory
__strong typeof(self) strongSelf = weakSelf;
// Check if self has been deallocated, if so return
if (strongSelf) {
// Do something
}
};iOS Interview Prep Series
iOS Interview Prep 1 — Memory management iOS Interview Prep 2 — Autorelease Pool iOS Interview Prep 3 — Blocks and Closures iOS Interview Prep 4 — Event Handling & Responder Chain iOS Interview Prep 5 — Singletons iOS Interview Prep 6 — Dependency Injection iOS Interview Prep 7 — Concurrency Part 1 iOS Interview Prep 7 — Concurrency Part 2 iOS Interview Prep 8 — View and Layout






