More:Interview Questions for iOS

What is a Block?

  • A Block is an object that encapsulates a function and its execution context.

Such as:

NSInteger num = 3;
    NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
        return n*num;
    };

    block(2);Copy the code

To compile the.m file with the clang-rewrite-objc wytest. m command, the block is compiled to this form:

NSInteger num = 3;

    NSInteger(*block)(NSInteger) = ((NSInteger (*)(NSInteger))&__WYTest__blockTest_block_impl_0((void *)__WYTest__blockTest_block_func_0, &__WYTest__blockTest_block_desc_0_DATA, num));

    ((NSInteger (*)(__block_impl *, NSInteger))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 2);Copy the code

WYTest is the file name and blockTest is the method name, which can be ignored. Where the __WYTest__blockTest_block_impl_0 structure is

struct __WYTest__blockTest_block_impl_0 { struct __block_impl impl; struct __WYTest__blockTest_block_desc_0* Desc; NSInteger num; __WYTest__blockTest_block_impl_0(void *fp, struct __WYTest__blockTest_block_desc_0 *desc, NSInteger _num, int flags=0) : num(_num) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

The __block_impl structure is

struct __block_impl { void *isa; //isa pointer, so Block is the object int Flags; int Reserved; void *FuncPtr; // function pointer};Copy the code

A block contains an ISA pointer, so it is essentially an OC object.

static NSInteger __WYTest__blockTest_block_func_0(struct __WYTest__blockTest_block_impl_0 *__cself, NSInteger n) {
  NSInteger num = __cself->num; // bound by copy

        return n*num;
    }Copy the code

So a Block is an object that encapsulates a function and its execution context and since a Block encapsulates a function, it also has parameters and return values.

2. Block variable interception

1. Local variable intercept is value intercept. Such as:

NSInteger num = 3;

    NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){

        return n*num;
    };

    num = 1;

    NSLog(@"%zd",block(2));Copy the code

The output here is 6 instead of 2 because the interception of the local variable num is value interception. Similarly, changing num in a block is invalid, and the compiler may even report an error.

2. Local static variable intercept is pointer intercept.

static  NSInteger num = 3;

    NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){

        return n*num;
    };

    num = 1;

    NSLog(@"%zd",block(2));Copy the code

The output is 2, which means that num = 1. It is also valid to modify m in a block.

3, global variables, static global variable interception: not interception, direct value.

Let’s do the same with Clang and see what happens.

static NSInteger num3 = 300;

NSInteger num4 = 3000;

- (void)blockTest
{
    NSInteger num = 30;

    static NSInteger num2 = 3;

    __block NSInteger num5 = 30000;

    void(^block)(void) = ^{

        NSLog(@"%zd",num); // local variable NSLog(@"%zd",num2); // Static variable NSLog(@"%zd",num3); // the global variable NSLog(@"%zd",num4); // global static variable NSLog(@"%zd",num5); //__block modifiers}; block(); }Copy the code

The compiled

struct __WYTest__blockTest_block_impl_0 { struct __block_impl impl; struct __WYTest__blockTest_block_desc_0* Desc; NSInteger num; // local variable NSInteger *num2; // static variable __Block_byref_num5_0 *num5; Void *fp, struct __WYTest__blockTest_block_impl_0(void *fp, struct __WYTest__blockTest_block_desc_0 *desc, NSInteger _num, NSInteger *_num2, __Block_byref_num5_0 *_num5, int flags=0) : num(_num), num2(_num2), num5(_num5->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

(impl. Isa = & _NSConcreteStackBlock; You can see that local variables are compiled as values, while static variables are compiled as Pointers. Global variables are not captured. The __block variable is also intercepted as a pointer, and a new struct object is generated:

struct __Block_byref_num5_0 {
  void *__isa;
__Block_byref_num5_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger num5;
};
Copy the code

This object has an attribute: num5, which is the variable we decorate with __block. Here __forwarding points to itself (stack block). In general, __block modifiers are required if we want to assign to local variables intercepted by a block, while __block modifiers are not required for static variables. In addition, any access to self or a member variable in a block intercepts self.

Several forms of Block

  • There are three forms: global Block(_NSConcreteGlobalBlock), stack Block(_NSConcreteStackBlock), and heap Block(_NSConcreteMallocBlock)

    Stack blocks are stored in the stack, heap blocks are stored in the heap, and global blocks are stored in the.data area

1. Blocks that do not use external variables are global blocks

Such as:

NSLog(@"% @",[^{
        NSLog(@"globalBlock");
    } class]);Copy the code

Output:

__NSGlobalBlock__Copy the code

2. A block that uses external variables and does not copy is a stack block

Such as:

NSInteger num = 10;
    NSLog(@"% @",[^{
        NSLog(@"stackBlock:%zd",num);
    } class]);Copy the code

Output:

__NSStackBlock__Copy the code

Daily development is often used in this case:

[self testWithBlock:^{
    NSLog(@"% @",self);
}];

- (void)testWithBlock:(dispatch_block_t)block {
    block();

    NSLog(@"% @",[block class]);
}Copy the code

3. Copy a stack block, that is, a heap block. Copy a global block, that is, a global block

  • For example, the global copy operation in heap 1 is assigned:
void (^globalBlock)(void) = ^{
        NSLog(@"globalBlock");
    };

 NSLog(@"% @",[globalBlock class]);Copy the code

Output:

__NSGlobalBlock__Copy the code

It’s still a global block

  • Assign to stack block 2:
NSInteger num = 10;

void (^mallocBlock)(void) = ^{

        NSLog(@"stackBlock:%zd",num);
    };

NSLog(@"% @",[mallocBlock class]);Copy the code

Output:

__NSMallocBlock__Copy the code

The mallock on the left is a heap block, and the copy on the right is still a stack block.

[self testWithBlock:^{

    NSLog(@"% @",self);
}];

- (void)testWithBlock:(dispatch_block_t)block
{
    block();

    dispatch_block_t tempBlock = block;

    NSLog(@"% @, % @",[block class],[tempBlock class]);
}Copy the code

Output:

__NSStackBlock__,__NSMallocBlock__Copy the code

  • That is, if you copy a stack Block, it’s going to be copied to the heap, if you copy a heap Block, it’s going to increase the reference count, it’s going to copy a global Block, because it’s already initialized, so it doesn’t do anything.

In addition, when a __block variable is copied, since __forwarding exists, the __forwarding pointer on the stack points to the __forwarding variable on the heap, and the __forwarding pointer on the heap points to itself. Therefore, if __block is changed, You are actually modifying a __block variable on the heap.

The purpose of the __forwarding pointer is to access the same __block variable from any memory location.

  • In addition, since the __block-modified variable captured by the block will hold the variable, if self is modified by __block and self holds the block, and self holds the block inside the block, it will cause a multi-loop reference, that is, self holds the block. Block holds a __block variable, which holds self, causing a memory leak. Such as:
__block typeof(self) weakSelf = self;

    _testBlock = ^{

        NSLog(@"% @",weakSelf);
    };

    _testBlock();Copy the code

If you want to solve this circular reference, you can actively disconnect the __block variable from holding self, that is, after using WeakSelf inside the block, set it to nil, but there is a problem with this approach, if the block is never called, then the circular reference will always exist. So, we’re better off decorating self with __weak

More: Direct 2020 — iOS Interview Questions