Introduction to the

Blocks are a language feature in C, Objective-C, and C++ that allows us to create independent code segments. For these snippets, we can pass them between methods as if they were values.

This article focuses on the basic syntax of Blocks and how to use Blocks. See Blocks Programming Topics for more details.

The basic syntax of blocks

The definition of the Block

A Block with no parameters and no return value

We can define a Block using the ^ symbol:

    ^{
         NSLog(@"This is a block");
    }
Copy the code

We use {} to limit the scope of blocks, much like the definition of functions and methods.

Similar to using a C function pointer, we can declare a variable to store blocks:

void (^simpleBlock)(void);
Copy the code

If you’re not familiar with C function Pointers, this syntax might seem a little unusual. The above example declares a variable called simpleBlock that points to a Block with no arguments and no return value. We can assign a Block variable like this:

    simpleBlock = ^{
        NSLog(@"This is a block");
    };
Copy the code

It is important to note that Block assignments, like any other type of variable assignment, need to start with; At the end.

We can also assign a value to a variable while declaring it:

    void (^simpleBlock)(void) = ^{
        NSLog(@"This is a block");
    };
Copy the code

We can then call the Block like this:

    simpleBlock();
Copy the code

Note: If we call a Block variable that is not assigned, the App will Crash.

A Block with arguments and return values

Like functions and methods, blocks can take arguments or have return values.

Suppose we need to declare a Block that multiplies two doubles and returns the result:

    double (^multiplyTwoValues)(double, double);
Copy the code

The corresponding Block implementation is as follows:

    ^ (double firstValue, double secondValue) {
        return firstValue * secondValue;
    }
Copy the code

In this case, the return value can be inferred from the return expression. Of course we could declare the return value type explicitly like this:

    ^ double (double firstValue, double secondValue) {
        return firstValue * secondValue;
    }
Copy the code

After we declare and define a Block, we can call a Block as if it were a function:

double (^multiplyTwoValues)(double, double) = ^(double firstValue, double secondValue) { return firstValue * secondValue; }; Double the result = multiplyTwoValues (2, 4); NSLog(@"The result is %f", result);Copy the code

Block captures context information

Blocks not only contain executable code snippets, they also have the ability to capture context information from adjacent scopes.

If we define a Block within a method, the Block can capture context information within the scope of the method:

- (void)testMethod {
    int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };
 
    testBlock();
}
Copy the code

In this example, anInteger is declared outside the block, but its value is “caught” by the block when it is defined.

Only values are captured here, when we change the value of anInteger between the definition of the block and the call to the block:

    int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };
 
    anInteger = 84;
 
    testBlock();
Copy the code

The output of testBlock() is not affected by the new value of anInteger because we only captured the value, so the output looks like this:

    Integer is: 42
Copy the code

This also means that a Block cannot change the original value of the captured variable (or even the captured value – since the captured value is captured as a const variable). .

The use of the __block

If we need to change the value of an externally captured variable from within a block, we can use the __block store type modifier in the external variable declaration to be modified. The storage space of a variable that uses the __block modifier is shared by its scope and the scope of the block that references it:

    __block int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };
 
    anInteger = 84;
 
    testBlock();
Copy the code

Because anInteger is decorated with a __block, the storage of anInteger is shared with the declaration of the block. So the output is as follows:

    Integer is: 84
Copy the code

This also means that the original value can be modified in the Block:

    __block int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
        anInteger = 100;
    };
 
    testBlock();
    NSLog(@"Value of original variable is now: %i", anInteger);
Copy the code

The output is as follows:

    Integer is: 42
    Value of original variable is now: 100
Copy the code

Pass blocks as function/method arguments

In the previous example, blocks are called immediately after they are defined, but in practice, blocks are often passed in functions or methods and called elsewhere.

For example, we are using the GCD(Grand Central Dispatch) to call a block in a child thread, or defining a block to call it repeatedly while iterating through the collection. Both scenarios will be described in more detail later.

Blocks can also be used as callbacks, defining a piece of code to execute when a task is complete. For example, the App may request information from the Web Service. As the request may take a long time, Loading should be added during the request and hidden at the end of the request. This requirement can of course be fulfilled by proxy: create an appropriate proxy protocol, implement the corresponding method, set up the task proxy object, and invoke the proxy method when the request completes. If we use blocks, the same process is much simpler because we can define the callback behavior at request time:

- (IBAction)fetchRemoteInformation:(id)sender {
    [self showProgressIndicator];
 
    XYZWebTask *task = ...
 
    [task beginTaskWithCallbackBlock:^{
        [self hideProgressIndicator];
    }];
}
Copy the code

Note: This callback Block captures self for calling the hideProgressIndicator method. When capturing self, we need to be careful because it’s easy to throw a circular reference. We will describe this in more detail later.

From a readability point of view, a block keeps the code at the time of the task in the same place as the code at the time of the task’s completion, whereas with a proxy we have to trace the proxy method to determine the exact invocation of the method.

The above beginTaskWithCallbackBlock: it’s such a statement:

- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock;
Copy the code

(void(^)void) specifies the type of the block argument — no arguments, no return value. The implementation of this method is similar to calling a block:

- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock {
    ...
    callbackBlock();
}
Copy the code

Block with parameters as method arguments:

- (void)doSomethingWithBlock:(void (^)(double, double))block { ... Block (21.0, 2.0); }Copy the code

It is best to have only one Block argument in a method. If other non-block arguments are needed in the method, the Block argument should be placed at the end of the argument list:

- (void)beginTaskWithName:(NSString *)name completion:(void(^)(void))callback;
Copy the code

This makes the method more readable when inlining Block code:

    [self beginTaskWithName:@"MyTask" completion:^{
        NSLog(@"The task is complete");
    }];
Copy the code

When we need to define multiple blocks that have the same signature (same argument list + return value type), we can define a type for the signature:

    typedef void (^XYZSimpleBlock)(void);
Copy the code

A Block type XYZSimpleBlock with no parameters and no return value is defined.

We can use it as a method parameter type or to create Block variables:

    XYZSimpleBlock anotherBlock = ^{
        ...
    };
Copy the code
- (void)beginFetchWithCallbackBlock:(XYZSimpleBlock)callbackBlock {
    ...
    callbackBlock();
}
Copy the code

Custom Block types are useful when dealing with blocks that return a Block type or that take a Block as an argument, such as:

void (^(^complexBlock)(void (^)(void)))(void) = ^ (void (^aBlock)(void)) {
    ...
    return ^{
        ...
    };
};
Copy the code

A complexBlock refers to aBlock that takes aBlock as an argument (aBlock) and returns aBlock. Why not void(^)(void)(complexBlock)(void(^)(void)). Refer to StackOverFlow’s answer, which describes how the C interpreter interprets this syntax step by step. Rewrite this code using custom Block types:

XYZSimpleBlock (^betterBlock)(XYZSimpleBlock) = ^ (XYZSimpleBlock aBlock) {
    ...
    return ^{
        ...
    };
};
Copy the code

Readability is instant!

Treat a Block as an object property

The following code defines a Block property in an object:

@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end
Copy the code

Note: A copyattribute should be specified for a Block, because a Block needs to be copied to store its context information outside the original scope. When we use ARC(Automatic Reference Counting), we don’t need to worry about these. These are used by default, but are best expressed explicitly in the attribute. For more information see: Blocks Programming Topics.

Block attributes are set and called like Block variables:

    self.blockProperty = ^{
        ...
    };
    self.blockProperty();
Copy the code

You can also use type definitions for attributes:

typedef void (^XYZSimpleBlock)(void);
 
@interface XYZObject : NSObject
@property (copy) XYZSimpleBlock blockProperty;
@end
Copy the code

Avoid circular references in blocks

When we need to capture self in a Block, such as when we define a callback Block, it is important to consider memory management.

Block maintains strong references to all objects it captures, including self. This makes it easy to create strong reference loops. Let’s say we have an object that has a block property decorated with copy, and the block captures self:

@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
Copy the code
@implementation XYZBlockKeeper - (void)configureBlock { self.block = ^{ [self doSomething]; // capturing a strong reference to self // creates a strong reference cycle }; }... @endCopy the code

The compiler warns against this kind of simple circular reference, but for more complex examples, it is difficult to locate the problem.

To avoid this problem, it is best to add a weak reference to the captured self:

- (void)configureBlock {
    XYZBlockKeeper * __weak weakSelf = self;
    self.block = ^{
        [weakSelf doSomething];   // capture the weak reference
                                  // to avoid the reference cycle
    }
}
Copy the code

In this way, the Block will not forcibly reference the XYZBlockKeeper object, and weakSelf will be set to nil if the object is destroyed before the Block is called.

The application of the Block

Use blocks to simplify traversal

In addition to callbacks, many Cocoa and Cocoa Touch apis use blocks to simplify tasks such as traversal of collections. NSArray provides several methods related to blocks:

- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
Copy the code

This method takes a single Block argument, which is called on each iteration:

    NSArray *array = ...
    [array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"Object at index %lu is %@", idx, obj);
    }];
Copy the code

The Block itself takes three arguments, the first two representing the object being traversed and the object’s index in the array, and the third argument a BOOL pointer that can be used to stop traversal:

    [array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
        if (...) {
            *stop = YES;
        }
    }];
Copy the code

We can also through enumerateObjectsWithOptions: usingBlock: custom traversal method way, such as a specified NSEnumerationReverse, can reverse traversal of the array.

If the code in traversing a Block is computationally intensive and safe for concurrent execution, the NSEnumerationConcurrent option can be used to execute the Block in multiple threads to improve performance:

    [array enumerateObjectsWithOptions:NSEnumerationConcurrent
                            usingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
        ...
    }];
Copy the code

Note: The traversal order is uncertain when using this option.

The NSDictionary class also provides block-related traversal methods:

    NSDictionary *dictionary = ...
    [dictionary enumerateKeysAndObjectsUsingBlock:^ (id key, id obj, BOOL *stop) {
        NSLog(@"key: %@, value: %@", key, obj);
    }];
Copy the code

Compared with the traditional traversal method, this method is more convenient.

Use blocks to simplify concurrent tasks

A Block represents a single task — it contains a piece of task code and state information captured from the context. This makes it an ideal tool for OS X/iOS asynchronous programming. Using blocks to define tasks and let the system handle them eliminates the need for underlying system mechanisms such as threads.

OS X and iOS offer a wide variety of asynchronous technologies, including two task scheduling mechanisms — Operation Queues and Grand Central Dispatches. These mechanisms are implemented around tasks and task queues. We can add task blocks to queues and the system will execute tasks when processor time and resources are available.

Serial queues allow only one task to be executed at a time, and the next task will not start until the previous one is complete. Concurrent queues execute as many tasks simultaneously as possible without waiting for previous tasks to complete.

Use Block Operation in Operation Queue

We can do this by creating the NSOperationQueue queue and adding the NSOperation task instance to it.

We can create custom NSOperation subclasses and implement complex task functionality ourselves, or we can create Block tasks directly using NSBlockOperation:

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    ...
}];
Copy the code

Although we can execute the Operation object manually, the more common scenario is to add it to the OperationQueue and wait for it to execute:

// schedule task on main queue:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];
 
// schedule task on background queue:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
Copy the code

When using Operation Queue, we can set the priority of operations and the dependencies between operations, such as specifying that an Operation must be executed after several other operations have completed. We can also monitor the execution of tasks through KVO. For more about Operation Queues, see Operation Queues.

Use blocks in GCD(Grand Central Dispatch)

GCD provides Dispatch Queues for managing tasks so that they can be executed synchronously or asynchronously.

We can create a new DispatchQueue or use the DispatchQueue provided by GCD. If we want to create a concurrent queue, we can simply use the existing queue — returned via dispatch_get_global_queue() and specify its priority:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Copy the code

Dispatches tasks to queues using either dispatch_async() or dispatch_sync(). Dispatch_async () returns immediately and does not wait for the Block to fire.

The dispatch_sync() function does not return until the Block is complete. Used when blocks that need to be executed asynchronously need to wait for other tasks to continue after the main thread has completed.

For more on GCD, see Dispatch Queues.

Reference: Working with Blocks