iOS底层探索28-Block分析上

227 阅读7分钟

[TOC]

本文主要介绍
1.block的基本类型
2.常见问题以及解决方式:循环引用
3.常见面试题

block的分类

三种: 堆上 栈上 全局

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>

GlobalBlock

  • 位于全局区
  • 在Block内部不适用外部变量,或者只使用静态变量和全局变量

MallocBlock

  • 位于堆区
  • 在Block内部使用局部变量或者OC属性,并且赋值给强引用或者Copy修饰的额变量

StackBlock

  • 位于栈区
  • 与MallocBlock一样,可以在内部使用局部变量或者OC属性。但是不能赋值给强引用或者Copy修饰的变量

Block拷贝到堆上的情况:

  • 手动copy
  • Block作为返回值
  • 被强引用 or Copy修饰
  • 系统API包含usingBlock

练习
1.block捕获外部变量-对外部变量的引用计数处理

#pragma mark - block 捕获外部变量-对外部变量的引用计数处理
- (void)blockDemo2{
    
    NSObject *objc = [NSObject new];
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
    // block 底层源码
    // 捕获 + 1
    // 堆区block
    // 栈 - 内存 -> 堆  + 1
    void(^strongBlock)(void) = ^{ // 1 - block -> objc 捕获 + 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();
    
}

结果
1
---3
---4
---5

2.内存拷贝的理解 先重写block底层实现 用于后面操作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
};
// 底层
struct _LGBlock {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    // 函数指针
    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)

// 打印Block 签名
- (NSMethodSignature *)getBlcokSignature;

// 打印Block 签名
- (NSString *)getBlcokSignatureString;

// 调用block
- (void)invokeBlock;

@end

NS_ASSUME_NONNULL_END

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) {
        // 动态的消息转发
        NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:signature];
        [blockInvocation invokeWithTarget:self];
    }
}
// block OC 对象
//- (NSString *)description {
//    if ([self isKindOfClass:NSClassFromString(@"__NSMallocBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSStackBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSGlobalBlock__")]) {
//        //签名
//        return [NSString stringWithFormat:@"<%@:%p>--%@", self.class, self, [self getBlcokSignatureString]];
//    }
//    return [NSString stringWithFormat:@"<%@:%p>", self.class, self];
//}
@end

示例

- (void)blockDemo1{
    int a = 0;
    void(^ __weak weakBlock)(void) = ^{
        NSLog(@"-----%d", a);
    };
    struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
        
    // 深浅拷贝
    id __strong strongBlock = weakBlock;
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;//让strongBlock具备block特性 可以执行strongBlock1()
    strongBlock1();
}

图片.png id __strong strongBlock = weakBlock; blc->invoke = nil

strongBlock和blc 都指向weakBlock 栈上的block 操作的是同一片内存空间,blc->invoke = nil 置空之后 影响后面strongBlock的调用 导致崩溃

- (void)blockDemo1{
    int a = 0;
    void(^ __weak weakBlock)(void) = ^{
        NSLog(@"-----%d", a);
    };
    struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
    
    // 深浅拷贝
    id __strong strongBlock = [weakBlock copy];
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;//让strongBlock具备block特性 可以执行strongBlock1()
    strongBlock1();
    // 预警什么 ???
}

id __strong strongBlock = [weakBlock copy]; 对weakBlock 进行copy则可以解决崩溃问题,新拷贝到堆上的Block和原来栈上的blc互不影响

3.block 堆栈释放差异

- (void)blockDemo3{
    
    // 压栈 内存平移
    
    int a = 0;
    void(^__weak weakBlock)(void) = nil;
    {
        // 栈区
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"---%d", a);
        };
        weakBlock = strongBlock;
        NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
    }
    weakBlock();
  
}

结果
1 - <__NSStackBlock__: 0x7ffeef5543a0> - <__NSStackBlock__: 0x7ffeef5543a0>
---0

把 void(^__weak strongBlock)(void) = ^{ 换成 void(^ strongBlock)(void) = ^{

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

1 - <__NSMallocBlock__: 0x6000007dadf0> - <__NSMallocBlock__: 0x6000007dadf0>
(lldb) 

堆区的block出了作用域就释放了 后面再调用就会报错

图片.png

block的循环引用以及解决方法

图片.png

图片.png

block最常见的的问题就是循环引用,上图描述了循环引用的过程和原因 产生循环引用的常见代码示例

static ViewController *staticSelf_;
typedef void(^LRBlock)(void);
@interface ViewController ()
@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];
    // 循环引用
    [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();
}

# pragma mark weak面试题
- (void)blockWeak_static {
    // 是同一片内存空间
    __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 来了");
}

@end

产生循环引用代码示例:

//代码一
self.block = ^(void){
    NSLog(@"%@",self.name);
};
self.block();

//代码二
[UIView animateWithDuration:1 animations:^{
    NSLog(@"%@",self.name);
}];

代码一产生了循环引用,在block内部使用了外部变量 name,name是self持有的,则blcok持有了self 而self本来就持有block, 所以 self -> block, block -> self.name -> self

代码二中没有循环引用, 虽然在block内部也使用了外部变量 name,但是block并不被self持有 只有block -> self.name -> self 没有构成循环引用

打破block对self强引用原理:解决self的作用域和block作用域的通讯,通讯方式有 代理、传值、通知等 通常我们有如下几种方式处理

  • 1.weak-strong-dance 强弱共舞
  • 2.手动置空,使用临时变量(用__block修饰)引用self,在block内部手动置空
  • 3.将可能产生循环引用的对象self以block参数的形式传入,在block内部使用参数就不会产生循环引用问题
  • 4.NSProxy虚拟类

1.weak-strong-dance 强弱共舞

1.1 常规情况: 使用__weak

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

常规情况,我们采用__weak 修饰self 的方式来解除循环引用,使用 __weak不会导致self的引用计数发生变化, weakSelf 和 self 指向同一片内存空间。

1.2 block嵌套的情况:同时使用 __weak 和 __strong

但是 有些情况(例如延迟操作) 会产生下面的问题: 点击进入vc然后返回 ,会调用上面的方法,延时2s之后会打印 (null)

图片.png

由打印可以发现,当在延时2s打印的时候 weakSelf已经释放了

为此通常采用 __strong创建临时变量的方式来 防止weakSelf在延时过程中被提前释放

- (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();
}

strongSelf是一个临时变量,作用域在block内部,当block执行完成strongSelf就会释放,从而保证weakSelf不会被提前释放和打破循环引用,依赖中介者模式

2.手动置空,使用临时变量(用__block修饰)引用self,在block内部手动置空

依赖中介者模式,需手动释放vc

- (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();
}

3.将可能产生循环引用的对象self以block参数的形式传入,在block内部使用参数就不会产生循环引用问题

- (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);
}

4.NSProxy虚拟类

  • OC只能单继承,由于它是运行实际值,所以可以通过NSProxy来实现伪多继承
  • NSProxy 和 NSObject是童年个基点一个类,也可以说是一个虚拟类,只是实现了NSObject的协议
  • NSProxy 其实是一个消息重定向封装的一个抽象类,类似一个代理人,中间件,可以通过继承它,并重写下面两个方法来实现消息转发到另一个实例
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel

NSProxy的使用场景主要有两种

实现多继承功能
解决了NSTimer&CADisplayLink创建时对self强引用问题,参考YYKit的YYWeakProxy。

通过自定义的NSProxy类对象来代替self,使用消息转发 自定义一个NSProxy子类

@interface LRProxy : NSProxy

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

+ (instancetype)proxyWithObjc:(id)objc;

@end

@interface LRProxy ()

@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.有了方法签名之后就会调用方法实现
- (void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = [invocation selector];
    if ([self.objc respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.objc];
    }
}

//1、查询该方法的方法签名
- (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

自定义Cat和Dog类

//********Cat类********
@interface Cat : NSObject
@end

@implementation Cat
- (void)eat{
   NSLog(@"猫吃鱼");
}
@end

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

@implementation Dog
- (void)shut{
    NSLog(@"狗叫");
}
@end

多继承示例

- (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)];
}

通过LRProxy解决定时器中对self强引用的问题,解决循环引用

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 来了");
}

block循环引用面试题

# pragma mark weak面试题
- (void)blockWeak_static {
    // 是同一片内存空间
    __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();
}

图片.png staticSelf_ -> weakSelf -> self 三个变量指向同一片内存空间,都是self指向的内存空间
由于staticSelf 是static修饰, vc返回之后还没有出作用域释放,导致self的引用计数不为0无法释放,产生了循环引用
当vc返回之后 重新进入新的vc页面调用此方法的时候,self是重新创建的 staticSelf = weakSelf 重新赋值,staticSelf不再指向原来的self,原来指向的self引用计数变为0 原来的vc释放,原来的循环引用打破, 但是重新赋值之后的staticSelf和新的vc继续产生了循环引用😏6️⃣6️⃣6️⃣😄

图片.png

对于block_weak_strong 这个题
本身就会产生循环引用
解决方案一 将强弱共舞进行到底
每一层block都有自己的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();

方案二 手动置空

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

方案三 传参

@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);