This post was first published on my personal blog “Uninhibited Pavilion”

Article link: bujige.net/blog/iOS-Bl…

Demo address: portal

This article introduces the basic use of Blocks in iOS development. In this article you will learn:

  1. What are Blocks?
  2. Blocks variable syntax
  3. Declaration and assignment of Blocks variables
  4. Blocks variables intercept local variable value properties
  5. Use the __block specifier
  6. Circular references to Blocks variables and how to avoid them

1. What isBlocks ?

In summary: Blocks are anonymous functions (functions without names) with local variables.

Blocks are also called closures, code Blocks. A block is a code block that encapsulates the code you want to execute and calls it when needed.

Let’s first understand the meaning of local variables and anonymous functions.

1.1 Local variables

In C, variables defined inside a function are called local variables. It is only scoped inside the function, and is invalid outside the function. If used again, an error will be reported.

int x, y; // x, y are global variables

int fun(int a) {
    int b, c; //a, b, c are local variables
    return a+b+c;
}

int main(a) {
    int m, n; // m, n are local variables
    return 0;
}
Copy the code

From the above code, we can see:

  1. We defined the variables x and y at the beginning. Both x and y are global variables. By default, their scope is the entire program, that is, all source files, including.c and.h files.
  2. In fun() we define variables A, b, and c. Their scope is fun(). It can only be used inside fun(), and is invalid without fun().
  3. Similarly, the variables m and n in main() can only be used inside main().

1.2 Anonymous Functions

Anonymous functions are functions that do not have a name. But C does not allow such functions.

In C, a normal function looks like this:

int fun(int a);
Copy the code

Fun is the name of this function, which must be called with the name fun.

int result = fun(10);
Copy the code

In C, we can also call a function directly through a function pointer. However, when assigning a value to a function pointer, we also need to know the name of the function.

int (*funPtr)(int) = &fun;
int result = (*funPtr)(10);
Copy the code

And with Blocks, we can just use the function, we don’t have to name the function.


2. Blocks variable syntax

We use the ^ operator to declare the Blocks variable and enclose the Blocks object body in {}. Signifying the end.

Here’s an official example:

int multiplier = 7;
int (^ myBlock)(int) = ^ (int num) {
    return num * multiplier;
};
Copy the code

In this Blocks example, myBlock is the declared block object that returns an integer value. The myBlock object takes one parameter, which is an integer value and whose name is num. The body of myBlock object is return Num * multiplier; , contained in {}.

Referring to the above example, we can express the Blocks expression syntax as:

^ Return value type (argument list) {expression};

For example, we could write Block syntax like this:

^ int (int count) { return count + 1; };
Copy the code

Blocks allows you to omit many items. For example, return value type and parameter list. If you don’t need it, you can omit it.

2.1 Omit return value types:^ (argument list) {expression};

Blocks can be written as:

^ (int count) { return count + 1; };
Copy the code

In this expression, the return statement uses the return type of the count + 1 statement. If there are multiple return statements in an expression, the return value types of all return statements must be the same.

If there is no return statement in the expression, use void, or omit it. The code is as follows:.

^ void (int count)  { printf("%d\n", count); };    // The return value type is void
^ (int count) { printf("%d\n", count); };    // Omit the return value type
Copy the code

## 2.2 Omit argument list ^ Return value type (void) {expression};

If there are no arguments in the expression, use void, or omit void.

^ int (void) { return 1; };    // The argument list uses void
^ int { return 1; };    // Omit the parameter list type
Copy the code

^ {expression};

As you can see from 2.1 above, the return value type can be omitted with or without a return value. Also, as you can see from 2.2, you can omit argument lists if they are not needed. Then the code can be simplified as:

^ { printf("Blocks"); }; 
Copy the code

3. Declaration and assignment of Blocks variables

3.1 Blocks variable declaration and assignment syntax

The syntax for declaring and assigning Blocks variables can be summarized as follows:

Return value type (^ Variable name) (argument list) = Blocks expression

Note: The return value type cannot be omitted. If there is no return value, void is used as the return value type.

For example, define a block variable named BLK:

int (^blk) (int) = ^ (int count) { return count + 1; };
int (^blk1) (int);    // Declare the Blocks variable named blk1
blk1 = blk;        // assign BLK to blk1
Copy the code

The syntax for declaring Blocks variables is a bit complicated, and can be compared to the C language for declaring Pointers to functions.

The declaration of the Blocks variable changes the * variable declaring the function pointer type to ^.

// C function pointer declaration and assignment
int func (int count) {
    return count + 1;
}
int (*funcptr)(int) = &func;

// Blocks variable declaration and assignment
int (^blk) (int) = ^ (int count) { return count + 1; };
Copy the code

3.2 Declaration and assignment of Blocks variables

3.2.1 As local variables:Return value type (^ variable name) (argument list) = Return value type (argument list) {expression};

We can use the Blocks variable as a local variable within a certain range (inside a function or method).

// Blocks variables as local variables
- (void)useBlockAsLocalVariable {
    void (^myLocalBlock)(void) = ^ {NSLog(@"useBlockAsLocalVariable");
    };

    myLocalBlock();
}

Copy the code

3.2.2 As a member variable with property declaration:@property (nonatomic, copy) Returns the value type (^ variable name) (argument list);

Acts like a delegate, implementing the Blocks callback.

/* Blocks variables as member variables with property declarations */
@property (nonatomic.copy) void (^myPropertyBlock) (void);

// The Blocks variable is a member variable with a property declaration
- (void)useBlockAsProperty {
    self.myPropertyBlock = ^{
        NSLog(@"useBlockAsProperty");
    };

    self.myPropertyBlock();
}
Copy the code

3.2.3 As OC method parameters:- (void) someMethodThatTaksesABlock (return value type (^) (parameter list)) variable names;

You can use the Blocks variable as an argument in the OC method, usually at the end of the method name.

// the Blocks variable is used as the OC method argument
- (void)someMethodThatTakesABlock:(void(^) (NSString *)) block {
    block(@"someMethodThatTakesABlock:");
}
Copy the code

3.2.4 Calling the OC method with Block:[someObject someMethodThatTakesABlock: ^ return value type (parameter list)} {expression];

// Call the OC method with Block
- (void)useBlockAsMethodParameter {
    [self someMethodThatTakesABlock:^(NSString *str) {
        NSLog(@ "% @",str);
    }];
}
Copy the code

By calling the Blocks variable as an argument to the OC method in 3.2.3 and 3.2.4, we can also implement a delegate-like function called the Blocks callback (described in the application scenario below).

3.2.5 Declare type as typedef:

Typedef Return value type (^ declaration name)(argument list); Declaration name Variable name = ^ Return value type (argument list) {expression};Copy the code
// Blocks variables declare types as typedef
- (void)useBlockAsATypedef {
    typedef void (^TypeName)(void);
    
    // We can then use TypeName to define blocks with no return type and no argument list.
    TypeName myTypedefBlock = ^{
        NSLog(@"useBlockAsATypedef");
    };

    myTypedefBlock();
}
Copy the code

4. Blocks variables intercept local variable value characteristics

Let’s start with an example.

// Use Blocks to intercept local variable values
- (void)useBlockInterceptLocalVariables {
    int a = 10, b = 20;

    void (^myLocalBlock)(void) = ^{
        printf("a = %d, b = %d\n",a, b);
    };

    myLocalBlock();    A = 10, b = 20

    a = 20;
    b = 30;

    myLocalBlock();    A = 10, b = 20
}
Copy the code

Why do I print a = 10 and B = 20 twice?

MyLocalBlock () is called the first time; MyLocalBlock () = myLocalBlock(); myLocalBlock() = myLocalBlock(); Is still using the value of the corresponding variable?

This is because Block expressions use local variables a and B that it previously declared. In Blocks, the Block expression intercepts the value of the local variable used, storing the transient value of that variable. So the second execution of the Block expression, even if the values of local variables A and B have been changed, does not affect the instantaneous values of local variables held by the Block expression when it is executed.

This is how Blocks variables intercept local variable values.


5. Use the __block specifier

In fact, when using Block expressions, you can only use the instantaneous value of the saved local variable, not overwrite it directly. Modifying the compiler directly results in an error, as shown in the following figure.

So what if we want to write the value of a local variable intercepted in a Block expression?

If we want to overwrite a local variable declared outside the Block expression in a Block expression, we need to prefix the local variable with the __block modifier.

This allows us to assign values to local variables outside of a Block expression.

// Modify local variable values using __block specifiers
- (void)useBlockQualifierChangeLocalVariables {
    __block int a = 10, b = 20;
    void (^myLocalBlock)(void) = ^{
        a = 20;
        b = 30;
        
        printf("a = %d, b = %d\n",a, b);  A = 20, b = 30
    };
    
    myLocalBlock();
}
Copy the code

As you can see, after using the __block specifier, we have successfully modified the local variable value in the Block expression.

If Blocks intercepts an Objective-C object, such as an NSMutablearray class object, calling the changed method on that object will not compile an error (such as calling the addObject: method). However, if an assignment method is called on it, an error is reported and a __block specifier must be added.


6. Circular references to Blocks variables and how to avoid them

We know from above that blocks hold references to local variables. Similarly, if a Block holds the referenced objects, it will hold each other, causing circular references.

/* —————— retaincycleblcok. m —————— */   
#import <Foundation/Foundation.h>
#import "Person.h"
int main() {
    Person *person = [[Person alloc] init];
    person.blk = ^{
        NSLog(@ "% @",person);
    };

    return 0;
}


/* —————— person. h —————— */ 
#import <Foundation/Foundation.h>

typedef void(^myBlock)(void);

@interface Person : NSObject
@property (nonatomic.copy) myBlock blk;
@end


/* —————— person. m —————— */ 
#import "Person.h"

@implementation Person    

@end
Copy the code

The code for the main() function in RetainCycleblcok.m above causes a problem: Person holds the member variable myBlock BLK, and BLK also holds the member variable Person, which references each other and can never be freed. This creates a circular reference problem.

So, how to solve this problem?

In 6.1 ARC, circular references are eliminated by the __weak modifier

Under ARC, variables with an __weak modifier can be declared and objects assigned to use.

int main() {
    Person *person = [[Person alloc] init];
    __weak typeof(person) weakPerson = person;

    person.blk = ^{
        NSLog(@"% @",weakPerson);
    };

    return 0;
}
Copy the code

Thus, with __weak, Person holds the member variable myBlock BLK, and BLK weakly references Person, thus eliminating circular references.

6.2 UNDER MRC, circular references are eliminated with the __block modifier

The __weak modifier is not supported under MRC. But we can eliminate circular references by __block.

int main(a) {
    Person *person = [[Person alloc] init];
    __block typeof(person) blockPerson = person;

    person.blk = ^{
        NSLog(@"% @", blockPerson);
    };

    return 0;
}
Copy the code

A blockPerson referenced by __block accesses a person through a pointer and does not strongly reference a person, so it does not create a circular reference.


The resources

  • Book: “Objective-C Advanced Programming for iOS with OS X Multithreading and Memory Management”
  • How Do I Declare A Block in Objective-C?

The above is iOS development: “Blocks” detailed summary (a) basic use of all the content, can be used to understand the Block, access to use. In the next article, we will talk about the underlying principle of Block through the transformation of OC code into C++ source code.

  • The second link:IOS development: detailed summary of “Blocks” (2) underlying principles