GitHub Repo: BoyangBlog

Here are some interview questions to expand your knowledge. Some of these questions were taken from Sunnyxx.

Q 1: What is the result of the following code run? ?

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int d = 1000; // Global variables
static int e = 10000; // Static global variables

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        
        int a = 10; // Local variables
        static int b = 100; Static local variables
        __block int c = 1000;
        void (^block)(void) = ^{
            NSLog(@"Block - \ n \ n b = a = % d % d \ n c = % d \ \ n e n d = % d = % d",a,b,c,d,e);
         };
         a = 20;
         b = 200;
         c = 2000;
         d = 20000;
         e = 200000;
         NSLog(@"On the Block - \ n \ n b = a = % d % d \ n c = % d \ \ n e n d = % d = % d",a,b,c,d,e);
         block();
         NSLog(@"Block - \ n \ n b = a = % d % d \ n c = % d \ \ n e n d = % d = % d",a,b,c,d,e);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code

The answer is

2019- 04- 04 04:50:58.508341+0800 Block_Test[19213:1138920] Block上--
 a = 20 
 b = 200
 c = 2000 
 d = 20000 
 e = 200000
2019- 04- 04 04:50:58.509229+0800 Block_Test[19213:1138920] Block中--
 a = 10 
 b = 200
 c = 2000 
 d = 20000 
 e = 200000
2019- 04- 04 04:50:58.509395+0800 Block_Test[19213:1138920Block -- a =20 
 b = 200
 c = 2000 
 d = 20000 
 e = 200000
Copy the code

Answer:

  • When a block captures a common local variable, it captures the value of A. Any subsequent changes to the value of A do not affect the value previously captured by the block, so the value of A remains unchanged.
  • A block captures the address of a static local variable b. Inside a block, it finds b by address and retrieves its value. So the value of b has changed.
  • __block is an object that wraps an external variable and stores C in that object. In fact, the address of C outside the block also points to the c stored in the object, and there is a pointer to the object at the bottom of the block. So when c is changed outside the block, the pointer inside the block finds the object and then finds C. And then you get the value of c, so c changes.
  • Global variables can be accessed anywhere. Blocks do not capture global variables, so wherever you change D and e, the latest values are retrieved in the block.

What is the result of running the code below Question2?

- (void)test{
  
    __block Foo *foo = [[Foo alloc] init];
    foo.fooNum = 20;
    __weak Foo *weakFoo = foo;
    self.block = ^{
        NSLog(@"block中-上 fooNum = %d",weakFoo.fooNum);
        [NSThread sleepForTimeInterval:1.0 f];
        NSLog(@"Block in-lower fooNum = %d",weakFoo.fooNum);
    };
    
    dispatch_async(dispatch_get_global_queue(0.0), ^{
        self.block();
    });
    
    [NSThread sleepForTimeInterval:0.2 f];
    NSLog(@"end");
}
Copy the code

As a result,

Block - up fooNum =20End block - lower fooNum =0
Copy the code

WeakFoo is a weak pointer, so self.block is a weak reference to Person. Then add a task to the concurrent queue via an asynchronous function to execute self.block(); , so a child thread is started to perform the task, the fooNum value is printed as 20, and then the child thread starts to sleep for 1 second; The main thread also sleeps for 0.2 seconds at the same time. Since foo is a local variable, and self.block makes a weak reference to it, the foo object is freed after test. After another 0.8 seconds, the child thread finishes sleeping, at which point the object that weakFoo points to has become nil, so the printed fooNum is 0.

  • Then ask: if the following[NSThread sleepForTimeInterval: 0.2 f];Instead of[NSThread sleepForTimeInterval: 2.0 f];?

As a result,

Block - up fooNum =20End block - lower fooNum =20
Copy the code

Since the main thread is still sleeping when the child thread finishes sleeping, that is, the test method is still executing, the Person object still exists, so the fooNum printed before and after the child thread finishes sleeping is 20.

  • Another way to ask: if you add inside a block__strong Foo *strongFoo = weakFoo;And print strong.foonum instead.

The result:

Block - up fooNum =20End block - lower fooNum =20
Copy the code

__strong is used to ensure that the object it modifiers is not released during the execution of the block. Even if there is no strong pointer to the object outside the block, the object is not released immediately. __weak and __strong should be used together to prevent weak references from being released during block execution.

What happens to the code below Questime3?

- (void)test{
    self.age = 20;
    self.block = ^{
      NSLog(@"%d",self.age);
    };
    
    self.block();
}
Copy the code

A: Circular references occur. Because self points to the block via a strong pointer, and the block inside captures self and points to self with a strong pointer, self and block strongly refer to each other, creating a circular reference. WeakSelf = self; __weak typeof(self) weakSelf = self; It is good.

  • So if I take awayself.block();?

Answer: Same will reference, same will occur circular reference.

  • That if theNSLog(@"%d",self.age);Instead ofNSLog(@"%d",_age);?

A: Circular references still happen. Because _age is really self->age.

Question4: will circular references occur below?

[UIView animateWithDuration:1.0 f animations:^{
       NSLog(@"%d",self.age);
}];
dispatch_sync(dispatch_get_global_queue(0.0), ^{
       NSLog(@"%d",self.age);
});
Copy the code

A: No. The block here is actually part of this function, and it’s an argument. Even though the block strongly references self, self doesn’t strongly reference the block, so that’s fine.

Question5: How can we continue to use blocks when direct invocation of blocks is prohibited?

- (void)blockProblem {
    __block int a = 0;
    void (^block)(void) = ^{
        self.string = @"retain";
        NSLog(@"biboyang");
        NSLog(@"biboyang%d",a);
    };
// block(); / / is prohibited
}
Copy the code

We can do this in several ways

1. Call other methods directly

- (void)blockProblemAnswer0:(void(^) (void))block {
    // Animation method
    [UIView animateWithDuration:0 animations:block];   
    / / main thread
    dispatch_async(dispatch_get_main_queue(), block);
}
Copy the code

Both of these are direct calls to the original block’s methods.

2.NSOperation

- (void)blockProblemAnswer1:(void(^) (void))block {
    [[NSBlockOperation blockOperationWithBlock:block]start];
}
Copy the code

Call it directly using the NSOperation method. Note that this method is executed on the main thread.

3.NSInvocation

- (void)blockProblemAnswer2:(void(^) (void))block {
    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@?"];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation invokeWithTarget:block];
}
Copy the code

NSMethodSignature is a method signature that encapsulates the return and parameter types of a method, only the return and parameter types.

  • @? It means that this is a block.

The NSInvocation object contains all the elements of the Objective-C message: target, selector, parameters, and return value. These elements can be set directly, and the return value is set automatically when the NSncOcObjt object is scheduled.

NSInvocation objects can be repeatedly assigned to different targets; Its parameters can be modified between dispatches to get different results; Even its selector can be changed to another with the same method signature (parameters and return type). This flexibility makes NSInvocation very useful for repeating messages with many parameters and variants; Instead of retyping a slightly different expression for each message, you can modify the NSInvocation object as needed each time before dispatching it to a new target.

4. The invoke method

- (void)blockProblemAnswer3:(void(^) (void))block {
    [block invoke];
}
Copy the code

We can print out the block’s inheritance line.

 -> __NSMallocBlock__ -> __NSMallocBlock -> NSBlock -> NSObject
Copy the code

And then we look for the method of NSBlock

(lldb) po [NSBlock instanceMethods]
<__NSArrayI 0x600003265b00>(
- (id)copy,
- (id)copyWithZone:({_NSZone=} *)arg0 ,
- (void)invoke,
- (void)performAfterDelay:(double)arg0 
)
Copy the code

We found an Invoke method that actually comes from NSInvocation too. The method is to send a message (with arguments) from the receiver to the target and set the return value.

Note: This method is the NSInvocation method, not the invoke method in the Block structure.

Struct method of block

    void *pBlock = (__bridge void*)block;
    void (*invoke)(void*,...). = * ((void **)pBlock + 2);
    invoke(pBlock);
Copy the code

Start (__bridge void*)block converts a block to a pointer to the first digit of the block structure. And then we calculate the offset.

Then observe the memory layout of the block

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void*,...). ;struct Block_descriptor *descriptor;
    /* Imported variables. */
};
Copy the code

In 64-bit, a void pointer takes up 8 bytes. If int occupies 4 bits, then flag and reserved occupy 8 bits in total and 16 bits plus one bit.

We know that a void* occupies 8 bits, and (void **)pBlock represents its own 8-bit address length. +2 means twice the length of 8 bits, which is 16 bits. The void (*invoke) method is reached.

Void (*invoke)(void *,…) This is the pointer to the block function, just call it.

6.attribute((the cleanup)) method

static void blockCleanUp(__strong void(^*block)(void)){
    (*block)();
}
- (void)blockProblemAnswer5:(void(^)(void))block {
    __strong void(^cleaner)(void) __attribute ((cleanup(blockCleanUp),unused)) = block;
}
Copy the code

Here you can see the dark magic __attribute__((cleanup))

7. Assembly method

- (void)blockProblemAnswer6:(void(^) (void))block {
    asm("movq -0x18(%rbp), %rdi");
    asm("callq *0x10(%rax)");
}
Copy the code

We give a block break point and type dis in LLDB to see the assembly code.

->  0x1088c8d1e <+62>:  movq   -0x18(%rbp), %rax
    0x1088c8d22 <+66>:  movq   %rax, %rsi
    0x1088c8d25 <+69>:  movq   %rsi, %rdi
    0x1088c8d28 <+72>:  callq  *0x10(%rax)
Copy the code

Make sure you write the first line.

If you don’t write the first line, this is fine if you don’t intercept the external variable, but once you do, you can’t determine the offset and crash.

Question3 HookBlock

The first question

My initial idea was to implement a structural replacement of a block as an intermediate for holding method Pointers. Then we also implement the replacement block structure for loading.

/ / intermediates
typedef struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
}__block_impl;

/ / acceptor
typedef struct __block_impl_replace {
    void *isa_replace;
    int Flags_replace;
    int Reserved_replace;
    void *FuncPtr_replace;
}__block_impl_replace;


// Replace the method
void hookBlockMethod(a) {
    NSLog(@"The Yellow River enters the Sea.");
}

void HookBlockToPrintHelloWorld(id block) {
    __block_impl_replace *ptr = (__bridge __block_impl *)block;
    ptr->FuncPtr_replace = &hookBlockMethod;
}
Copy the code

Note that the method name in the structure is not the same as the method name in the system block. The warning Incompatible pointer types initializing ‘__block_impl_REPLACE *’ (aka ‘struct __block_impl_replace *’) With an expression of type ‘__block_impl *’ (aka ‘struct __block_impl *’) warns us that the two methods are not compatible. In fact, the names of the methods in the two structures are not the same, or even the number of methods can be different, but make sure the first four members are of the same type; The first four members are the key to storing data inside the block. It doesn’t matter if there are four members followed by another.

typedef struct __block_impl_replace {
    void *isa_replace;
    int Flags_replace;
    int Reserved_replace;
    void *FuncPtr_replace;
    void *aaa;
    void *bbb;
    void *ccc;
}__block_impl_replace;
Copy the code

This way, for example, the method actually still works.

Of course, this approach can also be optimized. For example, we can combine an intermediate structure with a replacement block.

For example, this is the result of optimization.

typedef struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
}__block_impl;

void OriginalBlock (id Or_Block) {
    void(^block)(void) = Or_Block;
    block();
}

void HookBlockToPrintHelloWorld(id block) { __block_impl *ptr = (__bridge __block_impl *)block; ptr->FuncPtr = &hookBlockMethod; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -void (^block)(void) = ^void() {
        NSLog(@"The day follows the mountain.");
    };
    HookBlockToPrintHelloWorld(block);
    block();
Copy the code

So here we can print the Yellow River flowing into the sea.

But what if we want to print the original method as well?

The method is very simple

void OriginalBlock (id Or_Block) {
    void(^block)(void) = Or_Block;
    block();
}
void HookBlockToPrintHelloWorld(id block) {
    __block_impl *ptr = (__bridge __block_impl *)block;
    OriginalBlock(block);
    ptr->FuncPtr = &hookBlockMethod;
}
Copy the code

Preserve the original block and execute the original block method in the method.

We can implement the following

2018- 11- 19 17:12:16.599362+0800 BlockBlogTest[64408:32771276The day follows the mountains2018- 11- 19 17:12:16.599603+0800 BlockBlogTest[64408:32771276The Yellow River enters the seaCopy the code

The second question

Here I refer to some online discussions, combined with the original ideas, the answer is as follows

static void (*orig_func)(void *v ,int i, NSString *str);

void hookFunc_2(void *v ,int i, NSString *str) {
    NSLog(@"%d,%@", i, str);
    orig_func(v,i,str);
}

void HookBlockToPrintArguments(id block) { __block_impl *ptr = (__bridge __block_impl *)block; orig_func = ptr->FuncPtr; ptr->FuncPtr = &hookFunc_2; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --void (^hookBlock)(int i,NSString *str) = ^void(int i,NSString *str){
        NSLog(@"bby");
    };
    HookBlockToPrintArguments(hookBlock);
    hookBlock(1The @"biboyang");

Copy the code

So you can print it out

2018- 11- 19 17:12:16.599730+0800 BlockBlogTest[64408:32771276] 1,biboyang
2018- 11- 19 17:12:16.599841+0800 BlockBlogTest[64408:32771276] bby
Copy the code

The third question

To be honest, I haven’t realized the third question yet, but I discussed this problem with Donggua when I attended the SWIFT conference in Beijing. The idea was to put a block into a parent class and then modify it. But then Donggua introduced the Fishhook framework, and my thinking changed. In ARC we use heap blocks, but when we create stack blocks, it goes through a copy process, converting stack blocks into heaps, There is an objc_retainBlock->_Block_copy->_Block_copy_internal method chain in the middle. We can hook these methods to modify.

The demo address