iOS 底层原理:block 分析上

835 阅读4分钟

前言

本篇将会介绍block的类型、循环引用和一些相关的面试题,并在下一篇文章对这些上层表现用底层源码进行验证和分析。

一、block 的类型

  • GlobalBlock
    • 位于全局区
    • Block内部不使用外部变量,或者只使用静态变量和全局变量
  • MallocBlock
    • 位于堆区
    • Block内部使用局部变量或者OC属性,并且赋值给强引用或者Copy修饰的变量
  • StackBlock
    • 位于栈区
    • MallocBlock一样,可以在内部使用局部变量或者OC属性,但是不能赋值给强引用或者Copy修饰的变量

GlobalBlock示例:

- (void)viewDidLoad {
    void (^block)(void) = ^{
    
    };
    NSLog(@"%@",block);
}
打印结果:
<__NSGlobalBlock__: 0x104171100>

MallocBlock示例:

- (void)viewDidLoad {
    int a = 18;
    void (^block)(void) = ^{ // 默认强引用
        NSLog(@"ssl - %d",a);
    };
    NSLog(@"%@",block);
}
打印结果:
<__NSMallocBlock__: 0x6000012b5ce0>

StackBlock示例:

- (void)viewDidLoad {
    int a = 18;
    void (^__weak block)(void) = ^{ // 弱引用
        NSLog(@"ssl - %d",a);
    };
    NSLog(@"%@",block);
}
打印结果:
<__NSStackBlock__: 0x7ffee5569408>

二、block 面试题

1)

- (void)blockDemo {
    
    // 第一次
    NSObject *objc = [NSObject new];
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    
    // 第二次
    void(^strongBlock)(void) = ^{
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    strongBlock();

    // 第三次
    void(^__weak weakBlock)(void) = ^{
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    weakBlock();
    
    // 第四次
    void(^mallocBlock)(void) = [weakBlock copy];
    mallocBlock();
}
执行结果:
---1
---3
---4
---5
  • 第一次打印,只是初始化,引用计数为1
  • 第二次打印,栈blockobjc进行捕获引用计数+1,栈内存赋值到堆内存引用计数再+1,所以这时引用计数为3
  • 第三次打印,因为是弱引用,栈blockobjc进行捕获引用计数+1,所以这时引用计数为4
  • 第四次打印,栈block赋值到堆block引用计数+1,所以这时引用计数为5

2)

- (void)blockDemo {
    int a = 18;
    void(^__weak block1)(void) = nil;
    {
        void(^__weak block2)(void) = ^{
            NSLog(@"---%d", a);
        };
        block1 = block2;
        NSLog(@"1 - %@ - %@",block1,block2);
    }
    block1();
}
执行结果:
1 - <__NSStackBlock__: 0x7ffeeb3b33e0> - <__NSStackBlock__: 0x7ffeeb3b33e0>
---18
  • block2block1都是是栈block,作用域在blockDemo3函数内都是有效的,所以block1()也就可以正常执行。

3),将上面代码中block2__weak修饰符去掉:

image.png

  • block2block1都是是堆blockblock2在代码块内创建,只在代码块中有效,出了代码块就会销毁,block1又指向block2,所以调用block1()会崩溃。

三、block 循环引用

循环引用 示例代码

typedef void(^SSLBlock)(void);
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 循环引用
    self.name = @"ssl";
    self.block = ^(void) {
        NSLog(@"%@",self.name);
    };
    
    // 不循环引用
    [UIView animateWithDuration:1 animations:^{
        NSLog(@"%@",self.name);
    }];
}
  • 第一段代码因为blockself相互持有,所以产生了循环引用。
  • 第二段代码self并没有持有view,所以不会产生循环应用。

__weak 解决循环引用

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.name = @"ssl";
    __weak typeof(self) weakSelf = self;
    self.block = ^(void) {
        NSLog(@"%@",weakSelf.name);
    };
}
  • weakSelf弱引用self,引用计数不会加1,所以即使weakSelfblock持有,也不会增加self的引用计数不会产生循环引用。

如果block中有异步操作,我们又想让页面返回以后操作还能正常执行,那么就需要加入strongSelf

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.name = @"ssl";
    __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是强引用,它是不是又会引发循环引用呢,不会的因为它是个临时变量,作用域结束就释放了。

手动置 nil 解决循环引用

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.name = @"ssl";
    __block ViewController *vc = self;
    self.block = ^(void) {
        NSLog(@"%@",vc.name);
        vc = nil;
    };
    self.block();
}
  • 这种方式也可以解决循环引用,但是有个缺点是block必须被调用,只有block被调用了vc才能被置为nil,否则还是会产生循环引用。
  • 这里必须要使用__block否则会报错,下一篇文章将会进行解析。

传参数 解决循环引用

typedef void(^SSLBlock)(ViewController *vc);

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.name = @"ssl";
    self.block = ^(ViewController *vc) {
        NSLog(@"%@",vc.name);
    }; 
    self.block(self);
}
  • block会对外部变量进行捕获,这里通过参数传进来,参数不需要进行捕获,所以可以解决循环引用。

四、block 循环引用的面试题

1)

static ViewController *staticSelf_;

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self blockWeak_static];
}
- (void)blockWeak_static {
    __weak typeof(self) weakSelf = self;
    staticSelf_ = weakSelf;
}
@end
  • 这段代码也是内存泄漏的,weakSelf是弱引用,但它也是指向了self的内存空间的只是引用计数不加1而已,通过staticSelf_ = weakSelf这段代码,staticSelf_也是指向了self的内存空间,这会使引用计数加1staticSelf_是全局静态变量并不会释放,self也就不会释放因此产生了内存泄漏。

2)

typedef void(^SSLBlock)(void);

@interface ViewController ()
@property (nonatomic, copy) SSLBlock doWork;
@property (nonatomic, copy) SSLBlock doStudent;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self block_weak_strong];
}
- (void)block_weak_strong {
    __weak typeof(self) weakSelf = self;
    self.doWork = ^{
        __strong typeof(self) strongSelf = weakSelf;
        weakSelf.doStudent = ^{
            NSLog(@"%@", strongSelf);
        };
       weakSelf.doStudent();
    };
   self.doWork();
}
@end
  • 这段代码会循环引用,doWork内的strongSelf是当前作用域下的临时变量不会产生循环引用,doStudent会对strongSelf进行捕获,因此产生了循环引用,注意删掉weakSelf.doStudent()一样会循环引用。