[TOC]

This article mainly introduces 1. Basic types of blocks 2. Common problems and solutions: Circular reference 3. Frequently seen exam

The classification of the block

Three: heap on stack global

int a = 10;
void (^block)(void) = ^{
};
NSLog(@ "% @",block);//<__NSGlobalBlock__: 0x1015f9100>

int a = 10;
void (^block)(void) = ^ {NSLog(@"lr - %d",a);
};
NSLog(@ "% @",block);//<__NSMallocBlock__: 0x60000387c0f0>

int a = 10;
void (__weak ^block)(void) = ^ {NSLog(@"lr - %d",a);
};
NSLog(@ "% @",block);//<__NSStackBlock__: 0x7ffee1c29428>
Copy the code

GlobalBlock

  • Located in global area
  • External variables are not applied inside blocks, or only static and global variables are used

MallocBlock

  • Located in the heap area
  • Use local variables or OC attributes inside blocks and assign values to strongly referenced or copy-modified variables

StackBlock

  • Located in the stack area
  • As with mallocblocks, local variables or OC attributes can be used internally. But you cannot assign to strongly referenced or copy-modified variables

Block copied to heap:

  • Manually copy
  • Block as the return value
  • Modified by strong reference or Copy
  • The system API contains usingBlock

Exercise 1. Block captures external variables – reference counting processing of external variables

#pragmaMark-block captures external variables - reference counting processing of external variables
- (void)blockDemo2{
    
    NSObject *objc = [NSObject new];
    NSLog(@"%ld".CFGetRetainCount((__bridge CFTypeRef)(objc))); / / 1
    // Block source code
    // Capture + 1
    / block/heap area
    // stack - memory -> heap + 1
    void(^strongBlock)(void) = ^ {// 1 -block -> objc capture + 1 = 2
        NSLog(@"---%ld".CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    strongBlock();

    void(^ __weak weakBlock)(void) = ^ {/ / + 1
        NSLog(@"---%ld".CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    weakBlock();
    
    void(^mallocBlock)(void) = [weakBlock copy]; mallocBlock(); } the result1
--- 3
--4 -
--- 5
Copy the code

Block NSObject+ block. h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef void(*LGBlockCopyFunction)(void *, const void *);
typedef void(*LGBlockDisposeFunction)(const void *);
typedef void(*LGBlockInvokeFunction)(void*,...). ;enum {
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), // helpers have C++ code
    BLOCK_IS_GLOBAL =         (1 << 28),
    BLOCK_HAS_STRET =         (1 << 29), // IFF BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE =     (1 << 30),};struct _LGBlockDescriptor1 {
    uintptr_t reserved;
    uintptr_t size;
};

struct _LGBlockDescriptor2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    LGBlockCopyFunction copy;
    LGBlockDisposeFunction dispose;
};

struct _LGBlockDescriptor3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
/ / the underlying
struct _LGBlock {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    // Function pointer
    LGBlockInvokeFunction invoke;
    struct _LGBlockDescriptor1 *descriptor;
};


static struct _LGBlockDescriptor3 * _LG_Block_descriptor_3(struct _LGBlock *aBlock) {
    if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return nil;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct _LGBlockDescriptor1);
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
        desc += sizeof(struct _LGBlockDescriptor2);
    }
    return (struct _LGBlockDescriptor3 *)desc;
}

static const char *LGBlockTypeEncodeString(id blockObj) {
    struct _LGBlock *block = (__bridge void *)blockObj;
    return _LG_Block_descriptor_3(block)->signature;
}
@interface NSObject (Block)

// Prints the Block signature
- (NSMethodSignature *)getBlcokSignature;

// Prints the Block signature
- (NSString *)getBlcokSignatureString;

/ / call block
- (void)invokeBlock;

@end

NS_ASSUME_NONNULL_END

Copy the code

NSObject+Block.m

#import "NSObject+Block.h"


@implementation NSObject (Block)

- (NSString *)getBlcokSignatureString {
    
    NSMethodSignature *signature = self.getBlcokSignature;
    if (signature) {
        NSMutableString *blockSignature = [NSMutableString stringWithFormat:@"BlcokSignature: return type: %s, ", [signature methodReturnType]];
        for (int i = 0; i < signature.numberOfArguments; i++) {
            [blockSignature appendFormat:@"argument number: %d, argument type: %s ", i+1, [signature getArgumentTypeAtIndex:i]];
        }
        return blockSignature;
    }
    return nil;
}

- (NSMethodSignature *)getBlcokSignature {
    if ([self isKindOfClass:NSClassFromString(@"__NSMallocBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSStackBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSGlobalBlock__")]) {
        NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:LGBlockTypeEncodeString(self)];
        return signature;
    }
    return nil;
}

- (void)invokeBlock {
    NSMethodSignature *signature = self.getBlcokSignature;
    if (signature) {
        // Dynamic message forwarding
        NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:signature];
        [blockInvocation invokeWithTarget:self]; }}// block the OC object
//- (NSString *)description {
// if ([self isKindOfClass:NSClassFromString(@"__NSMallocBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSStackBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSGlobalBlock__")]) {
/ / / / signature
// return [NSString stringWithFormat:@"<%@:%p>--%@", self.class, self, [self getBlcokSignatureString]];
/ /}
// return [NSString stringWithFormat:@"<%@:%p>", self.class, self];
/ /}
@end

Copy the code

The sample

- (void)blockDemo1{
    int a = 0;
    void(^ __weak weakBlock)(void) = ^ {NSLog(@"-----%d", a);
    };
    struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
        
    // Depth copy
    id __strong strongBlock = weakBlock;
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;// Use strongBlock to execute strongBlock1()
    strongBlock1();
}
Copy the code

id __strong strongBlock = weakBlock; blc->invoke = nil

StrongBlock and BLC both point to the block on weakBlock stack and operate on the same piece of memory space. BLC -> Invoke = nil will affect the call of strongBlock behind, resulting in crash

- (void)blockDemo1{
    int a = 0;
    void(^ __weak weakBlock)(void) = ^ {NSLog(@"-----%d", a);
    };
    struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
    
    // Depth copy
    id __strong strongBlock = [weakBlock copy];
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;// Use strongBlock to execute strongBlock1()
    strongBlock1();
    // Warning what??
}
Copy the code

id __strong strongBlock = [weakBlock copy]; WeakBlock copy can solve the crash problem, and the newly copied Block on the heap and the BLC on the original stack do not affect each other

3. Block stack releases the difference

- (void)blockDemo3{
    
    // Stack memory translation
    
    int a = 0;
    void(^ __weak weakBlock)(void) = nil;
    {
        / / the stack area
        void(^ __weak strongBlock)(void) = ^ {NSLog(@"---%d", a);
        };
        weakBlock = strongBlock;
        NSLog(@"1 - %@ - %@",weakBlock,strongBlock); } weakBlock(); } the result1 - <__NSStackBlock__: 0x7ffeef5543a0> - <__NSStackBlock__: 0x7ffeef5543a0>
--0
Copy the code

Void (^__weak strongBlock)(void) = ^{void(^__weak strongBlock)(void) = ^{

- (void)blockDemo3{
    int a = 0;
    void(^ __weak weakBlock)(void) = nil;
    {
        / / heap area
        void(^ strongBlock)(void) = ^ {NSLog(@"---%d", a);
        };
        weakBlock = strongBlock;
        NSLog(@"1 - %@ - %@",weakBlock,strongBlock);//1 - <__NSMallocBlock__: 0x6000007dadf0> - <__NSMallocBlock__: 0x6000007dadf0>
    }
    weakBlock();
}

1 - <__NSMallocBlock__: 0x6000007dadf0> - <__NSMallocBlock__: 0x6000007dadf0>
(lldb) 
Copy the code

Blocks in the heap are released when they are out of scope and an error is reported when they are called again

A circular reference to a block and its resolution

The most common problem with blocks is circular references. The figure above describes the process of circular references and common code examples of why circular references occur

static ViewController *staticSelf_;
typedef void(^LRBlock)(void);
@interface ViewController(a)
@property (nonatomic.strong) LRBlock block;
@property (nonatomic.copy) NSString *name;

@property (nonatomic.copy) LRBlock doWork;
@property (nonatomic.copy) LRBlock doStudent;

@property (nonatomic.strong) UITableView *tableView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Circular reference
    [self block1];
}

- (void)block1{
    __weak typeof(self) weakSelf = self;
    self.block = ^(void){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@ "% @",weakSelf.name);
        });
    };
    self.block();
}

# pragmaMark Weak
- (void)blockWeak_static {
    // It is the same memory space
    __weak typeof(self) weakSelf = self;
    staticSelf_ = weakSelf;
    // staticSelf_ -> weakSelf -> self
}

# pragma mark weak_strong_dance
- (void)block_weak_strong {
    __weak typeof(self) weakSelf = self;
    self.doWork = ^{
        __strong typeof(self) strongSelf = weakSelf;
        weakSelf.doStudent = ^{
            NSLog(@ "% @", strongSelf);
        };
       weakSelf.doStudent();
    };
   self.doWork();
}

- (void)dealloc{
    NSLog(@ "dealloc is coming");
}

@end
Copy the code

Examples of code to generate a circular reference:

/ / code
self.block = ^(void) {NSLog(@ "% @".self.name);
};
self.block();

/ / code 2
[UIView animateWithDuration:1 animations:^{
    NSLog(@ "% @".self.name);
}];
Copy the code

As soon as the code generates a circular reference, it uses the external variable name inside the block. Name is held by self, so blcok holds self and self already holds the block, so self -> block, block -> self.name -> self

There is no circular reference in code 2. Although the external variable name is used inside the block, the block is not held by self. Only block -> self.name -> self does not constitute a circular reference

Break block to self strong reference principle: solve self scope and block scope communication, communication methods have proxy, transmission value, notification, etc. Usually we have the following several ways to deal with

  • Weak-strong-dance
  • 2. Manually null, using a temporary variable (decorated with __block) referencing self, manually null inside the block
  • 3. Pass the object self, which may generate a circular reference, as a block argument. Using arguments inside a block does not cause circular reference problems
  • 4. NSProxy virtual class

Weak-strong-dance

1.1 General: __weak is used

- (void)block1{
    __weak typeof(self) weakSelf = self;
    self.block = ^(void) {NSLog(@ "% @",weakSelf.name);
    };
    self.block();
}
Copy the code

In general, we use __weak to modify self to remove the circular reference. Using __weak will not change the reference count of self. WeakSelf and self point to the same piece of memory space.

1.2 Block nesting: use both __weak and __strong

But there are cases (such as delayed operations) where clicking on VC and returning will call the above method and print (NULL) after 2s delay.

It can be found from printing that weakSelf has been released when printing delayed 2s

For this reason, __strong is usually used to create temporary variables to prevent weakSelf from being released early in the delay process

- (void)block1{
    __weak typeof(self) weakSelf = self;
    self.block = ^(void){
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@ "% @",strongSelf.name);
        });
    };
    self.block();
}
Copy the code

StrongSelf is a temporary variable, scoped inside the block, strongSelf will be released when the block execution is complete, so as to ensure weakSelf will not be released early and break the loop reference, depending on the mediator mode

2. Manually null, using a temporary variable (decorated with __block) referencing self, manually null inside the block

Depending on the mediator mode, vc needs to be released manually

- (void)block1{

    // self -> block -> vc -> self
    __block ViewController *vc = self;
    self.block = ^(void){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@ "% @",vc.name);
            vc = nil;
        });
    };
    self.block();
}
Copy the code

3. Pass the object self, which may generate a circular reference, as a block argument. Using arguments inside a block does not cause circular reference problems

- (void)block1{
    self.block = ^(ViewController *vc){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@ "% @",vc.name);
        });
    };
    self.block(self);
}
Copy the code

4. NSProxy virtual class

  • OC can only have single inheritance, and since it runs real values, pseudo-multiple inheritance can be implemented through NSProxy
  • NSProxy and NSObject is a childhood base class, or virtual class, that just implements the protocol of NSObject
  • NSProxy is an abstract class that encapsulates message redirection. It is like a proxy, middleware, that can inherit it and rewrite the following two methods to implement message forwarding to another instance
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
Copy the code

There are two scenarios for using NSProxy

The realization of multiple inheritance function solves the problem of self strong reference when nstimer and CadisplayLink are created, refer to YYWeakProxy of YYKit.Copy the code

Use message forwarding to customize a NSProxy subclass by replacing self with a custom NSProxy class object

@interface LRProxy : NSProxy

- (id)transformObjc:(NSObject *)objc;

+ (instancetype)proxyWithObjc:(id)objc;

@end

@interface LRProxy(a)

@property (nonatomic.weak.readonly) NSObject *objc;

@end

@implementation LRProxy

- (id)transformObjc:(NSObject *)objc{
   _objc = objc;
    return self;
}

+ (instancetype)proxyWithObjc:(id)objc{
    return  [[self alloc] transformObjc:objc];
}


//2. The method implementation is called once the method signature is in place
- (void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = [invocation selector];
    if ([self.objc respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.objc]; }}//1. Query the signature of the method
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    NSMethodSignature *signature;
    if (self.objc) {
        signature = [self.objc methodSignatureForSelector:sel];
    }else{
        signature = [super methodSignatureForSelector:sel];
    }
    return signature;
}

- (BOOL)respondsToSelector:(SEL)aSelector{
    return [self.objc respondsToSelector:aSelector];
}

@end

Copy the code

Custom Cat and Dog classes

/ / * * * * * * * * the Cat class * * * * * * * *
@interface Cat : NSObject
@end

@implementation Cat
- (void)eat{
   NSLog(@" Cats eat fish");
}
@end

/ / Dog class * * * * * * * * * * * * * * * *
@interface Dog : NSObject
@end

@implementation Dog
- (void)shut{
    NSLog(@ "dog");
}
@end
Copy the code

Multiple inheritance example

- (void)proxyTest{
    Dog *dog = [[Dog alloc] init];
    Cat *cat = [[Cat alloc] init];
    LRProxy *proxy = [LRProxy alloc];
    
    [proxy transformObjc:cat];
    [proxy performSelector:@selector(eat)];
    
    [proxy transformObjc:dog];
    [proxy performSelector:@selector(shut)];
}
Copy the code

LRProxy is used to solve the problem of timer self strong reference, solve the circular reference

self.timer = [NSTimer timerWithTimeInterval:1 target:[LRProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

- (void)dealloc{
    [self.timer invalidate];
    NSLog(@ "dealloc is coming");
}
Copy the code

Block loop quotes interview questions

# pragmaMark Weak
- (void)blockWeak_static {
    // It is the same memory space
    __weak typeof(self) weakSelf = self;
    staticSelf_ = weakSelf;
    // staticSelf_ -> weakSelf -> self
}

# pragma mark weak_strong_dance
- (void)block_weak_strong {
    __weak typeof(self) weakSelf = self;
    self.doWork = ^{
        __strong typeof(self) strongSelf = weakSelf;
        weakSelf.doStudent = ^{
            NSLog(@ "% @", strongSelf);
        };
       weakSelf.doStudent();
    };
   self.doWork();
}
Copy the code

StaticSelf_ -> weakSelf -> self Three variables point to the same memory space, which is the same memory space that self points to

Because staticSelf is static and has not been released out of scope after the VC returns, self’s reference count is not zero and cannot be released, resulting in a circular reference

WeakSelf = weakSelf reassigns a value. StaticSelf no longer points to self. The reference count of the original self is 0. The original cyclic reference is broken, but staticSelf and new VC after reassignment continue to generate cyclic reference 😏6️ 6️ 6️ one

For block_weak_strong, the problem itself will generate a circular reference solution. First, the strong and weak dance to the end. Each layer of block has its own strongSelf

__weak typeof(self) weakSelf = self;
    self.doWork = ^{
        __strong typeof(self) strongSelf = weakSelf;
        __weak typeof(self) weakSelf = strongSelf;
        weakSelf.doStudent = ^{
            __strong typeof(self) strongSelf = weakSelf;
            NSLog(@ "% @", strongSelf);
        };
       weakSelf.doStudent();
    };
   self.doWork();
Copy the code

Plan 2 Manually empty the switch

__block ViewController *vc = self;
    self.doWork = ^{
        vc.doStudent = ^{
            NSLog(@ "% @", vc);
            vc = nil;
        };
       vc.doStudent();
    };
   self.doWork();
Copy the code

Plan 3: Transfer parameters

@property (nonatomic.copy) void(^doWork1)(ViewController *vc);
@property (nonatomic.copy) void(^doStudent1)(ViewController *vc);

self.doWork1 = ^(ViewController *vc) {
    vc.doStudent1 = ^(UIViewController *vc) {
        NSLog(@ "% @", vc);
    };
    vc.doStudent1(vc);
};
self.doWork1(self);
Copy the code