IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> iOS学习之block不为人知的一面(附:面试题) -> 正文阅读

[移动开发]iOS学习之block不为人知的一面(附:面试题)

前言

说起block,想必作为一名iOS开发人员,不可能没有接触过,但是用的多不代表你就真正懂了,本篇的目的也就是巩固一下对于block的学习,以及一些坑点和面试题进行分析,看我们到底有多懂block😄

一.block的分类

在这里插入图片描述

结合对于block的分类,我们分别把三种block展示出来,代码如下

NSGlobalBlock

    void (^block)(void) = ^{
        NSLog(@"YCX");
    };
    NSLog(@"%@",block);

打印结果

在这里插入图片描述
NSMallocBlock

    int a = 10;
    void (^block)(void) = ^{
        NSLog(@"Cooci - %d",a);
    };
    NSLog(@"%@",block);

打印结果

在这里插入图片描述

NSStackBlock

    int a = 10;
    void (^ __weak block)(void) = ^{
        NSLog(@"Cooci - %d",a);
    };
    NSLog(@"%@",block);

打印结果

在这里插入图片描述
说明:前面也说了堆栈block区别在于是否被强引用或者赋值给copy修饰的属性变量,void (^ block)(void) =不就是一个引用的过程吗,而下面第三个例子之所以是栈block,主要是用__weak修饰了,弱引用没有增加引用计数而已

二.block的引用问题

1.block拷贝到堆block

  • 1.手动copy
  • 2.block作为返回值
  • 3.被强引用或者copy修饰
  • 4.系统API包含usingBlock

2.循环引用

正常情况下
在这里插入图片描述
为什么会产生循环引用,如下图所示
在这里插入图片描述

3.解决循环引用的方案

案例:常见的循环引用 self持有block block持有self

    self.name = @"ycx";
    
    self.block = ^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",self.name);
        });
    };

解决方案1

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

解析:这种方式也是最常见的方式,俗称强弱共舞,通过__weak和__strong,从而达到改变引用关系,同时改变作用域,使得只有在block内容执行完毕后,才可以释放self

解决方案2

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

解析:通过__block修饰的临时变量vc去引用self,所以只有当block里面执行完毕,并且vc置空,才可以正常释放self

解决方案3

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

解析:通过传参的方式,其实和方案2原理有些相似,只不过通过传参的方式,那么vc的作用域默认就是block代码块的范围,执行完毕后,vc置空,自然就可以正常释放self

当然了,还有其他的方案可以解决,后续会补上的

三.面试题分析

例题1

- (void)blockDemo2{
    
    NSObject *objc = [NSObject new];
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 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();
}

解析:打印结果是1345,首先一点每一个block里面都有引用objc,所以每个地方都会是引用计数+1,这是没问题的,但是为什么strongBlock里面会是引用计数+2呢?在这里我们要明白,block本身是栈block,只不过它的代码块当中引用了objc,所以才使得objc引用计数+1,但是因为栈block又被strongBlock所持有,也就是所谓的copy修饰的变量持有,意味着又拷贝了一份到了堆中,也就是堆block,因此使得引用计数再+1,而weakBlock本身是栈block,但是下面的mallocBlock又对weakBlock进行了一次copy,所以计数再+1,其实strongBlock完全可以看成下面2步操作的结合,通过这题也就充分说明了堆栈block的区别了

例题2

// 底层  参考底层仿写的block
struct _LGBlock {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    // 函数指针
    LGBlockInvokeFunction invoke;
    struct _LGBlockDescriptor1 *descriptor;
};

- (void)blockDemo1{
    int a = 0;
    void(^ __weak weakBlock)(void) = ^{
        NSLog(@"-----%d", a);
    };
    struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
  
    // 深浅拷贝
    id __strong strongBlock = weakBlock;
    //id __strong strongBlock = [weakBlock copy];
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;
    strongBlock1();
}

解析:这题主要是为了区分block的深浅拷贝问题,_LGBlock只是参考block底层源码,写的一个自定义block,为的是便于修改block的一些属性,上面demo方法运行会崩溃,因为strongBlock1引用的strongBlock,而strongBlock引用weakBlock,也就是说他们持有的是同一个block,而blc->invoke = nil,是把block中的一个属性置空了,自然就会运行block崩溃,如果该用注释的copy方法,就可以正常运行

例题3

问题:a能不能打印出来,weakBlock,strongBlock分别是什么类型block

- (void)blockDemo3{
    int a= 10;
    void(^__weak weakBlock)(void) = nil;
    {
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"---%d", a);
        };
        weakBlock = strongBlock;
        NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
    }
    weakBlock();
}

解析:可以,首先weakBlock,局部变量的作用域为blockDemo3方法中,而strongBlock也是一个栈block,而weakBlock弱引用了strongBlock,所以两者都是栈block,虽然出了括号,但是因为weakBlock还没出作用域,所以可以执行

例题4

问题同例题3

- (void)blockDemo3{
    int a= 10;
    void(^__weak weakBlock)(void) = nil;
    {
        void(^ strongBlock)(void) = ^{
            NSLog(@"---%d", a);
        };
        weakBlock = strongBlock;
        NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
    }
    weakBlock();
}

解析:首先可以确定weakBlock和strongBlock都是堆block,而堆block的作用域就是在括号内,所以出了括号就释放了,因此执行堆weakBlock()是会崩溃,当然从这里也可以看出堆栈的释放内存的差异

例题5

问题:是否产生内存泄露

static ViewController *staticSelf;
- (void)viewDidLoad {
	[self blockWeak_static];
}
- (void)blockWeak_static {
    __weak typeof(self) weakSelf = self;
    staticSelf = weakSelf;
}

解析:会,首先weakSelfself是映射关系,弱引用,指向同一片内存空间,然而全局静态变量staticSelf引用了weakSelf,其实也就是引用了这块内存空间,但是全局静态变量没办法释放,所以self也释放不掉

例题6

问题:是否产生内存泄露

@property (nonatomic, copy) YCXBlock doWork;
@property (nonatomic, copy) YCXBlock doStudent;

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

解析:会,可以把这个block代码拆分看,首先按照我们的理解__weak和__strong,延迟了self的释放是没问题的,但是在weakSelf.doStudentblock中,再次引用了strongSelf,所以引用计数再次+1,所以最终self还是多引用了一次,导致最终无法释放。

今天的分享就到次为止了,当然了后续如果有更多比较有趣的block题目也会补充进来,下一篇会对block的底层源码进行分析,敬请期待吧😄

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-23 16:48:38  更:2021-08-23 16:50:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 9:45:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码