Professional Blocks in Objective-C

joker hook
18 min readFeb 12, 2025

--

Photo by Adi Goldstein on Unsplash

Although most developers have now shifted to using Swift and SwiftUI, Objective-C (OC), as the foundational language of the Apple ecosystem, still holds significant importance in many projects. The deep foundation of OC is widely used in iOS frameworks and numerous existing libraries, and its design principles have had a profound influence on the development of Swift. One particularly crucial feature is Block, which plays an essential role in OC by enabling many functional programming concepts such as closures, asynchronous callbacks, and memory management. Understanding the inner workings of Blocks, especially the memory management and variable capture mechanisms, not only helps us code more efficiently in OC projects but also enhances our understanding and optimization skills in Swift. Therefore, OC and Block remain essential skills that should not be overlooked in modern iOS development.

This article mainly introduces the basic knowledge of Blocks in Objective-C and their underlying principles.

1 Basic Knowledge of Blocks

1.1 Block Declaration Syntax

A Block is an extension of the C language. Compared to a standard C function definition, a complete Block syntax differs in two aspects:

  • It has no function name.
  • It includes the ^ symbol.

The complete syntax for defining a Block is as follows:

/// ^ Return Type Parameter List Expression  
^int (int count) { return count + 1; }

A simplified Block syntax can omit the return type. If there are no input parameters, the parameter list can also be omitted.

/// Omitting the return type  
^(int count) { return count + 1; }

/// Omitting the parameter list
^ { NSLog(@"Blocks!"); }
  • When omitting the return type, if there is a return statement in the expression, the return type is inferred from it. If there is no return statement, the return type defaults to void.
    - If there are multiple return statements, all returned values must have the same type.

1.2 Block Type Variables

Like C functions, Blocks support being assigned to Block type variables. The definition of a Block type variable is as follows:

/// Return type (^Block name)(parameter type, ...);  
int (^blk)(int);

/// Complete assignment statement
int (^blk)(int) = ^int (int count) { return count + 1; };

Using typedef simplifies the type declaration, allowing you to use a simple notation to mark the type when a Block is passed as a parameter:

/// Use `typedef` to simplify the type declaration  
typedef int (^blk_t)(int);

/// Assign to a variable
blk_t blk = ^int (int count) { return count + 1; };

/// Passing as a parameter
- (int)methodWithBlock:(blk_t)blk rate:(int)rate
{
return blk(rate);
}

1.3 Blocks Can Capture Local Variables

Consider the following code:

typedef void (^blk_t)(void);

int main(int argc, const char * argv[]) {

int dym = 256;
int val = 10;
const char *fmt = "val = %d\n";

blk_t blk = ^{
printf(fmt, val);
};

val = 2;
fmt = "These values were changed. val = %d\n";

blk();

return 0;
}

// Result:
// val = 10

The execution result is not the modified “These values were changed. val = 2,” but the value at the moment before the declaration, which indicates that the Block automatically captures the value of local variables.

Directly modifying the value inside the Block will result in a compilation error:

typedef void (^blk_t)(void);

int main(int argc, const char * argv[]) {

int val = 10;

blk_t blk = ^{
val = 2; /// Variable is not assignable (missing __block type specifier)
};

blk();

return 0;
}

To modify the value of a local variable inside a Block, you need to use the __block keyword.

Case 2, consider the following code:

typedef void (^blk_t)(void);

int main(int argc, const char * argv[]) {

NSMutableArray<NSNumber *> *array = [[NSMutableArray alloc] init];

blk_t blk = ^{
NSLog(@"array = %@", array);
};

NSNumber *num1 = [NSNumber numberWithInt:1];
[array addObject:num1];

NSNumber *num2 = [NSNumber numberWithInt:2];
[array addObject:num2];

blk();

return 0;
}

// Result:
// array = (
// 1,
// 2
// )

Note the difference between object types and basic types. In a Block, what is captured is the object of the NSMutableArray class, which is an instance pointer pointing to the address of the data. Since subsequent operations that add data do not modify the `array` object’s address, the array elements can be printed normally.

The following operation attempts to modify the instance pointer of the array, which will cause a compilation error:

typedef void (^blk_t)(void);

int main(int argc, const char * argv[]) {

NSMutableArray<NSNumber *> *array = [[NSMutableArray alloc] init];

blk_t blk = ^{
array = [[NSMutableArray alloc] init]; // <- Variable is not assignable (missing __block type specifier)
...
};

blk();

return 0;
}

In the following operation, data is added to the array inside the Block. Since the instance pointer is not modified, the operation runs normally:

typedef void (^blk_t)(void);

int main(int argc, const char * argv[]) {

NSMutableArray<NSNumber *> *array = [[NSMutableArray alloc] init];

blk_t blk = ^{
NSNumber *num3 = [NSNumber numberWithInt:3];
[array addObject:num3];

NSNumber *num4 = [NSNumber numberWithInt:4];
[array addObject:num4];
NSLog(@"array = %@", array);
};

NSNumber *num1 = [NSNumber numberWithInt:1];
[array addObject:num1];

NSNumber *num2 = [NSNumber numberWithInt:2];
[array addObject:num2];

blk();

return 0;
}

// Result:
// array = (
// 1,
// 2,
// 3,
// 4
// )

1.4 The __block Storage Modifier

If you want to assign a value to an automatically declared variable outside of the Block, you need to add the __block storage modifier to that variable:

typedef void (^blk_t)(void);

int main(int argc, const char * argv[]) {

__block int val = 10;
__block NSMutableArray<NSNumber *> *array = [[NSMutableArray alloc] init];

NSLog(@"Former val = %d", val);
NSLog(@"Former array's address = %p", array);

blk_t blk = ^{
val = 2;
array = [[NSMutableArray alloc] init];
};

blk();

NSLog(@"Now val = %d", val);
NSLog(@"Now array's address = %p", array);

return 0;
}

// Former val = 10
// Former array's address = 0x600001fec450
// Now val = 2
// Now array's address = 0x600001fe0090

2 Underlying Principles of Blocks

2.1 The Essence of Blocks

A Block is essentially an Objective-C object, and it has an isa pointer. This object encapsulates the function call address as well as the function’s environment (such as function parameters, return values, and captured external variables).

Consider the following simple Block:

int main(int argc, const char * argv[]) {

void (^blk_t)(void) = ^{
NSLog(@"这是一个Block哦~");
};

blk_t();

return 0;
}

The following code can be used to convert Objective-C source code into C++ code:

clang -rewrite-objc main.m

Some code involving the runtime library needs to be converted to C++ source code using the following code:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 main.m -o main.cpp

Block Structure

In the above code, the underlying Block is actually a structure called __main_block_impl_0. The structure has further encapsulation, mainly consisting of two member variables: the __block_impl structure and the __main_block_desc_0 pointer.

/// Underlying Data Representation of a Block  
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
/// The Block is stored on the stack and will be destroyed when the function returns
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
/// FuncPtr is a function pointer
/// The underlying implementation wraps the code to be executed in the Block into a function
/// Then it uses this pointer to reference that function.
impl.FuncPtr = fp;
Desc = desc;
}
};

__block_impl Structure

The __block_impl structure is used to provide basic Block runtime information and contains the following properties:

struct __block_impl {
/// Defines the storage method of the Block
void *isa;

/// Identifies the Block type and memory management behavior
int Flags;

int Reserved;

/// Function pointer to the Block's code
void *FuncPtr;
};

The Flags field is used to indicate the Block’s type, memory management behavior, and other related attributes. The specific enumerations are as follows:

#define BLOCK_HAS_COPY_DISPOSE  (1 << 25)  // Block holds __block variables
#define BLOCK_HAS_CTOR (1 << 26) // Block has a constructor (for C++)
#define BLOCK_IS_GLOBAL (1 << 28) // Block is a global Block (won't be copied)
#define BLOCK_HAS_STRET (1 << 29) // Block returns a structure (for x86 architecture)
#define BLOCK_NEEDS_FREE (1 << 30) // Block needs to be manually freed (i.e., a Heap Block)
#define BLOCK_IS_NOESCAPE (1 << 31) // Block will not be copied to the heap

The relationship between Flags and the Block’s storage type

There are three types of Block storage, each corresponding to specific Flags values:

  • Global Block: Stored in the data segment, and it is not copied.
  • Stack Block: Stored on the stack, and it needs to be copied to the heap for long-term use.
  • Heap Block: Stored on the heap, and it needs to be manually released.

If the Flags contain BLOCK_IS_GLOBAL, it indicates that the Block is stored in the data segment; if it contains BLOCK_NEEDS_FREE, it indicates that the Block is stored on the heap.

The relationship between Flags and whether a Block captures variables

When a Block captures a variable modified with __block, the Flags will have the BLOCK_HAS_COPY_DISPOSE set.

The role of the isa pointer

It is important to note that a Block is actually an Objective-C object. Just like other instances and class objects, a Block also has an isa pointer. This isa pointer allows the Block to identify its class, which in turn provides access to the corresponding class methods or instance methods.

  • The isa pointer points to the class object of the Block, distinguishing between stack Blocks (_NSConcreteStackBlock), heap Blocks (_NSConcreteMallocBlock), and global Blocks (_NSConcreteGlobalBlock):
  • The isa pointer allows the Block to be used as an Objective-C object, supporting methods like retain/release, copy, and isKindOfClass:.

Reserved

The Reserved field is a reserved field, typically used for padding to align the memory structure or to provide space for future extensions.

FuncPtr*

FuncPtr is a function pointer and one of the most important fields in the Block structure. It stores the address of the function to execute the Block. In other words, the underlying system wraps the code to be executed in the Block into a function and uses this pointer to reference that function.

__main_block_desc_0 Structure

The __main_block_desc_0 structure is used to describe runtime information about a Block. It contains the following properties:

/// Block descriptor structure
static struct __main_block_desc_0 {
/// Reserved field (typically unused)
size_t reserved;

/// The size of the Block structure
size_t block_size;

/* The following two properties are absent when no variables are captured.
* They help inform the C compiler on how to manage Objective-C object memory at runtime.
*/

/// Block copy function (retain method)
/// Used when copying from the stack to the heap
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);

/// Block dispose function (release method)
/// Used when the heap Block is discarded
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};

The principle of Block function calls

The main function module, after being converted into C++ source code, will look like the following:

/// Block internal execution task
static void __main_block_func_0(
struct __main_block_impl_0 *__cself
) {
// Block task implementation
}

int main(int argc, const char *argv[]) {
/// Create Block instance and cast it to a function pointer type
void (*blk_t)(void) = (void (*)())&__main_block_impl_0(
/// Block execution function
(void *)__main_block_func_0,
/// Block description information
&__main_block_desc_0_DATA
);

/// Access Block's execution function via `FuncPtr` and call it
((void (*)(__block_impl *))((__block_impl *)blk_t)->FuncPtr)((__block_impl *)blk_t);

return 0;
}

The Principle of Block Variable Capture

Before explaining the principle of variable capture, it is necessary to first understand some concepts related to variables in C++ and Objective-C.

Global variables, static local variables, and regular local variables.

In Objective-C (OC) and C, the scope and storage method of variables determine their lifecycle and how they are accessed. There are three main types of variables:

1. Global Variables
2. Static Local Variables
3. Local Variables

Global Variables

Global variables have the following characteristics:

  • Defined outside functions/classes, they exist for the entire duration of the program.
  • Stored in the static storage area (Data Segment), they are not released until the program ends.
  • Accessible from multiple files (using the extern keyword).
  • Default initialized to 0 if no initial value is assigned.
  • Can be accessed by all functions (unless access is restricted with static).
#import <Foundation/Foundation.h>

/// Global variable
int globalVar = 10;

void testFunction() {
NSLog(@"Global Variable: %d", globalVar);
}

int main() {
/// Output: Global Variable: 10
testFunction();
return 0;
}

// file2.c
extern int globalVar;
printf("%d", globalVar);

Static Local Variables

Static local variables have the following characteristics:

  • Modified with the static keyword.
  • Only visible within the current function, but their value persists between function calls.
  • Stored in the static storage area (Data Segment), but their scope is limited to the function in which they are defined.
  • Default initialized to 0 (if no initial value is provided).
void testStatic() {
static int counter = 0;
counter++;
NSLog(@"Static Counter: %d", counter);
}

int main() {
testStatic(); // Static Counter: 1
testStatic(); // Static Counter: 2
return 0;
}

Local Variables

Regular local variables have the following characteristics:

  • Defined inside a function, and visible only within the scope of that function.
  • Stored on the stack, and destroyed after the function call ends.
  • Must be manually initialized, otherwise their value is undefined (random garbage value).
void func() {
int localVar = 10;
printf("%d\n", localVar); // 10
}

int main() {
func();
// printf("%d\n", localVar); // ❌
return 0;
}

Static Global Variables

A static global variable is a global variable defined within the file scope and modified with the static keyword. Its scope is limited to the current file, meaning it cannot be accessed by other files.

// file1.c
int globalVar = 100; // Regular global variable
static int staticVar = 200; // Static global variable

void printVar() {
printf("globalVar = %d, staticVar = %d\n", globalVar, staticVar);
}

// file2.c
extern int globalVar; // ✅ Can access the regular global variable from file1.c
extern int staticVar; // ❌ Error! Cannot access the static global variable

void test() {
printf("globalVar = %d\n", globalVar); // ✅ Works fine
printf("staticVar = %d\n", staticVar); // ❌ Error: Cannot access static global variable
}

Capture of global variables

Consider the following simple code:

#import <Foundation/Foundation.h>

int age = 10;

int main(int argc, const char * argv[]) {

void (^blk_t)(void) = ^{
NSLog(@"age的值为%d", age);
};

blk_t();

return 0;
}

The simplified code obtained after converting the above code to C++ source code is as follows:

int age = 10;

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(
void *fp,
struct __main_block_desc_0 *desc,
int flags=0
) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(
struct __main_block_impl_0 *__cself
) { ... }

static struct __main_block_desc_0 { ... } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(int argc, const char * argv[]) {
void (*blk_t)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk_t)->FuncPtr)((__block_impl *)blk_t);
return 0;
}

From the above code, it can be seen that Block does not capture global variables. In fact, neither regular global variables nor static global variables are captured by a block. Since global variables can be accessed anywhere, there is no need for the block to capture them. Therefore, if the global variable’s value is changed externally, the block will access the most up-to-date value within its body.

Capture of static local variables

Consider the following simple code:

int main(int argc, const char * argv[]) {

static int age = 10;

void (^blk_t)(void) = ^{
NSLog(@"age的值为%d", age);
};

blk_t();

return 0;
}

The simplified code obtained after converting the above code to C++ source code is as follows:

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *age;
__main_block_impl_0(
void *fp,
struct __main_block_desc_0 *desc,
int *_age,
int flags=0
) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

The static local variable age is captured by the Block, and it is stored in the Block structure as int *age;. This means that the Block actually captures the address of the variable age. Inside the Block, the value of age is accessed or modified through its address. Therefore, if the value of age is changed outside the Block, it will affect the value of age inside the Block. Similarly, if the value of age is changed inside the Block, it will also affect the value of age outside the Block.

The reason for storing the address of a static local variable is that this local variable exists throughout the entire program cycle, but it cannot be accessed outside the function.

The capturing of ordinary local variables

int main(int argc, const char * argv[]) {

int age = 10;

void (^blk_t)(void) = ^{
NSLog(@"age的值为%d", age);
};

blk_t();

return 0;
}

The simplified code obtained after converting the above code to C++ source code is as follows:

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
/// here
int age;
__main_block_impl_0(
void *fp,
struct __main_block_desc_0 *desc,
int _age,
int flags=0
) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

It is observed that the ordinary local variable age is captured by the Block and stored within the underlying structure of the Block as int age;. This indicates that the Block essentially captures the value of age, and within the Block, a new variable is defined to store this value. At this point, the age outside and the age inside the Block are effectively two distinct variables. Therefore, any changes made to ‘age’ outside will not affect the ‘age’ inside the block.

The capture of “object-type local variables” by blocks

int main(int argc, const char * argv[]) {

__strong NSString *str = @"OMG";
__weak NSString *weakStr = str;

void (^blk_t)(void) = ^{
NSLog(@"str的值为%@", str);
NSLog(@"weakStr的值为%@", weakStr);
};

blk_t();

return 0;
}

The simplified code obtained after converting the above code to C++ source code is as follows:

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSString *__strong str;
NSString *__weak weakStr;
__main_block_impl_0(
void *fp,
struct __main_block_desc_0 *desc,
NSString *__strong _str,
NSString *__weak _weakStr,
int flags=0
) : str(_str), weakStr(_weakStr) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

It is observed that the storage method of Objective-C objects is essentially through pointers, and the original reference relationship is preserved. This explains why the array in section 1.3.2 can be modified within a Block.

  • If the keyword is __strong, the Block will perform a retain operation on the object, increasing its reference count by 1, which means the Block will hold a strong reference to the object. This is also the reason why using Blocks can easily lead to circular references.
  • If the keyword is __weak or __unsafe_unretained, the Block will hold a weak reference to the object, thus avoiding circular references. Therefore, it is common practice to define a weak pointer outside the Block using __weak or __unsafe_unretained to point to the object, and then use this weak pointer inside the Block to resolve circular reference issues.

The capture of ‘self’ by a Block

In Objective-C, self is a (hidden) local variable that points to the current object. Its role and lifecycle are similar to those of ordinary local variables, but it possesses certain particularities. Essentially, self is an implicit parameter of methods. Whenever an instance method is called, the compiler automatically passes self to the method, ensuring that it points to the object that invoked the method.

Therefore, when self is used within a Block, the Block will indeed capture self. In this scenario, the capture is done by a strong reference, which necessitates careful consideration to avoid potential circular references.

Circular reference between Block and self

Circular references typically occur when an object’s Block property holds a reference to self, and self in turn holds a reference to that Block.

@interface MyClass : NSObject
@property (nonatomic, copy) void (^myBlock)(void);
- (void)startTask;
@end

@implementation MyClass
- (void)startTask {
self.myBlock = ^{
NSLog(@"self = %@", self);
};
}
@end

Solution

- (void)startTask {
__weak typeof(self) weakSelf = self;
self.myBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
NSLog(@"self = %@", strongSelf);
};
}

In the aforementioned code, weakSelf does not increment the reference count of self, thereby avoiding circular references. strongSelf holds a reference to self only during the execution of the Block, preventing crashes that could occur if weakSelf were to become nil. The line if (!strongSelf) return; ensures that if self is deallocated, no further code is executed, thus guarding against EXC_BAD_ACCESS errors.

Summary of Variable Capture in Blocks

  • (Ordinary/Static) Global Variables: Not captured; accessed directly.
  • Static Local Variables: The address of the variable is captured.
  • Ordinary Local Variables: The value of the variable is captured. self also falls under this category as an ordinary local variable.
  • Ordinary Local Object Variables: The address of the object is captured.

Block Classes

Printing the following three forms of Blocks will yield different Block classes:

int main(int argc, const char * argv[]) {

int age = 10;

void (^block1)(void) = ^{
NSLog(@"这个是block1");
};
NSLog(@"block1的类:%@",[block1 class]);

NSLog(@"block2的类:%@",[^{
NSLog(@"这个是block2,其中age=%d",age);
} class]);

NSLog(@"block3的类:%@",[[^{
NSLog(@"这个是block3,其中age=%d",age);
} copy] class]);

return 0;
}

/// Result
/// block1的类:__NSGlobalBlock__
/// block2的类:__NSStackBlock__
/// block3的类:__NSMallocBlock__

__NSGlobalBlock__

Defined directly in the global scope, or a Block that does not capture external variables.
Characteristics:

  • Stored in the global data area (not copied to the heap or stack).
  • Does not invoke copy to enter the heap, and retain/release does not change its address.
  • Invoking the copy method still results in __NSGlobalBlock__.
  • block1 does not capture any external variables, hence it belongs to __NSGlobalBlock__.
  • The inheritance chain of __NSGlobalBlock__ is: __NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject.

__NSStackBlock__

Defined within a function scope and captures external variables (but not copyed).
Characteristics:

  • By default, stored on the stack; once the function returns, the Block becomes invalid.
  • Needs to be copy ed to the heap for long-term use, otherwise it will access illegal memory.
  • The inheritance chain of __NSStackBlock__ is: __NSStackBlock__ : __NSStackBlock : NSBlock : NSObject.

__NSMallocBlock__

Invoking the copy method on a block of type __NSStackBlock__ will copy the block from the stack to the heap. The type of the block on the heap is __NSMallocBlock__, so blocks of type __NSMallocBlock__ are stored in the heap area. If a copy operation is performed on a block of type __NSMallocBlock__, the reference count of this block is incremented by 1.

The inheritance chain of __NSMallocBlock__ is: __NSMallocBlock__ : __NSMallocBlock : NSBlock : NSObject.

Under ARC (Automatic Reference Counting) environment, the compiler will automatically copy blocks from the stack to the heap based on the situation. In fact, whenever a Block may exceed its scope, it is copyed to the heap:

  • When a stack block is returned as a function return value (the stack block will be destroyed after returning);
  • When assigning a stack block to a strong pointer;
  • When a Block is used as a property of an Objective-C object, it is usually declared as copy;
  • When a stack block is used as a parameter in GCD APIs;
  • When passing a Block to Cocoa framework methods whose names contain usingBlock;

Why does `clang -rewrite-objc` generate `_NSConcreteStackBlock`?

In the example above, even the Block that does not access any local variables is initialized as _NSConcreteStackBlock after code transformation. The reasons are as follows:

  • Default Setting: clang by default does not check whether the Block is global. Instead, it first creates it as a stack Block (_NSConcreteStackBlock).
  • Runtime Check: If the Block needs to live longer, the compiler will perform a copy at runtime, transforming it into a _NSConcreteMallocBlock (heap Block). Global Blocks are optimized at runtime by Block_copy, converting them to __NSGlobalBlock__.

2.4 The principle of the `__block` modifier

int main(int argc, const char * argv[]) {

__block int age = 10;

void (^blk_t)(void) = ^{
age = 6;
NSLog(@"age的值为%d", age);
};

blk_t();
NSLog(@"age的值为%d", age);
return 0;
}

The block structure in the form of the converted C++ code is as follows:

/// After being modified by `__block`, age is wrapped into an object.
/// Whether accessed inside or outside the block, age is always accessed through this object.
struct __Block_byref_age_0 {
void *__isa;
/// If the block is on the heap, this pointer points to itself.
/// If the block is on the stack, this pointer points to the block after it is copied to the heap.
__Block_byref_age_0 *__forwarding;
int __flags;
/// Size of the structure
int __size;
int age;
};

/// Block structure
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(
void *fp,
struct __main_block_desc_0 *desc,
__Block_byref_age_0 *_age,
int flags=0
) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

The method to modify the value of age inside the block is defined as follows:

static void __main_block_func_0(
struct __main_block_impl_0 *__cself
) {
/// Access the age structure inside the block
__Block_byref_age_0 *age = __cself->age;
/// Modify the value of the age variable stored in the structure
(age->__forwarding->age) = 6;
/// NSLog method...
}

The age in the main function is rewritten as follows:

__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};

Subsequently, whether age is accessed inside or outside the block, it is accessed through this structure. For example, in the last NSLog method, the way to access age is as follows:

NSLog(
(NSString *)&__NSConstantStringImpl__var_folders_ck_1_gw06bs0pz2j64qt_vk7v180000gn_T_main_2409b9_mi_1,
(age.__forwarding->age)
);

__block Variable Memory Management

  • When a block is on the stack, the block does not strongly retain the __Block_byref_age_0.
  • When the block calls the copy function to move from the stack to the heap, it also copies the __Block_byref_age_0 to the heap and strongly retains it.
    - When multiple blocks use the __block object, and one block (Block0) is copied from the stack to the heap, the __block object is also copied and retained by Block0.
    - When other blocks are copied from the stack to the heap, the copied blocks will also retain the __block object, incrementing its reference count.
  • When the block is removed from the heap, it will call the block’s dispose function. The dispose function will in turn call the _Block_object_dispose function to release the __Block_byref_age_0.

The Role of the __forwarding Pointer

int main(int argc, const char * argv[]) {

__block int age = 10;

void (^blk_t)(void) = [^{
age++;
NSLog(@"age的值为%d", age);
} copy];

blk_t();

age++;
NSLog(@"age的值为%d", age);

return 0;
}

The above code uses the copy method to copy the block to the heap, along with the __block variable age. The code modifies the value of age in two places. The C++ source code for these two modifications is as follows:

// Declaration of age structure:
__attribute__((__blocks__(byref)))
__Block_byref_age_0 age = {
(void*)0, // isa pointer
(__Block_byref_age_0 *)&age, // forwarding pointer
0, // flags
sizeof(__Block_byref_age_0), // size of the structure
10 // initial age value
};

// Increment the value of age
(age.__forwarding->age)++;

In the actual execution, the age within the block uses the __block variable on the heap, while the age outside the block is the __block variable before it is copied.

To ensure that both modify the same value, the __forwarding pointer is used. The content that __forwarding points to is related to the block’s storage location. On the heap, __forwarding points to the __block object itself, while on the stack, __forwarding points to the __block object on the heap. Through this pointer, it ensures that no matter where the __block object is accessed, the same __block object can be accessed successfully.

If you think this article is helpful, you can support me by downloading my first iOS App, WeTally, on the iOS App Store. WeTally is a paramount, exquisite and practical app, allowing you to grasp all financial information in an instant, with a soothing and pleasant use experience, and easily accomplish every accounting.. It is free and useful for many people. You can ask me for free one-month access to advanced features. Hope you like it.

--

--

joker hook
joker hook

Written by joker hook

👨‍🎓/study communication engineering🛠/love iOS development💻/🐶🌤🍽🏸🏫

No responses yet