Block/Closure in iOS

Omkar Raut
iOS App Development Concepts
12 min readApr 9, 2024

📌 Outline

I. Introduction

  • What is Block/Closure?
  • Use of Block/Closure in iOS

II. Block in Objective-C

  • Creating Block
  • Passing block as a parameter
  • Capturing values in Block

III. Closure in Swift

  • Creating closure
  • Passing closure as a parameter
  • Capturing values in closure
  • Types of closure

IV. Retain cycle in block/closure

V. Conclusions

📌 Introduction

In the dynamic world 🌎 of iOS development, mastering Blocks/Closures is not just a skill but a necessity. Blocks/Closures, self-contained units of code, have revolutionized the way developers approach tasks, offering flexibility and power previously unseen in iOS programming. From asynchronous operations to event handling, Blocks/Closures play a pivotal role in crafting modern and efficient iOS applications. Understanding their syntax, usage, and best practices is essential for developers looking to elevate their skills and harness the full potential of iOS development. In this article, we delve into the fundamentals of Blocks/Closures, exploring their significance and providing insights into their practical application within the iOS ecosystem.

📍 What is Block/Closure?

The “block” represents a language feature within the Objective-C programming language designed to encapsulate lines of code, enabling them to function as variables. This effectively encapsulates a unit of code with its associated state. While originating as an extension of the C programming language, blocks find substantial utility within Apple’s Objective-C environment.

Blocks exhibit similarities to function pointers in the C programming language or closures in various other programming languages. 🤷

If you’re familiar with the Swift programming language, you may have encountered the term “closure,” which bears similarities to Objective-C blocks.

📍 Use of Block/Closure in iOS

Blocks or closures are frequently employed in iOS development to transmit lines of code from one location to another, often as function parameters. They serve a multitude of purposes, some of which are outlined below:

1) Asynchronous Programming: Blocks or closures play a significant role in Grand Central Dispatch (GCD), particularly for executing asynchronous tasks. Typically, time-consuming operations are carried out on background threads, allowing the main thread to remain available for handling user interactions. The block or closure is useful in that context. While a comprehensive discussion of GCD is beyond the scope of this article, I will try to come up with a dedicated article on it. 👍

2) Enumerating Collections: In Objective-C, we utilize blocks to iterate over each element within collections like arrays, sets, and dictionaries. Within these blocks, we gain access to each element as well as its corresponding index, enabling us to execute the necessary actions on each element of the collection.

Example:

[myArray enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
// perform required action on each element.
}];


//let's consider myArray is an array.

3) Animations: In iOS development, we implement a completion block when applying animations to components, which executes upon the completion of the animation. This allows us to perform necessary actions following the animation’s completion, ensuring that the animation has finished. An in-depth exploration of animations in iOS will be covered in a separate article.

4) Completions Handler: As stated previously, when it’s essential to receive notification upon the completion of a specific operation, we employ a completion block. This ensures that the operation has been executed in its entirety.

Certainly, dividing the article into two parts, “Block in Objective-C” and “Closure in Swift” would allow for a more focused exploration of each topic. This separation would provide readers with a clearer understanding and easier navigation through the concepts discussed in Objective-C and Swift programming languages, respectively.

📌 Block in Objective-C

Indeed, the Block language feature was introduced in iOS 4.0 and macOS 10.6 (Snow Leopard) in 2010. It was developed by Apple engineers with the primary objective of enhancing asynchronous programming, enabling concurrent execution, and promoting better code readability within Objective-C.

📍 Creating Block

Creating a block in Objective-C mirrors the process of defining a function.

Example:

^(int numberToPrint) {
NSLog(@"The number is: %@", numberToPrint);
};

It’s similar to function in Objective-C except a caret (^) symbol. The block defined earlier takes a parameter “numberToPrint” of type int and simply prints it. However, to utilize lines of code as a variable, the block must be stored in a variable.

💁‍♂️ Certainly, blocks in Objective-C can contain multiple lines of code, allowing for more complex operations to be encapsulated within them.

Indeed, blocks in Objective-C can be stored just like any other property within the language. This allows for their reuse ♻️ and invocation at various points within the program.

Syntax:

@property (nonatomic, copy) return_type (^block_or_property_name) (list_of_parameter_type);

When declaring a block as a property in Objective-C, it’s essential to always use the copy attribute. This ensures that the block is copied when assigned to the property, preventing potential issues with memory management and ensuring the block’s retention.

So, if we have to store the above created block in a property, we need to do something like below.

@property (nonatomic, copy) void (^numberToPrintProperty)(int);

numberToPrintProperty = ^(int numberToPrint) {
NSLog(@"The number is: %@", numberToPrint);
};
// calling the block
numberToPrintProperty(10) // The number is: 10

As demonstrated in the preceding snippet, invoking the block is similar to calling a function in Objective-C.

Additionally, we can assign a block to a block property using the Objective-C set method, like below.

@property (nonatomic, copy) void (^numberToPrintProperty)(int);

[self setNumberToPrintProperty:^(int numberToPrint) {
NSLog(@"The number is: %@", numberToPrint);
}];
// calling the block
self.numberToPrintProperty(10) // The number is: 10

📢 Objective-C automatically generates getter and setter methods for each and every property.

📍 Passing block as a parameter

Passing a block as a parameter in Objective-C is as straightforward as passing a variable to a function. The syntax for a function that accepts a block parameter should specify the parameter type accordingly during declaration.

Syntax:

-(function_return_type)functionNameWithBlock:(block_return_type (^)(list_of_block_parameters))block_Parameter_name;

Let’s illustrate the syntax with a practical example. In this scenario, I will pass both a value and a block as parameters to a function, which will then execute the block within the function’s scope.

// Step 1: define the block to double the value.
int (^doubleValue)(int) = ^(int value) {
return value * 2;
};

// Step 2: write function to accept block and value.
-(void)printDoubleValueOf:(int)value withBlock:(int (^)(int))block {
NSLog(@"The double value is: %d", block(value));
}

// Step 3: call method to print double value.
[self printDoubleValueOf:10 withBlock:doubleValue];
// This will print: "The double value is: 20" in terminal.

It’s quite straightforward, similar to defining and invoking a regular function. Typically, we pass a block as a parameter in a method to receive a callback upon the completion of a specific task.

📍 Capturing values in Block

As previously mentioned, a block has access to all variables within its scope. This allows for the modification of property values from within the block, making it one of the most compelling features in the programming realm. Consider the example below, where I have altered the property within the block.

// Declaring property
@property (nonatomic, assign) int count;

// Set initial value
self.count = 10;

// Defining block and changing property value inside it.
typeof(self) __weak weakSelf = self; // Explained at last

void (^doubleValue)(int) = ^(int value) {
NSLog(@"Before modifying property: %d X %d = %d", value, self.count, value * self.count);

// change the property
weakSelf.count += 10;

NSLog(@"After modifying property: %d X %d = %d", value, self.count, value * self.count);
};

// Calling block
doubleValue(10)

// This will print the following output in terminal
// Before modifying property: 10 X 10 = 100
// After modifying property: 10 X 20 = 200

📢 Accessing and modifying properties within a block is entirely feasible.

Now, pay special attention, as I’m about to share a few interesting points. While we’re aware that we can access and modify properties inside a block, it’s crucial to note that we can only access variables within a block and cannot modify them directly. If this sounds confusing, that’s perfectly fine. 😂 Take a look at the example below for clarification.

✅ Accessing variables within a block is indeed possible.

int capturedValue = 10;
int (^blockValue)(int) = ^(int value) {
// Access capturedValue variable inside block
return value * capturedValue;
};

NSLog(@"The value is: %d", blockValue(10));
// This will print: "The value is: 100" in terminal.

❌ Attempting to modify variables within a block will result in an error. Let’s explore the consequences when such an attempt is made.

int capturedValue = 10;
int (^blockValue)(int) = ^(int value) {
// Modify variable
capturedValue += 10 // This line throw an error
return value * capturedValue;
};

NSLog(@"The value is: %d", blockValue(10));

When attempting to modify a variable inside a block, the compiler will issue an error message 😔: “Variable is not assignable (missing __block type specifier)”.

ℹ️ If you intend to modify a variable within a block, you must use the special keyword “__block” when declaring the variable. This enables you to modify the variable’s value inside the block.

__block int capturedValue = 10;
int (^blockValue)(int) = ^(int value) {
// Modify variable
capturedValue += 10 // This line throw an error
return value * capturedValue;
};

NSLog(@"The value is: %d", blockValue(10));
// This will print: "The value is: 200" in terminal.

Variables can indeed be modified within a block when using the “__block” keyword. 💁

📌 Closure in Swift

Closures are akin to blocks in Objective-C. They encapsulate lines of code and can be passed as parameters.

📍 Creating closure

Creating a closure in Swift is as straightforward as creating a variable. If you’re familiar with Swift, you already know how to create variables. However, if not 😛, the syntax is as follows:

var variable_name: variable_type = variable_value

We create closures in a similar manner. Refer to the example below for clarification.

let closure: (Int) -> String = { (num) in
return "The number is: \(num)"
}

In the provided example, “(Int) -> String” represents the type of closure, indicating that the closure will accept one parameter of type Int and return a String.

Certainly! A closure type that accepts no parameters and returns a String would be represented as “() -> String”.

Let me know you answer in comment section: what would be the closure type that accepts two parameters of type Int and String respectively, and returns a String value? 🧐

Example:

let closure: (Int) -> String = { (num) in
return "The number is: \(num)"
}

print(closure(10))

// This will print: "The number is: 10" in terminal.

Invoking a closure is as straightforward as calling a method in Swift.

📍 Passing closure as a parameter

As demonstrated earlier, we can store a closure in a variable and then pass that variable as a parameter in a method. This allows us to utilize the closure within the method.

Example:

// Step 1: Defining closure
let doubleValue: (Int) -> Int = { (num) in
return num * 2
}

// Step 2: Defining method that will accept closure
private func printDoubleValue(of value: Int, withBlock block: (Int) -> Int) {
print("Double value is: \(block(value))")
}

// Step 3: Call method
printDoubleValue(of: 10, withBlock: doubleValue)

// This will print: "Double value is: 20" in terminal.

The primary purpose of passing a block as a parameter is to facilitate the execution of the closure after the completion of a specific task. This guarantees that the code within the closure will consistently run following the completion of the specified task.

📍 Capturing values in closure

Similar to blocks in Objective-C, closures in Swift can access and modify variables within their scope without any restrictions.

Example:

private var initialValue: Int = 10

let closure: (Int) -> Int = { [weak self] (num) in
self?.initialValue += 10
return num * (self?.initialValue ?? 2)
}

print("Closure value is: \(closure(10))")

// This will print: "Closure value is: 200" in terminal.

📍 Types of closure

In Swift, there are two types of closures:

  • Escaping closure: This type of closure is used when we intend to utilize it globally, within asynchronous code, or when passing it outside the method scope. To indicate that a closure is escaping, we need to explicitly add the @escaping keyword before the closure type when passing it into a method.
  • Non-Escaping closure: Conversely, a non-escaping closure is intended for use strictly within the method’s scope.

📢 By default, closures in Swift are considered non-escaping. This means they are confined to the method’s scope unless explicitly marked as escaping using the @escaping keyword.

Example 1: If the intention is to store a closure globally, it’s necessary to pass an escaping closure inside the method. Attempting to store a non-escaping closure in a global variable from within a method will result in a compilation error, as non-escaping closures cannot outlive the scope of the method.

The compiler indicates an error, stating that a non-escaping parameter is being assigned to an @escaping closure. Therefore, it’s necessary to add the @escaping keyword, as demonstrated below.

Now the compiler is satisfied with the code, as the @escaping keyword has been appropriately added to resolve the issue.

Example 2: When incorporating closures within asynchronous code, the utilization of escaping closures is imperative. Let’s explore the consequences of neglecting to do so.

Observe the error message. It indicates that an escaping closure should be used instead of a non-escaping one.

Example 3: When attempting to simply return a closure from a method, the compiler will generate an error.

Utilizing an escaping closure can resolve the error, as demonstrated below.

Therefore, it’s crucial to remember that if we intend to use a closure outside the scope of the method, we should utilize an escaping closure. Otherwise, it will default to being a non-escaping closure.

📌 Retain cycle in block/closure

📢 It’s advisable to use a weak reference to self inside a block or closure to prevent strong reference cycles and potential memory leaks.

Utilizing a strong reference inside a block or closure can lead to a retain cycle, wherein two or more objects maintain strong references to each other, preventing deallocation from memory.

Retain cycles can occur in blocks or closures due to the strong reference of self holding onto a class object inside the block. As long as the object remains alive within the block, the block object will also be retained within the class, leading to a retain cycle. To avoid this issue, it’s advisable to use a weak reference to self inside the block.

In scenarios where class objects need to be accessed multiple times within a block or closure, repeatedly using weak references to self may pose a risk of deallocation during execution. To ensure that deallocation occurs only after complete execution, it’s recommended to store self in a strong reference variable inside the block using guard let or if let statements.

Swift:

{ [weak self] in
guard let strongSelf = self else {
return
}

// use strongSelf multiple times
}

Objective-C:

typeof(self) __weak weakSelf = self;

{
typeof(self) __strong strongSelf = weakSelf;

// use strongSelf multiple times
}

📌 Conclusions

  • Blocks and closures encapsulate lines of code and can be stored in variables for later use.
  • Blocks and closures can access variables declared outside of their own scope, including variables from the surrounding context in which they are defined.
  • To modify a variable inside a block, you should declare it using the __block keyword. This allows the variable to be modified within the block’s scope.
  • Indeed, closures allow access to variables declared outside of their own scope, and they can also modify these variables if they are declared with the var keyword or are marked as inout.
  • Closures can be categorized into two types: escaping and non-escaping closures.
  • By default, closures are non-escaping.
  • It is recommended to use a weak reference to self inside a block or closure to prevent the occurrence of retain cycles.

Thank you 🙏 for reading this article. Your feedback and suggestions are welcome in the comment section below. Your support is greatly appreciated. 😊

--

--

Omkar Raut
iOS App Development Concepts

Bachelor of Technology in Computer Engineer, at Dr. Babasaheb Ambedkar Technological University, Lonere, Raigad, India.