Blocks

This article is about OS X Snow Leopard(10.6) and Blocks, the C language extension introduced in iOS 4. Let’s explore it step by step through its implementation.

What is the Blocks

Blocks is an extension of THE C language. The extension of Blocks can be expressed in one sentence: anonymous functions with automatic variables (local variables). (For programmers, naming is the essence of the job.) The following example code explains what anonymity is:

typedef void(^Blk_T)(int);
int i = 10;

// Notice the block definition on the right side of the equals sign (anonymous)
Blk_T a = ^void (int event) {
    printf("i = %d event = %d\n", i, event);
};

// Function definition
void Func(int event) {
    printf("buttonID: %d event = %d\n", i, event);
};
void (*funcPtr)(int) = &Func;
Copy the code

Anonymity is for names; the block definition following the Blk_T a equals sign above does not need a name, whereas the Func function definition below must give it a function name.

The full block syntax differs from the normal C function definition in only two ways:

  1. There is no function name.
  2. with^.

The Block definition paradigm is as follows: ^ Return value type Parameter list expression “return value type” is the same as the return value type of C functions, “parameter list” is the same as the argument list of C functions, and “expression” is the same as the expression allowed in C functions.

Under Block syntax, Block syntax can be assigned to a variable declared as a Block type. The use of Block syntax in source code is equivalent to generating “values” that can be assigned to Block variables. The values generated by Block syntax in Blocks are also called Blocks. Block refers to both the block syntax in the source code and the values generated by the block syntax. Assign a Block to a Block variable using Block syntax:

int (^blk)(int) = ^ (int count) { return count + 1; };

// Returns a function of type Block
// func() and {} describe the return type
void (^ func() ()int) {
    return^ (int count) { return count + 1; }; 
}
Copy the code

Block variables can be used exactly like normal C variables, so Pointers to Block variables can also be used, that is, Pointers to Block variables.

typedef int (^blk_t)(int);
blk_t blk = ^(int count) { return count + 1; };

// Pointer assignment
blk_t* blkPtr = &blk;

/ / execution block
(*blkPrt)(10);
Copy the code

Block variables are exactly the same as normal C variables and can be used for the following purposes:

  • Automatic variables
  • Function parameters
  • A static variable
  • Static global variable
  • The global variable

Calling a Block from a variable of type Block is no different from normal C function calls.

Intercepts external variable values

Blocks are anonymous functions with automatic variables (local variables) that are represented in blocks as intercepting external variable values.

// Example 🌰 1:
int val = 10;
const char* fmt = "val = %d\n";
void (^blk)(void) = ^ {// Block intercepts the address 10 and the FMT pointer point to
    printf(fmt, val);
};

// BLK simply intercepts the transient value of val (10) to initialize the val member of the block structure.
// The value of val, however rewritten, has nothing to do with the value of block
val = 2;

// The BLK block structure only intercepts the char string that the FMT pointer originally pointed to.
Val = %d\n val = %d\n
fmt = "These values were changed. val = %d\n";

blk(a);// Print the result:
// val = 10

// Example 🌰 2:
int temp = 10;
int* val = &temp;
const char* fmt = "val = %d\n";
void (^blk)(void) = ^ {// Block intercepts the address to which the FMT pointer and the address to which the val pointer point
    printf(fmt, *val);
};

// overwrite the address pointed to by val
*val = 20; 
fmt = "These values were changed. val = %d\n";

blk(a);// Print the result:
// val = 20

// Example 🌰 3:
int temp = 10;
int* val = &temp;
const char* fmt = "val = %d\n";
void (^blk)(void) = ^ {// We can change the value of val directly through the pointer
    *val = 22;
    printf(fmt, *val);
};

// Overwrite the value of the address pointed to by val
*val = 20; 

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

blk(a);// Print the result:
// val = 22

// Example 🌰 4:
__block int val = 10;
const char* fmt = "val = %d\n";
void (^blk)(void) = ^ {printf(fmt, val);
};

// When val is modified with __block, the type is no longer an int. It has been converted to a structure type, as detailed below
// Val is converted to a struct instance
val = 2;
fmt = "These values were changed. val = %d\n";

blk(a);// Print the result:
// val = 2

// Example 🌰 5:
// Operate from top to bottom on the contents of the address space of a variable
int a = 10;
__block int* b = &a;
void (^blk)(void) = ^ {NSLog(@"⛈⛈⛈ block internal b before modification: b = %d", *b);
    *b = 20; // External b is also 20 after modification
};

NSLog(@"⛈ ⛈ ⛈ b = % d", *b);
a = 30;
NSLog(@"⛈ ⛈ ⛈ b = % d", *b);

blk(a);NSLog(@"⛈ ⛈ ⛈ b = % d", *b);
NSLog(@"⛈ ⛈ ⛈ a = % d", a);
// Print the result:⛈ ⛈ ⛈ = b10⛈ ⛈ ⛈ = b30⛈⛈⛈ Block internal B before modification: b =30⛈ ⛈ ⛈ = b20⛈ ⛈ ⛈ = a20
Copy the code

No matterblockDefine where and when to do it. whenblockWhen it’s executed, it’s either the value of the base variable that it intercepted at the time it was defined or the memory address that it intercepted, and if it’s a memory address, between the time it was defined and the time it was executed, regardless of whether the value that was stored in it was changed,blockWhen executing, we use the values that are in memory at the time. Definition can be understood as generationblockStructure instance, intercept can be understood as taking external variables to initializeblockStruct instance member variables)

The essence of the block

Blocks are “anonymous functions with automatic variables,” but what exactly is a block? The syntax looks special, but it is actually handled as fairly ordinary C source code. With a block-enabled compiler, source code with block syntax is converted into source code that can be processed by a normal C compiler and compiled as plain C source code. This is nothing more than a conceptual issue and cannot be translated into source code that we can understand at actual compile time, but clang(LLVM compiler) has the capability to translate into source code that we can read. The -rewrite-objc option converts block syntax source code to C++ source code. C++, in fact, is only the use of struct structure, its essence is C language source code.

Clang-rewrite-objc source code file name, the following source code can be converted to clang:

int main(a) {
    void (^blk)(void) = ^ {printf("Block\n"); };
    blk(a);return 0;
}
Copy the code
  • __block_impl
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
Copy the code
  • __main_block_impl_0
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  // Struct constructor
  __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; }};Copy the code
  • __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("Block\n");
}
Copy the code
  • __main_block_desc_0
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0.sizeof(struct __main_block_impl_0)};
Copy the code
  • Inside main
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_24_5w9yv8jx63bgfg69gvgclmm40000gn_T_main_948e6f_mi_0);
        
    Void (* BLK)(void); void (* BLK)(void);
    // Create a cast before &(address)
    // the __main_block_impl_0 struct instance is created on the stack.
    // Convert its address to a function pointer.
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
                                                          &__main_block_desc_0_DATA));
    
    // Execute the FuncPtr function inside __block_impl.
    // The __main_block_func_0 function takes arguments of type struct __main_block_impl_0,
    // But __block_impl * is used for casting,
    Struct __main_block_impl_0 (impl);
    // The type of the address starting space is the same (__cself is not used in this example)
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }

    return 0;
}

Struct __main_block_impl_0 (struct __main_block_impl_0)
// The first argument is a C function pointer converted by Block syntax
// The second argument is the __main_block_desc_0 instance pointer initialized as a static global variable
struct __main__block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;

__main_block_impl_0 struct type automatic variable,
// A pointer to the __main_block_IMPL_0 struct instance generated on the stack, and a variable BLK of type __main_block_IMPL_0 struct pointer is assigned.

void (^blk)(void) = ^ {printf("Block\n"); };// Assign the Block generated by Block syntax to the Block type variable BLK.
// It is equivalent to assigning a pointer to an instance of the __main_block_impl_0 structure to the variable BLK.
// Block is an automatic variable of type __main_block_impl_0 structure,
// The __main_block_IMPL_0 structure instance generated on the stack.

// Execute the Block to remove the conversion part.
(*blk->impl.FuncPtr)(blk); // The __cself argument is an instance of the Block structure
Copy the code

Struct __block_impl the impl in the name of struct __block_impl stands for implementation. In other words, this part is the implementation part of the block structure. Void *isa C void *isa pointer to an indeterminate type and can be used to declare a pointer. Isa is associated with objc_class, so our block is essentially an object, and a class object, and we know that instance objects -> class objects -> metaclasses form one of the ISA chains, The __block_impl structure takes the place of the middle class object, and the instance object should be the generated block variable. Therefore, the ISA pointer points to the metaclass. The metaclass is mainly used to indicate the storage area of the block. When implementing the internal operation of a block, int Reserved indicates the size of the area required for future version upgrades. Reserved is usually filled with a 0. The function that the void *FuncPtr pointer actually executes, which is the code inside the curly braces in the block definition, is converted to a C function.

As the transformed source code shows, anonymous functions used through blocks are actually treated as simple C functions (the __main_block_func_0 function). In addition, clang-transformed functions are named based on the name of the function to which the block syntax belongs (in this case, main) and the order in which the block syntax appears in that function (in this case, 0). The function’s __cself argument is a pointer to an instance of the block structure, the equivalent of the c ++ instance method’s this variable to the instance itself, or the Objective-C instance method’s self variable to the object itself.

Static void __main_block_func_0(struct __main_block_impl_0* __cself) is the same as C++ self for this and OC, The __cself argument is a pointer to the __main_block_IMPL_0 structure.

Isa = &_NSConcretestackBlock specifies the type to which an instance of a block belongs. In order to understand this, we must first understand the nature of OC classes and objects.

// If __main_block_impl_0 is expanded,
Struct __block_impl impl member
// It is almost identical to the OC object
struct __main_block_impl_0 {
void* isa; // isa isa pointer to an instance object of a class
int Flags; // Add a member variable to the class definition
int Reserved;
void* FuncPtr;

struct __main_block_desc_0* Desc;
};

Struct objc_object and struct objc_class are data structures used by instance objects and class objects in OC
typedef struct objc_object* id;
typedef struct objc_class* Class;

// The objC_Object and objC_class structures are ultimately the most basic data structures used in various object and class implementations.
Copy the code

blockIntercepts the essence of external variable values

In the previous section, to see the primitive form of a block without intercepting any variables in the block, let’s look at the result of a block intercepting an external variable. Clang-rewrite-objc converts the following block definition:

int dmy = 256; // This variable is used for comparison, unused variables are not intercepted by blocks
int val = 10;
int* valPtr = &val;
const char* fmt = "val = %d\n";

void (^blk)(void) = ^ {Int * const char * const char * const char * const char * const char * const char * const char *
    printf(fmt, val);
    printf("valPtr = %d\n", *valPtr);
};

// Val is set to 2, and valPtr is set to 2.
// BLK internal call is *valPtr is also 2,
// The initial value is passed to the external valPtr pointer,
// So they both point to the same memory address.

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

blk(a);// Print the result:
val = 10
valPtr = 2
Copy the code

The converted code: the __block_impl structure remains unchanged:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
Copy the code

The __main_block_IMPL_0 member variable has been added, and the external variables used in block syntax expressions (which appear to be the same variable, but actually have the same name) have been appended as member variables to the __main_block_IMPL_0 structure and are of exactly the same type as the external variables. The __main_block_IMPL_0 constructor assigns values to the corresponding contents of the impl. Isa = &_nsConcretestackBlock This concretestackblock domain and the metaclass of the current block. The constructor changes. FMT, val, and valPtr are assigned in the initializer list. This gives us a pretty good idea of how to intercept external variables, and the values used are stored in the block. What looks like an external variable in a block expression is actually a member variable of an instance of the block structure with the same name, so we can’t directly assign to it.

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  __main_block_impl_0 adds its own member variable,
  // And the type of the external automatic variable is exactly the same,
  // The __block variable is converted to a struct.
  const char *fmt;
  int val;
  int *valPtr;
  
  FMT (_fmt), val(_val), valPtr(_val)
  // The constructor instance is initialized with the value of the captured external variable, and the parameter type is exactly the same as the external variable
  __main_block_impl_0(void *fp,
                      struct __main_block_desc_0 *desc,
                      const char *_fmt,
                      int _val,
                      int *_valPtr,
                      int flags=0) : fmt(_fmt), val(_val), valPtr(_valPtr) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

The __main_block_func_0 function also uses the __cself argument:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    The __main_block_impl_0 instance is passed to the function to read the value of the corresponding intercepted external variable
    const char *fmt = __cself->fmt; // bound by copy
    int val = __cself->val; // bound by copy
    int *valPtr = __cself->valPtr; // bound by copy

    printf(fmt, val);
    printf("valPtr = %d\n", *valPtr);
}
Copy the code

__main_block_desc_0 remains unchanged:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0.sizeof(struct __main_block_impl_0)};
Copy the code

Inside main, the __main_block_IMPL_0 constructor instance is built and the __main_block_func_0 function is executed unchanged:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_24_5w9yv8jx63bgfg69gvgclmm40000gn_T_main_4ea116_mi_0);

        int dmy = 256;
        int val = 10;
        int* valPtr = &val;
        const char* fmt = "val = %d\n";
        
        // Initializes the member variables appended by automatic variables in struct __main_block_impl_0 according to the arguments passed to the constructor
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
                                                              &__main_block_desc_0_DATA,
                                                              fmt,
                                                              val,
                                                              valPtr));

        val = 2;
        fmt = "These values were changed. val = %d\n";
        
        // Execute the FuncPtr function in __block_impl with the __main_block_IMPL_0 instance variable BLK
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }

    return 0;
}
Copy the code

In general, “intercepting external variable values” means that when a block syntax is executed, the variables with the same name as the external variables used by the block syntax expression are actually members of the block’s structural instance (i.e., the block itself). The initialized values of these member variables come from the intercepted values of external variables. As mentioned earlier, blocks cannot directly use automatic variables of ARRAY types in C. As mentioned earlier, when intercepting external variables, the value is passed to the constructor of the structure for saving. If a C array is passed, let’s say a[10], The assignment that occurs inside the constructor is int b[10] = a, which is not allowed by the C specification, which the block complies with.

__blockspecifier

A block intercepts the value of an external variable. It intercepts the value of an external variable immediately when the block syntax defines it. After saving the value, it cannot be overwritten. It is a syntax requirement of a block that the value cannot be overwritten. If a pointer variable is intercepted, it can be used to modify the value in memory space. For example, if you pass in an NSMutableArray variable, you can add objects to it, but you can’t assign to that variable. Int *val can also be passed with *val = 20 to change the value in memory that the pointer to val points to. Reading the value outside the block is also synchronized with changes inside the block. Because they operate on the same memory address.

The possible reason why the syntax is immutable is that the value cannot be passed out, but is used inside the block, which makes no sense. For example, if you intercept the variable val in the block definition, you’re using the variable val, and you’re just assigning the value of val to the val member of the block structure. If we change the value of val inside the block, we will change the value of val outside the block. If we intercept the pointer variable, we will intercept the address that the pointer variable points to. Inside a block, changes are made only to the direction of the block structure’s member variables. These changes are irrelevant to external variables.

// Example 🌰 :
int dmy = 256;
int temp = 10;
int* val = &temp;

printf("🎉🎉 val initial value: = %d\n", *val);

const char* fmt = "🎉 Block inside: val = %d\n";
void (^blk)(void) = ^ {printf(fmt, *val);
    int temp2 = 30;
    / /!!!!!!!!!!!!!!!!!!!!!!!! Here is an error
    // Variable is not assignable (missing __block type specifier)
    val = &temp2;
    *val = 22;
};

*val = 20; / / modify val
fmt = "These values were changed. val = %d\n";

blk(a);printf("🎉 🎉 val = % d \ n", *val); // block execution changes *val to 22
// Result:
// 🎉🎉 val Initial value: = 10
// 🎉 Block internal: val = 20
// 🎉🎉 val = 22
Copy the code

When the above cannot be modified (or understood as assigning a value to it), the variable can be modified with a __block specifier, which is called a __block variable.

Note that C arrays cannot be used inside blocks. This is because the current block method does not intercept C arrays. In fact, C specifies that arrays cannot be assigned directly.

const char text[] = "Hello"; 
void (^blk)(void) = ^ {// Cannot refer to declaration with an array type inside block 
  // This is because Blocks currently intercepts external variables without intercepting C arrays.
  Char * is used instead of assigning to arrays
  printf("%c\n", text[0]);
}; 
Copy the code

Recall the previous example of intercepting external variable values:

/ / block definition^ {printf(fmt, val); };

// After conversion:
static void __main_block_func_0(struct __main_block_impl_0* __cself) {
    const char* fmt = __cself->fmt;
    int val = __cself->val;

    printf(fmt, val);
}
Copy the code

After looking at the transformed source code, the intercepted external variables used in the block are described as “anonymous functions with automatic variable values”, only the value of the intercepted external variables. Overwriting the member variable in the struct instance of the block also does not change the previously intercepted external variable. A compilation error occurs when you try to change a member variable with the same name as an external variable inside a block expression. Because it is implementationally impossible to overwrite the value of a intercepted external variable, a compilation error occurs when the compiler detects an assignment to a intercepted external variable during compilation. Theory within the block member variables has been completely without involvement and external variables, block structure member variables is theoretically can change, but change here is only part of the structure itself member variable, and and external completely the same, if changed the internal member variable developers would mistakenly assume that joint external variables change together, It’s better to just have a compilation error! __block variables are created to modify external variables within block expressions.

There are two ways to modify an external variable in a block expression (ignoring the passing of a pointer to change the value of a variable as in many of the above examples) :

  1. CThe language allows variable typesblockRewrite the value:
  • A static variable
  • Static global variable
  • The global variable

While the anonymous function part of block syntax is simply converted to C functions, accessing static global variables from the function of this transformation is unchanged and can be used directly. However, in the case of static local variables, the converted function is set outside of the function with block syntax, so the static local variable cannot be accessed directly from the variable scope. In our clang-rewrite-objc conversion in C++ code we can clearly see that static local variables are defined in the main function, Static void __main_block_func_0(struct __main_block_impl_0 *__cself){… } is a static function defined entirely externally.

Static variable access, outside of scope, should be considered, although the code is written together, but after the transformation is not in the same scope, can be accessed across scope only by Pointers.

Code verification:

int global_val = 1; // Global variables
static int static_global_val = 2; // Static global variables

int main(int argc, const char * argv[]) {
@autoreleasepool {
    // insert code here...
    
    // If the static local variable is a pointer,
    // Then the block structure is converted to a pointer to a pointer,
    // Example: NSMutableArray **static_val;
    
    static int static_val = 3; Static local variables
    
    // The block expression and static_val are in the same scope.
    // They are not scoped at all
    void (^blk)(void) = ^ {// Modify two different types of external variables directly within the block
       global_val *= 2;
       static_global_val *= 2;
       
       // Static variables are modified by Pointers
       static_val *= 3;
    };
    
    static_val = 12;
    blk(a);// static_val = 111;
    printf("static_val = %d, global_val = %d, static_global_val = %d\n", static_val, global_val, static_global_val); }}// Print the result:
// static_val = 36, global_val = 2, static_global_val = 4

// Static val is set to 12 before BLK
// Then BLK will execute static_val = 12 * 3 => static_val = 36
// Static_val can be modified within a block, as can changes outside of static_val
// Pass it inside the BLK
Copy the code

__main_block_impl_0 appends the static_val pointer as a member variable:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  // int *, the initializer passes in a pointer to static_val
  int *static_val;
  
  __main_block_impl_0(void *fp,
                      struct __main_block_desc_0 *desc,
                      int *_static_val,
                      int flags=0) : static_val(_static_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

__main_block_func_0:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

   // Fetch the static_val pointer from the block struct instance
   int *static_val = __cself->static_val; // bound by copy
   
   global_val *= 2;
   static_global_val *= 2;
   (*static_val) *= 3;
}
Copy the code

The main function:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_24_5w9yv8jx63bgfg69gvgclmm40000gn_T_main_54420a_mi_0);
        
        // static_val initializes
        static int static_val = 3;
        
        // see that the _static_val entry is &static_val
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
                                                              &__main_block_desc_0_DATA,
                                                              &static_val));
        
        // This assignment is just an assignment, and can be compared to the __block forwarding pointer
        static_val = 12;
        
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

        printf("static_val = %d, global_val = %d, static_global_val = %d\n", static_val,
                                                                             global_val,
                                                                             static_global_val);
    }

    return 0;
}
Copy the code

You can see that both global_val and static_global_val in __main_block_func_0 have exactly the same access and pre-conversion. The static_val variable is accessed through a pointer, In the initializer list of the __main_block_IMPL_0 constructor, &static_val is assigned to the int *static_val member of the struct __main_block_IMPL_0, In this way, variables are accessed and modified outside the scope of the variable through an address.

This approach to static variables seems to work for external variables as well, but why not?

However, if the block does not hold this variable, as bock did with weak and unsafe_unretained variables, it is possible to store external variables of intercepted objects that exceed their scope. When the scope of the variable ends, the automatic variable will probably be released and destroyed. If the automatic variable is accessed again, the weak variable will be set to nil, while the unsafe_unretained variable will crash directly due to the access of wild pointer. While accessing static local variables will not appear this kind of problem, static variables are stored in a static variable, it is always there, before the end of the program is referred to as local, simply say the scope cannot be directly accessed through the variable name it (compared to a global variable in the module can directly access to any location), is not to say that this piece of data, As long as we have a pointer to the static variable, it can still be accessed out of scope, so external blocks cannot be treated in the same way as static local variables.

Sample code:

// Block does not hold __weak Object
void (^blk)(void);

{
    NSObject *object = [[NSObject alloc] init];
    NSObject * __weak object2 = object;
  
    // object2 is a weak reference to object, so BLK intercepts only a weak reference variable,
  	// Block does not hold the variable, with the following curly braces, object is released and discarded, and object2 is set to nil
    blk = ^{
        NSLog(@"object2 = %@", object2);
    };
}

blk(a);/ / print:
object2 = (null)

// Block holds object
void (^blk)(void);
{
    NSObject *object = [[NSObject alloc] init];

    // Object still exists after braces because it is held by BLK
    blk = ^{
        NSLog(@"object = %@", object);
    };
}
blk(a);/ / print:
object = <NSObject: 0x10059cee0>
Copy the code
  1. The second is to use__blockSpecifier. A more accurate expression would be”__blockStorage domain specifier “(__block storage-class-specifier).

C has the following storage domain class specifiers:

  • typedef
  • extern
  • static
  • auto
  • register

The __block specifiers are similar to static, auto, and Register specifiers and are used to specify which storage domain to set variables to. For example, auto means stored in the stack as a variable, static means stored in the data area as a static variable.

For the use of__blockModify the variable, regardless of theblockIf it is used or not, a struct instance will be generated for it.

Append a __block specifier to the automatic variable declaration of the source code that compiled the error earlier:

int main(int argc, const char* argv[]) {
const char* fmt = "val = %d\n";
__block int val = 10;
void (^blk)(void) = ^{
    val = 20; // Variables can be modified normally here
    printf(fmt, val);
};

val = 30;

blk(a);return 0;
}
Copy the code

According to the clang-rewrite-objc conversion, __block val is converted to struct __Block_byref_val_0 (0 indicates the current number of __block variables). (__Block_byref_val_0 is prefixed by __Block, followed by byref for the variable modified by __Block, val for the original variable name, and 0 for the current __Block variable number)

__Block_byref_val_0

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding; // A pointer to oneself
 int __flags;
 int __size;
 int val;
};
Copy the code

And __Block_byref_val_0 is defined separately so that it can be reused in multiple blocks.

Struct __Block_byref_val_0 (struct __Block_byref_val_0, struct __Block_byref_val_0, struct __Block_byref_val_0) __Block_byref_id_object_copy and __Block_byref_id_object_dispose are used to copy and dispose __block variables into the heap.

__Block_byref_m_Parray_1

struct __Block_byref_m_Parray_1 {
  void *__isa;
__Block_byref_m_Parray_1 *__forwarding;
 int __flags;
 int __size;
 
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 
 NSMutableArray *m_Parray;
};
Copy the code

__block_impl, as a reused construct, remains the same:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
Copy the code

__main_block_impl_0

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  // See two new member variables
  // FMT and val are two external variables intercepted in the block definition
  // FMT is no different from the previous conversion
  const char *fmt;
  
  // val is a __Block_byref_val_0 structure pointer
  __Block_byref_val_0 *val; // by ref
  
  // The first thing you see is the __Block_byref_val_0 * _val argument,
  _val->forwarding pointer
  _val->forwarding
  
  __main_block_impl_0(void *fp,
                      struct __main_block_desc_0 *desc,
                      const char *_fmt,
                      __Block_byref_val_0 *_val,
                      int flags=0) : fmt(_fmt), val(_val->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

__main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

// First fetch val and FMT from the __main_block_IMPL_0 struct instance
__Block_byref_val_0 *val = __cself->val; // bound by ref
const char *fmt = __cself->fmt; // bound by copy

Forwarding-val ->forwarding-val
// Find forwarding, select val and assign 20 to it
(val->__forwarding->val) = 20;

// The __Block_byref_val_0 structure instance is intercepted by val.
// The forwarding pointer is used to assign to it, and the following uses the forwarding pointer.

printf(fmt, (val->__forwarding->val));

}
Copy the code

Assigning to a static variable in a block just uses a pointer to that static variable. Assigning to a __block variable is more complicated, The __main_block_IMPL_0 struct instance holds a pointer to the __Block_byref_val_0 struct instance of the __block variable. The member variable __forwarding of the __Block_byref_val_0 struct instance holds a pointer to the instance itself, and the member variable val is accessed through __forwarding. (The member variable val is a variable held by the instance itself, which is equivalent to the original external variable.)

Looking down at the converted.cpp file, we see two new functions: __main_block_copy_0 and __main_block_dispose_0: (BLOCK_FIELD_IS_BYREF will be explained later), the following pair of copy and dispose functions are generated when the following conditions are found:

  1. When a block intercepts an object type variable (such as NSObject NSMutableArray), it generates the following copy and dispose functions.

  2. When a __block variable is used inside a block (even primitive types such as __block int a = 10), the following copy and dispose functions are generated.

    The pair of copy and Dispose functions are generated when an external object variable and an external __block variable are used within a block expression, as well as an external block.

__main_block_copy_0

// The first argument to the _Block_object_assign function is (void*)& DST ->val. The second argument is (void*) SRC ->val
static void __main_block_copy_0(struct __main_block_impl_0*dst,
                                struct __main_block_impl_0*src) {
  _Block_object_assign((void*)&dst->val,
                       (void*)src->val,
                       8/*BLOCK_FIELD_IS_BYREF*/);
}
Copy the code

__main_block_dispose_0

// Internal _Block_object_dispose (void*) SRC ->val
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
  _Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
Copy the code

__main_block_copy_0 and __main_block_dispose_0 call _Block_object_assign and _Block_object_dispose respectively. And their arguments are all val members of the struct __main_block_IMPL_0 used.

To extend this, when we use __block of an object type, such as __block NSMutableArray *m_Parray, it is converted to the following structure: __Block_byref_m_Parray_1

struct __Block_byref_m_Parray_1 {
  void *__isa;
__Block_byref_m_Parray_1 *__forwarding;
 int __flags;
 int __size;
 
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 
 NSMutableArray *m_Parray;
};
Copy the code

Its internal __Block_byref_id_object_copy and __Block_byref_id_object_dispose dispose uses two global functions when the structure is initialized: __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131.

__attribute__((__blocks__(byref))) __Block_byref_m_Parray_1 m_Parray = {(void*)0,
                                                                        (__Block_byref_m_Parray_1 *)&m_Parray,
                                                                        33554432.sizeof(__Block_byref_m_Parray_1),
                                                                        __Block_byref_id_object_copy_131,
                                                                        __Block_byref_id_object_dispose_131,
                                                                        ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"))};
Copy the code

The definitions of __Block_byref_id_object_copy_131 and __Block_byref_id_object_dispose_131 can be found in a global search in the converted.cpp file:

// The internal call is also _Block_object_assign
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, * (void((* *)char*)src + 40), 131);
}

// Internal _Block_object_dispose is also called
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void((* *)char*)src + 40), 131);
}
Copy the code

Struct __Block_byref_m_Parray_1, struct __Block_byref_m_Parray_1, struct __Block_byref_m_Parray_1 Offset 40 bytes from the start address of the __Block_byref_m_Parray_1 instance to the position of NSMutableArray *m_Parray, and this is either NSMutableArray or any other object type, Both are fixed at 40 bytes, both the __block variable of all object types can be used in the __Block_byref_id_object_copy_131 and __Block_byref_id_object_dispose_131 functions.

__main_block_desc_0 added member variables:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  
  // Copy function pointer
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  // Dispose function pointer
  void (*dispose)(struct __main_block_impl_0*);
  
  // See the following static global variables initialized with the two new functions above
} __main_block_desc_0_DATA = { 0.sizeof(struct __main_block_impl_0),
                               __main_block_copy_0,
                               __main_block_dispose_0};
Copy the code

Inside main:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_24_5w9yv8jx63bgfg69gvgclmm40000gn_T_main_3821a8_mi_0);
        
        // The FMT definition remains unchanged
        const char* fmt = "val = %d\n";
        
        // create an instance of the __Block_byref_val_0 structure from val,
        // Member variables __isa, __forwarding, __flags, __size, val
        
        // convert 0 to a void* pointer
        // __forwarding uses the structure's own address
        // size 就是 sizeof(__Block_byref_val_0)
        // the value of val is the initial value 10
        
        // __block int val = 10; One line is converted to the initialization of the following structure
        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,
                                                                      (__Block_byref_val_0 *)&val,
                                                                      0.sizeof(__Block_byref_val_0),
                                                                      10};
        
        // An instance of the __main_block_IMPL_0 structure as shown earlier
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
                                                              &__main_block_desc_0_DATA,
                                                              fmt,
                                                              (__Block_byref_val_0 *)&val,
                                                              570425344));
                                                              
        __Block_byref_val_0 = val;
        // Not at all like you might think of int val
        (val.__forwarding->val) = 30;
        
        Impl ->FuncPtr (* BLK)
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }

    return 0;
}
Copy the code

The __block variable is converted to an automatic variable of the __Block_byref_val_0 structure type, which is an instance of the struct __Block_byref_val_0 structure generated on the stack.

The __block variable of an object type is a separate case:

// __block NSObject *object = [[NSObject alloc] init];

__attribute__((__blocks__(byref))) __Block_byref_object_4 object = {(void*)0,(__Block_byref_object_4 *)&object, 33554432.sizeof(__Block_byref_object_4), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};

/ / simplified
__Block_byref_object_4 object = {
                                 (void*)0.// isa
                                 (__Block_byref_object_4 *)&object, // __forwarding
                                 33554432.// __flags
                                 sizeof(__Block_byref_object_4), // __size
                                 __Block_byref_id_object_copy_131, // __Block_byref_id_object_copy
                                 __Block_byref_id_object_dispose_131, // __Block_byref_id_object_dispose
                                 
                                 ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)
                                 ((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")) // obj
                                 }
Copy the code

Where __flags = 33554432 = 1 << 25, BLOCK_HAS_COPY_DISPOSE = (1 << 25), struct __Block_byref_object_4 has copy and dispose functions, The structure of a __block variable of the basic type is initialized with a __flags value of 0.

blockStorage domain

Block is converted to an instance of a block structure and a __block variable is converted to an instance of a __block variable structure. Blocks can also be used as OC objects. When a block is considered as an OC object, the block has one of three classes: _NSConcreteStackBlock, _NSConcreteGlobalBlock, and _NSConcreteMallocBlock. Malloc is set in the heap allocated by the malloc function. Malloc is set in the heap allocated by the malloc function. Malloc is set in the heap allocated by the malloc function.

class Sets the storage domain of the object
_NSConcreteStackBlock The stack
_NSConcreteGlobalBlock The data area of the program (.data area)
_NSConcreteMallocBlock The heap

When block syntax is used where global variables are written, the generated block is an _NSConcreteGlobalBlock class object. The specific type of block is not visible from the Clang conversion code, but the actual ISA of a block is determined dynamically by the Runtime.

Block of type _NSConcreteGlobalBlock:

void (^blk)(void) = ^ {printf("Global _NSConcreteGlobalBlock Block! \n"); };

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"🎉 🎉 🎉 Hello, World!");
        
        blk(a);NSLog(@"❄ ️ ❄ ️ ❄ ️ block isa: % @", blk); }}/ / print:_NSConcreteGlobalBlock Block! ❄ ️ ❄ ️ ❄ ️ block isa: < __NSGlobalBlock__ :0x100002068>
Copy the code

This block, the instance of the block structure, is stored in the data area of the program, because automatic variables cannot be used where global variables are used, so there is no interception of automatic variables. Thus, the contents of a block’s struct instance do not depend on the state at execution, so only one instance is needed in the entire program. So just set the block with the struct instance in the same data area as the global variable.

Only when automatic variables are intercepted do the intercepted values of the block struct instance change depending on the state at execution time. Even if you define a block expression inside a function instead of where global variables are written, you can set a block with a struct instance in a program’s data area as long as the block does not intercept automatic variables.

The following blocks are defined inside main, but no external variables are intercepted:

// No external automatic variables are currently captured inside main
void (^globalBlock)(void) = ^ {NSLog(@"❄️❄️❄️ test Block ISA");
};

globalBlock(a);NSLog(@"❄ ️ ❄ ️ ❄ ️ block isa: % @", globalBlock);

// Print the result:❄ ️ ❄ ️ ❄ ️ test block isa ❄ ️ ❄ ️ ❄ ️ block isa: < __NSGlobalBlock__ :0x100002088> / / the global block
Copy the code

For those without automatic variables to be interceptedblockWe don’t need to rely on its runtime state — captured variables so we don’t have toblockcopyThe case is therefore placed in the data area.

Also note that throughclangcompiledisaIn the second case, it showsstackblockThis is becauseOCAs a dynamic language, true metaclasses are determined at run time, in which case they can be usedlldbDebugger view.

Although throughclangThe source code for the transformation is usually_NSConcreteStackBlockClass object, but the implementation is different. The summary is as follows:

  • Where global variables are describedblockWhen the grammar
  • blockSyntax expressions that do not use captured automatic variables

In this case, block is the _NSConcreteGlobalBlock class object, which is configured in the program’s data area. In addition, block syntax generates blocks as _NSConcreteStackBlock objects and sets them on the stack.

// Do not capture the external automatic variable is global
void (^globalBlock)(void) = ^ {NSLog(@"❄️❄️❄️ test Block ISA");
};

int a = 2;
ARC will be copied to the heap, MRC will not be copied
// When the right stack block is assigned to the left block, it is copied to the heap
void (^mallocBlock)(void) = ^ {NSLog(@"❄️❄️❄️ test block ISA a = %d", a);
};

globalBlock(a);mallocBlock(a);NSLog(@"❄ ️ ❄ ️ ❄ ️ globalBlock isa: % @", globalBlock);
NSLog(@"❄ ️ ❄ ️ ❄ ️ mallocBlock isa: % @", mallocBlock);

/ / block stack area
NSLog(@"❄ ️ ❄ ️ ❄ ️ stackBlock isa: % @"The ^ {NSLog(@"❄ ️ ❄ ️ ❄ ️ a = % d", a); });

/ / print:❄️❄️❄️ Tests Block ISA ❄️ service port ❄️ Tests Block ISA A =2❄ ️ ❄ ️ ❄ ️ globalBlock isa: < __NSGlobalBlock__ :0x100002088>
❄️❄️❄️ mallocBlock isa: <__NSMallocBlock__: 0x100540fa0>
❄️❄️❄️ stackBlock isa: <__NSStackBlock__: 0x7ffeefbff4e0>
Copy the code

A block configured in a global variable can also be safely used by Pointers from outside the variable scope, but a block set on the stack is discarded if the scope of the variable to which it belongs ends, since the __Block variable is also configured on the stack. Similarly, if the scope of the variable to which it belongs ends, The __block variable is also discarded.

Blocks provide a way to solve this problem by copying block and __block struct instances from the stack to the heap. Copies blocks configured on the stack to the heap so that blocks on the heap continue to exist even after the variable scope described by the block syntax ends.

  • There won’t be anyblockThe heap is stored in the first place, please remember that!
  • _NSConcreteMallocBlockThe meaning of existence andautoreleaseAgain, to extend the scope of the block.
  • We will beblock__blcokThe struct instance is copied from the stack to the heap and thus counts as on the stackblockIt’s obsolete. You can still use the one on the heap.
  • You can think of us inARCHow is the return value handled__strongStudent: Roughly the same thing.

Here’s a problem to think about: there’s one on the stack and one on the heapblockHow should our assignment, modification, and discard operations be managed?

Block ISA copied to the heap will point to _NSConcreteMallocBlock, that is, impl. Isa = &_NSConcretemalLocblock;

On the stack__blockStruct instance member variable__forwardingPointing to the heap__blockStruct instance, on the heap__blockStruct instance member variable__forwardingPoint to itself, so it doesn’t matter if it’s on the stack__blockVariables are still coming from the heap__blockVariables can all access the same block__blockInstance content.

Code examples:

// This a is an instance of the stack __Block_byref_a_0 structure, which is no longer of type int
__block int a = 2;

// The following blocks are copied to the heap, and a is also copied to the heap
void (^mallocBlock)(void) = ^ {// A's __forwarding on the heap points to itself
    / / a - > __forwarding - > since the gain a
    ++a;
    NSLog(@"❄️❄️❄️ test block ISA a = %d", a);
};

// The following a is still an instance of the __Block_byref_a_0 structure in the stack,
// But its __forwrding pointer points to a above the copied heap,
Int a = 2; int a = 2; int a = 2;
++a;
Copy the code

What exactly does block provide for replication? In fact, in most cases under ARC, the compiler will make the appropriate judgment and automatically generate code that copies blocks from the stack to the heap.

There are two scenarios in which blocks are automatically copied from stack to heap when assigned:

//
// Clang-rewrite-objc can convert successfully
typedef int(^BLK)(int);

BLK func(int rate) {
    // The right stack block is copied to the heap and held by temp
    BLK temp = ^(int count){ return rate * count; };
    return temp;
}

// The following code fails to convert to clang-rewrite-objc, and will succeed:
typedef int(^BLK)(int);

BLK func(int rate) {
    // Return the stack block directly
    return^ (int count){ return rate * count; };
}

// Failed description, the clang conversion failed, but it is normal to execute the function directly
// Clang conversion error description returns a block in the stack,
// When the stack block is out, the curly braces are released, so it cannot return,
// Clang cannot dynamically copy stack blocks to the heap.
// The block to the right of the equals sign has been copied to the heap and assigned to temp.

// When executed correctly, the compiler can dynamically copy blocks from stack to heap.

returning block that lives on the local stack
return^ (int count){ returnrate * count; }; ^ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~64 warnings and 1 error generated.

//
BLK __weak blk;
{
    NSObject *object = [[NSObject alloc] init];
    
    // NSObject * __weak object2 = object;
    
    void (^strongBlk)(void) = ^ {NSLog(@"object = %@", object);
    };
    
    // BLK is a weak reference variable, assigned to it by a strong variable,
    // It does not hold the strong variable
    blk = strongBlk;
}

// blk();
printf("blk = %p\n", blk);

// Print normal, curly braces out, block instance has been released:
blk = 0x0

BLK __weak blk;
{
    NSObject *object = [[NSObject alloc] init];
    // NSObject * __weak object2 = object;
    // void (^strongBlk)(void) = ^{
    // NSLog(@"object = %@", object);
    // };

    // Here is a warning:
    // Assigning block literal to a weak variable; object will be released after assignment
    blk = ^{
        NSLog(@"object = %@", object);
    };
    
    printf("Internal BLK = %p\n", blk);
}

// blk();
printf("blk = %p\n", blk);

// Print the address of the stack block (BLK not 0x0)
// Print a stack block address (i.e. the stack block address on the right)

// The reason for this is that taking an instance of the stack block structure to assign a weak variable is not the real weak processWithin a series =0x7ffeefbff538
blk = 0x7ffeefbff538

Copy the code

Consider the following function that returns a block:

typedef int (^blk_t)(int);
blk_t func(int rate) {
    return^ (int count) {
        return rate * count;
    };
}
Copy the code

The source code is a function that returns a block configured on the stack. That is, the scope of the variable ends when the caller is returned from the function during program execution, so the block on the stack is discarded. Although this appears to be a problem, the source code can be converted to the following by the corresponding ARC compiler:

blk_t func(int rate) {
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);

/ / reference + 1
tmp = objc_retainBlock(tmp);

// Is put into the automatic release pool
return objc_autoreleaseReturnValue(tmp);
}
Copy the code

Also, because ARC is in a valid state, blk_T TMP is actually the same as blk_t __strong TMP with the __strong modifier. Find the objc_retainBlock function in objC4 which is actually the Block_copy function:

// Line 31 in the nsobject. mm file
//
// The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
//

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}

Found in // usr/ include/block. h
// Create a heap based copy of a Block or simply add a reference to an existing one.
// Create heap-based copies of blocks, or just add references to existing blocks. (Copy has been called on blocks on the heap, and the reference count is increased)
// This must be paired with Block_release to recover memory, even when running under Objective-C Garbage Collection.
// If used under OC garbage collection, it must be used with "Block_release".

BLOCK_EXPORT void *_Block_copy(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
Copy the code

That is:

tmp = _Block_copy(tmp);
return objc_autoreleaseReturnValue(tmp);
Copy the code

Analysis:

// In the first step, the __func_block_impl_0 structure instance is generated
// Block to be generated by Block syntax,
// That is, the Block configured on the stack is assigned by the struct instance to the variable TMP of type Block
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);

// In the second step, _Block_copy is executed
The _Block_copy function copies blocks on the stack to the heap.
// After copying, assign the address on the heap as a pointer to the variable TMP.
tmp = _Block_copy(tmp);

// Set the Block on the heap as an OC object,
// Register with Autoreleasepool and return the object
return objc_autoreleaseReturnValue(tmp);
Copy the code

inARCNext,blockWhen returned as a function return value, the compiler automatically generates code copied to the heap.

In most cases the compiler will make the appropriate judgment, but in other cases you need to manually generate code (calling copy yourself) to copy a block from the stack to the heap (_Block_copy creates a heap-based copy of the block, as noted in the comment on the _Block_copy function). That is, we actively call the copy instance method ourselves.

What happens when the compiler can’t tell?

  • To the parameters of a method or functionblockAt the right time. But if you’re in a method or a functionIn theThe parameters passed in are properly copied, so there is no need to manually copy before calling the method or function.

Instead of manually copying the following methods or functions, the compiler automatically copies them:

  • CocoaFramework method and method nameusingBlockWhen etc.
  • Grand Central DispatchAPI
  • willblockAppend to a class__strongThe modifieridType orblockType member variable (of course this case is most, as long as the assignment of a valueblockThe variable is automatically copied.

NSArrayenumerateObjectsUsingBlockAs well asdispatch_asyncFunctions don’t have to be manually copied.NSArrayinitWithObjectsPassed onblockManual replication is required.

Here’s 🌰 :

id obj = [Son getBlockArray];
void (^blk)(void) = [obj objectAtIndex:0];
blk(a);// Copy is called on block
+ (id)getBlockArray {
    int val = 10;
    return [[NSArray alloc] initWithObjects:[^{NSLog(@"blk0: %d", val); } copy], [^{NSLog(@"blk1: %d", val); } copy], nil]; }// If copy is not added, the operation crashes
+ (id)getBlockArray {
    int val = 10;
    return [[NSArray alloc] initWithObjects:^{NSLog(@"blk0: %d", val); ^ {},NSLog(@"blk1: %d", val); }, nil]; }// Cause of crash: block on stack is discarded after getBlockArray is executed when copy is not called.
// The compiler cannot determine whether a copy is needed in this case.
// Copying blocks from stack to heap is cpu-intensive, although it is not necessary to do so in all cases.
// When blocks are available on the stack, copying from the stack to the heap is a waste of CPU resources.
// Copy the file manually.
Copy the code
Block of the class The configuration storage domain of the copy source Print effect
_NSConcreteStackBlock The stack Copy from stack to heap
_NSConcreteGlobalBlock The data area of the program Do nothing
_NSConcreteMallocBlock The heap Reference count increment
No matterBlockWhere is the configuration? UsecopyMethod replication does not cause any problems and is called at uncertain timescopyMethod can.

__blockVariable storage domain

__block variables are also affected when blocks that use them are copied from the stack to the heap.

Configure storage domains for __block variables The effect of a Block being copied from stack to heap
The stack Copied from the stack to the heap and held by the Block
The heap Be Block to retain

If you use a __block variable in a block on a stack, the __block variables used are also configured on the stack. When the block is copied from the stack to the heap, these __block variables are also copied from the stack to the heap. At this point, the block holds the __block variable. Even if the block is copied to the heap, copying the block has no effect on the __block variable used.

use__blockThe variableblockhold__blockThe variable. ifblockTo be abandoned, it holds__blockVariables are also released.

Recall why a __block variable uses the structure member variable __forwarding: the __block variable can be accessed correctly whether it is configured on the stack or on the heap. By copying blocks, __block variables are also copied from the stack to the heap. __block variables on the stack and on the heap can be accessed at the same time.

Sample code:

__block int val = 0;

// Use copy to copy blocks that use __block variables,
// Block and __block variables are both copied from stack to heap,
// Use the initialized __block variable to increment the expression in block syntax

void (^blk)(void) = [^{++val;} copy];

// Use block-independent variables after block syntax,
// val is an instance of the __block structure generated in the first line,
// Val is a member of the block structure.
Val (_val->__forwarding) {}

// And the val found by val inside the block is the same as the val found by the external __block structure instance
Int val = 0; // Int val = 0; // Int val = 0; It is
// an instance of struct __Block_byref_val_0, which is then used when the Block structure is initialized.
// The __forwarding pointer is used subtly to unify the stack and heap __block structures

++val;

// With the clang conversion, we can see that both autoincrement operations are converted to the following form:

// Block expression inside:
// First find the member variable val of the block struct instance
__Block_byref_val_0 *val = __cself->val; // bound by ref
// val is the __Block_byref_val_0 pointer to the structure
++(val->__forwarding->val);

/ / external:
++(val.__forwarding->val);

blk(a);Val.__forwarding->val
NSLog(@"val = %d", val);
Copy the code

In the transformed block syntax functions (__main_block_func_0), val is used for struct instances of __block variables copied to the heap, and val, outside of the block syntax, is used for struct instances of __block variables copied to the stack.

When a __block variable is copied from the stack to the heap, the value of the member variable __forwarding is replaced by the address of the __block variable on the target heap.

At this point, the same __block variable can be accessed either inside or outside the Block syntax, or configured on the stack or heap.

All usevalAre actually translated into:val->__forwarding->val(blockInside) orval.__forwarding->val(Externally, the structure instance can be used directly).

blockHolds the intercepted object

In OC, C constructs cannot contain variables with a __strong modifier, because the compiler does not know when C constructs should be initialized and discarded and does not manage memory well. The OC runtime library, however, knows exactly when blocks are copied from the stack to the heap and when blocks on the heap are discarded, so blocks can be properly initialized and discarded even if they contain variables with __strong or __weak modifiers. To do this, the __main_block_copy_0 and __main_block_dispose_0 functions are placed in copy and dispose, member variables of the __main_block_desc_0 structure.

The __main_block_copy_0 function uses the _Block_object_assign function to assign an object type object to a member variable of the block’s structure and holds the object.

The _Block_object_assign function calls the function equivalent to the retain instance method that assigns the object to a structure member variable of the object type. __main_block_dispose_0 calls _Block_object_dispose, which releases the object assigned to the block structure’s member variable. The _Block_object_dispose function calls the equivalent of the release instance method to free an object assigned to a structure member variable of the object type.

Copy and dispose in __main_block_desc_0 are never used, so when are they?

(These methods are called by the compiler itself; we do not actively invoke them.)

function Call time
The copy function Blocks on the stack are copied to the heap
The dispose function Blocks on the heap are discarded

What happens when blocks on the stack are copied to the heap:

  • callblockcopyInstance method
  • blockWhen returned as a function return value
  • willblockAssign to attach__strongThe modifieridType of a variable orblockType member variable
  • Contains in the method nameusingBlockCocoaFramework approach orGrand Central DispatchAPIIn the transferblock

In these cases, the compiler automatically takes the block of the object as an argument and calls _Block_copy, which has the same effect as manually calling the copy instance method of the block. When a block is passed in usingBlock and GCD, the copy instance method of the block or the _Block_copy function is called on the passed block inside the method or function.

What looks like copy from stack to heap actually boils down to block copy from stack to heap when the _Block_copy function is called.

In contrast, dispose is called when a block copied to the heap is released and no one owns it so it is discarded, which is the dealloc instance method of the object.

With these constructs, objects intercepted in a block can exist out of scope by using automatic variables with a __strong modifier.

The copy and dispose functions are used when using the __block variable:

_Block_object_assign and _Block_object_dispose have different final arguments:

The difference between intercepting objects and using a __block variable:

| | object BLOCK_FIELD_IS_OBJECT | | __block object | BLOCK_FIELD_IS_BYREF |

BLOCK_FIELD_IS_OBJECT and BLOCK_FIELD_IS_BYREF distinguish the object type of copy and Dispose function from that of __block variable.

The copy function holds the intercepted object, and the Dispose function releases the intercepted object.

blockThe assignment used in the__strongModifiers for automatic variables of objects and copies to the heap__blockVariable due to being on the heapblockHolds and can therefore exist outside the scope of its variables.

Block loop reference

If you use an automatic object type variable with the strong modifier in a block, the object is held by the block when the block is copied from the stack to the heap, or otherwise, and is already captured when the block structure initializers.

Sample code:

/ / 🌰 1.
id array = [[NSMutableArray alloc] init];
{
    NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
    
    ^(id obj) {
        [array addObject:obj];
        NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
    };
    
    NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
}
NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);

/ / print:⛈ ⛈ ⛈ array retainCount =1 / / array⛈ ⛈ ⛈ array retainCount =2 // Both array and block are held on the stack⛈ ⛈ ⛈ array retainCount =1 // When the braces are out, the block on the stack is released, leaving only the array held

/ / 🌰 2.
id array = [[NSMutableArray alloc] init];
{
    NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
    
    blk = ^(id obj) {
        [array addObject:obj];
        NSLog(@"⛈⛈⛈ Block array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
    };
    
    NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
}
NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);

if(blk ! = nil) {blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
}
/ / print:⛈ ⛈ ⛈ array retainCount =1 / / array⛈ ⛈ ⛈ array retainCount =3 // In curly braces, blocks on the stack, blocks copied to the heap, and arrays are held, for a total of 3⛈ ⛈ ⛈ array retainCount =2 // The block on the stack is released after the curly braces, and the array is released as well, so it's decreased by 1
⛈⛈⛈  Block array retainCount = 2 // Block prints all 3 times, both array holds and block BLK holds on the heap
⛈⛈⛈  Block array retainCount = 2
⛈⛈⛈  Block array retainCount = 2

/ / 🌰 3.
id array = [[NSMutableArray alloc] init];
{
    NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
    
    blk = ^(id obj) {
        [array addObject:obj];
        NSLog(@"⛈⛈⛈ Block array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
    };
    
    NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
}

NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);

if(blk ! = nil) {blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
}

blk = nil;

NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
/ / print:⛈ ⛈ ⛈ array retainCount =1⛈ ⛈ ⛈ array retainCount =3⛈ ⛈ ⛈ array retainCount =2
⛈⛈⛈  Block array retainCount = 2
⛈⛈⛈  Block array retainCount = 2
⛈⛈⛈  Block array retainCount = 2 // Print exactly the same as 2⛈ ⛈ ⛈ array retainCount =1 // Only here, after BLK is executed three times, BLK is null, BLK is freed, and array is freed, so array is left holding, and retainCount is 1

/ / 🌰 4.
{
    id array = [[NSMutableArray alloc] init];
    NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
    
    blk = ^(id obj) {
        [array addObject:obj];
        NSLog(@"⛈⛈⛈ Block array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
    };
    
    NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);
}

// NSLog(@"⛈⛈⛈ array retainCount = %lu", (unsigned long)[array arcDebugRetainCount]);

if(blk ! = nil) {blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
}
/ / print:⛈ ⛈ ⛈ array retainCount =1 // The object is created with 1⛈ ⛈ ⛈ array retainCount =3 // Block on stack is held and block on heap is held when copied to heap
                             // When the braces are out, blocks on the stack are released, and array local variables are released
                             // The remaining 1 is held by blocks on the heap
                             // So when the following block points to, the print is always 1
⛈⛈⛈  Block array retainCount = 1 // After the braces are out, the array variable is released, leaving the block BLK to hold itself, so print 1
⛈⛈⛈  Block array retainCount = 1
⛈⛈⛈  Block array retainCount = 1
Copy the code
- (id)init { self = [super init]; blk_ = ^{ NSLog(@"self = %@", self); }; return self; } // Self will still be caught. To the compiler, obj_ is just a member variable of the object's structure. // blk_ = ^{ NSLog(@"obj_ = %@", self->obj_); }; - (id)init { self = [super init]; blk_ = ^{ NSLog(@"obj_ = %@", obj_); }; return self; } // __weak self can also be used: id __weak obj = obj_; blk_ = ^{ NSLog(@"obj_ = %@", obj); };Copy the code

In this source code, because when a block exists, the Object that holds the block, self assigned to the variable TMP, must exist, there is no need to determine whether the value of the variable TMP is nil. Across iOS 4 and OS X 10.6, you can replace the __weak modifier with _unsafe_unretained, and don’t worry about dangling Pointers.

Because the block syntax assignment is in the member variable BLk_, the block generated on the stack by block syntax is then copied from the stack to the heap, holding the self used.

When using the __weak modifier to avoid circular references, while it is possible to determine if a variable with a __weak modifier is nil, it is more necessary to make it live in order to use an object with a __weak modifier variable assigned. When a block expression starts executing, it checks if self is nil. If it’s not nil, it’s necessary to continue executing, and it wants self to be there all the time, not to be released while it’s being used, but if it’s a single thread, it doesn’t matter, But be sure to keep this in mind when developing multithreading.

Add __strong to modify weakSelf to get strongSelf in Block to prevent self member variable of Block structure instance from being released prematurely. The difference between objects that a block captures from the outside world and objects that use a strong __strong reference inside a block is that the reference count is affected when the object is defined, and the object is strongly referenced when the block is running and the block expression is still -1.

__weakThe object is modified byblockReference, does not affect the object’s release, while__strongblockInternally decorated objects are guaranteed to be used in the objectscopeInside, this object will not be released, outscope, the reference count will- 1And,__strongMainly used in multi-threaded applications, if only a single thread, only need to use__weakCan.

Use __block variables to avoid circular references by assigning the captured variable nil inside the block, hard breaking the reference ring.

- (id)init {
    self = [super init];
    __block id tmp = self;
    blk_ = ^{
        NSLog(@"self = %@", tmp);
        tmp = nil;
    };
}
Copy the code

The use of__blockVariables avoid methods and uses of circular references__weakThe modifier and__unsafe_unretainedModifier to avoid looping references to methods for comparison:

__block advantages:

  • through__blockVariable controls how long an object is held.
  • Out of use__weakModifier is not used in the environment__unsafe_unretainedModifier (don’t worry about accessing dangling Pointers)
  • In the implementationblockCan dynamically determine whether tonilOr other object assignment in__blockVariable.

__block faults:

  • Must be executed to avoid circular referencesblock.

copy/release

When ARC is invalid, you generally need to manually copy blocks from the stack to the heap, and since ARC is invalid, you definitely need to manually release the copied blocks. The copy instance method is used to copy and the release instance method is used to release.

void (^blk_on_heap)(void) = [blk_on_stack copy];
[blk_on_heap release];
Copy the code

As long as the block has been copied once and configured on the heap, it can be held through the RETAIN instance method.

[blk_on_heap retain];
Copy the code

However, calling the retain instance method for blocks configured on the stack does not work.

[blk_on_stack retain];
Copy the code

In this source code, the retain instance method is called on the block on the stack assigned to blk_on_STACK, but it does nothing for the source code. Therefore, the copy instance method is recommended to hold blocks.

In addition, since block is an extension of C, block syntax can also be used in C. In this case, use the Block_copy function and Block_release function instead of the copy/release instance methods. Use and think about reference counting in the same way as the copy/ Release instance method in OC.

// Copy blocks on the stack to the heap
void (^blk_on_heap)(void) = Block_copy(blk_on_stack);
// Release blocks on the heap
Block_release(blk_on_heap);
Copy the code

The Block_copy function is the previous _Block_copy function used by the OC runtime library for C. Releasing blocks on the heap also calls the OC runtime library Block_release function.

Neither ARC nor MRC stack blocks hold __block objects. An __block specifier is used to avoid circular references in a block when ARC is invalid. This is because when a block is copied from stack to heap, an automatic variable with an ID or object type attached to the __block specifier will not be retained. If a block uses an automatic variable of id type or object type without an __block specifier, it is retained.

Since the purpose of the __block specifier is very different when ARC is in effect and when ARC is not, it is important to know when writing source code whether the source code is compiled with ARC in effect or not.

Due to the length is too long, the next chapter will be analyzed by source code:

  1. ARCThe stack areablockHolds external object variables and external__blockThe variable.
  2. The stack areablockCopying to the heap,__blockThe process by which variables are copied from stack to heap, and the heapblockThe copy operation of the heap area__blockCopy operation of a variable.
  3. globalblockCopy and release.
  4. inblockInternal use__strong.

.