Blocks

Block objects are a C-based language feature that you can use in your C, Objective-C, and C++ code. Blocks make it easy to define a self-contained unit of work. Although they might seem akin to function pointers, a block is actually represented by an underlying data structure that resembles an object and is created and managed for you by the compiler. The compiler packages up the code you provide (along with any related data) and encapsulates it in a form that can live in the heap and be passed around your application.

One of the key advantages of blocks is their ability to use variables from outside their own lexical scope. When you define a block inside a function or method, the block acts as a traditional code block would in some ways. For example, a block can read the values of variables defined in the parent scope. Variables accessed by the block are copied to the block data structure on the heap so that the block can access them later. When blocks are added to a dispatch queue, these values must typically be left in a read-only format. However, blocks that are executed synchronously can also use variables that have the __block keyword prepended to return data back to the parent’s calling scope.

You declare blocks inline with your code using a syntax that is similar to the syntax used for function pointers. The main difference between a block and a function pointer is that the block name is preceded with a caret (^) instead of an asterisk (*). Like a function pointer, you can pass arguments to a block and receive a return value from it. 


Blocks are a language-level feature added to C, Objective-C and C++, which allow you to create distinct segments of code that can be passed around to methods or functions as if they were values. Blocks are Objective-C objects,which means they can be added to collections like NSArray or NSDictionary. They also have the ability to capture values from the enclosing scope, making them similar to closures or lambdas in other programming languages.

声明block

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

You can also combine the variable declaration and assignment:

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

Once you’ve declared and assigned a block variable, you can use it to invoke the block:

    simpleBlock();

Blocks Take Arguments and Return Values

Blocks can also take arguments and return values just like methods and functions.

As an example, consider a variable to refer to a block that returns the result of multiplying two values:

    double (^multiplyTwoValues)(double, double);

The corresponding block literal might look like this:

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

Once you’ve declared and defined the block, you can invoke it just like you would a function:

 double (^multiplyTwoValues)(double, double) =
                              ^(double firstValue, double secondValue) {
                                  return firstValue * secondValue;
                              };
 
    double result = multiplyTwoValues(2,4);

Blocks Can Capture Values from the Enclosing Scope

Only the value is captured, unless you specify otherwise. This means that if you change the external value of the variable between the time you define the block and the time it’s invoked, like this:

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

the value captured by the block is unaffected. This means that the log output would still show:

Integer is: 42

It also means that the block cannot change the value of the original variable, or even the captured value (it’s captured as a const variable).

Use __block Variables to Share Storage

If you need to be able to change the value of a captured variable from within a block, you can use the __blockstorage type modifier on the original variable declaration. This means that the variable lives in storage that is shared between the lexical scope of the original variable and any blocks declared within that scope.

As an example, you might rewrite the previous example like this:

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

Because anInteger is declared as a __block variable, its storage is shared with the block declaration. This means that the log output would now show:

Integer is: 84

It also means that the block can modify the original value, like this:

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

This time, the output would show:

Integer is: 42
Value of original variable is now: 100

You Can Pass Blocks as Arguments to Methods or Functions

Blocks make this much easier, however, because you can define the callback behavior at the time you initiate the task, like this:

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

The (void (^)(void)) specifies that the parameter is a block that doesn’t take any arguments or return any values. The implementation of the method can invoke the block in the usual way:

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

Method parameters that expect a block with one or more arguments are specified in the same way as with a block variable:

- (void)doSomethingWithBlock:(void (^)(double, double))block {
    ...
    block(21.0, 2.0);
}
A Block Should Always Be the Last Argument to a Method

It’s best practice to use only one block argument to a method. If the method also needs other non-block arguments, the block should come last:

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

This makes the method call easier to read when specifying the block inline, like this:

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

Use Type Definitions to Simplify Block Syntax

As an example, you can define a type for a simple block with no arguments or return value, like this:

typedef void (^XYZSimpleBlock)(void);

You can then use your custom type for method parameters or when creating block variables:

    XYZSimpleBlock anotherBlock = ^{
        ...
    };

Objects Use Properties to Keep Track of Blocks

The syntax to define a property to keep track of a block is similar to a block variable:

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

Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior.

A block property is set or invoked like any other block variable:

    self.blockProperty = ^{
        ...
    };
    self.blockProperty();

It’s also possible to use type definitions for block property declarations, like this:

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

Avoid Strong Reference Cycles when Capturing self

If you need to capture self in a block, such as when defining a callback block, it’s important to consider the memory management implications.

Blocks maintain strong references to any captured objects, including self, which means that it’s easy to end up with a strong reference cycle if, for example, an object maintains a copy property for a block that captures self:

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

The compiler will warn you for a simple example like this, but a more complex example might involve multiple strong references between objects to create the cycle, making it more difficult to diagnose.

To avoid this problem, it’s best practice to capture a weak reference to self, like this:

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

By capturing the weak pointer to self, the block won’t maintain a strong relationship back to the XYZBlockKeeperobject. If that object is deallocated before the block is called, the weakSelf pointer will simply be set to nil.

Blocks Can Simplify Enumeration

In addition to general completion handlers, many Cocoa and Cocoa Touch API use blocks to simplify common tasks, such as collection enumeration. The NSArray class, for example, offers three block-based methods, including:

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

This method takes a single argument, which is a block to be invoked once for each item in the array:

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

The block itself takes three arguments, the first two of which refer to the current object and its index in the array. The third argument is a pointer to a Boolean variable that you can use to stop the enumeration, like this:

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

It’s also possible to customize the enumeration by using the enumerateObjectsWithOptions:usingBlock: method. Specifying the NSEnumerationReverse option, for example, will iterate through the collection in reverse order.

If the code in the enumeration block is processor-intensive—and safe for concurrent execution—you can use theNSEnumerationConcurrent option:

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

This flag indicates that the enumeration block invocations may be distributed across multiple threads, offering a potential performance increase if the block code is particularly processor intensive. Note that the enumeration order is undefined when using this option.

The NSDictionary class also offers block-based methods, including:

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

This makes it more convenient to enumerate each key-value pair than when using a traditional loop, for example.

Blocks Can Simplify Concurrent Tasks

A block represents a distinct unit of work, combining executable code with optional state captured from the surrounding scope. This makes it ideal for asynchronous invocation using one of the concurrency options available for OS X and iOS. Rather than having to figure out how to work with low-level mechanisms like threads, you can simply define your tasks using blocks and then let the system perform those tasks as processor resources become available.

Use Block Operations with Operation Queues

Schedule Blocks on Dispatch Queues with Grand Central Dispatch






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值