前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >BLOCK介绍及常见问题

BLOCK介绍及常见问题

作者头像
用户5521279
发布2019-10-28 15:22:30
6720
发布2019-10-28 15:22:30
举报
文章被收录于专栏:搜狗测试

前言

这段时间小编在整理开发代码问题时发现开发同学在使用block时经常出现一些BUG,其中还有一些隐藏的很深的问题,这里小编就为大家介绍一下block的原理,简单用法和常见问题。

Block概要

Block:带有自动变量的匿名函数。

匿名函数:没有函数名的函数,一对{}包裹的内容是匿名函数的作用域。

Block表达式语法:^ 返回值类型 (参数列表) {表达式}

返回类型为空:

参数列表为空:

声明Block类型变量语法:返回值类型 (^变量名)(参数列表) = Block表达式

声明一个变量名为blk的Block:

Block实现原理

Block实际上是作为极普通的C语言源码来处理的:含有Block语法的源码首先被转换成C语言编译器能处理的源码,再作为普通的C源代码进行编译。首先我们先写一个简单的block。

利用终端编译生成C++代码:

clang -rewrite-objc main.m

代码语言:javascript
复制
static void __main_block_func_0( struct __main_block_impl_0 *__cself) { int count = __cself->count; 
// bound by copy 
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_64_vf2p_jz52yz7x4xtcx55yv0r0000gn_T_main_d2f8d2_mi_0, count); 
}

这是一个函数的实现,对应Block中{}内的内容,这些内容被当做了C语言函数来处理,函数参数中的__cself相当于Objective-C中的self。

代码语言:javascript
复制
struct __main_block_impl_0 { 
  struct __block_impl impl; 
  struct __main_block_desc_0* Desc; //描述Block大小、版本等信息 
  int count; 
  //构造函数函数 
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _count, int flags=0) : count(_count) { 
    impl.isa = &_NSConcreteStackBlock; 
    //在函数栈上声明,则为_NSConcreteStackBlock 
    impl.Flags = flags; 
    impl.FuncPtr = fp; 
    Desc = desc; 
  } 
};

__main_block_impl_0即为main()函数栈上的Block结构体,其中的__block_impl结构体声明如下:

代码语言:javascript
复制
struct __block_impl {
  void *isa;//指明对象的Class
  int Flags;
  int Reserved;
  void *FuncPtr;
};

__block_impl结构体,即为Block的结构体,可理解为Block的类结构。

再看下main()函数翻译的内容:

代码语言:javascript
复制
int main() { 
  int count = 10; 
  void (* blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, count)); 
  ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); 
}

由此,可以看出,Block也是Objective-C中的对象。

Block常见问题

问题1. Block对外部变量进行修改,最后没有生效导致出现BUG

首先,为什么Block不能修改外部自动变量?

自动变量存于栈中,在当前方法执行完,会释放掉。一般来说,在 block 中用的变量值是被复制过来的,自动变量是值类型复制,新开辟栈空间,所以对于新复制变量的修改并不会影响这个变量的真实值(也称只读拷贝)。大多情况下,block是作为参数传递以供后续回调执行的。所以在你想要在block中修改此自动变量时,变量可能已被释放,所以不允许block进行修改是合理的。 对于 static 变量,全局变量,在 block中是有读写权限的,因为此变量存于全局数据区(非栈区),不会随时释放掉,也不会新开辟内存创建变量, block 拷贝的是指向这些变量的指针,可以修改原变量。

那么怎么让自动变量不被释放,还能被修改呢?

__block修饰符把所修饰的变量包装成一个结构体对象,即可完美解决。Block既可以强引用此结构体对象,使之不会自动释放,也可以拷贝到指向该结构体对象的指针,通过指针修改结构体的变量,也就是__block所修饰的自动变量。

问题2. Block在使用过程中出现循环引用

在测试过程中,我们经常遇到内存泄漏问题,这里提到的循环引用就是引起内存泄漏的元凶之一,而且Block的循环引用很难被开发同学察觉,因此也需要我们重点注意。

举例说明:

代码语言:javascript
复制
//DemoObj.m

@interface DemoObj ()
@property (nonatomic, strong) NSMutableArray *myBlocks;
@end

#pragma mark 将代码改为调用self的方法

-(NSMutableArray *)myBlocks
{
    if (_myBlocks == nil) {
        _myBlocks = [NSMutableArray array];
    }
    
    return _myBlocks;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        int(^sum)(int, int) = ^(int x, int y) {
            return [self sum:x y:y];
        };
        [self.myBlocks addObject:sum];
    }
    
    return self;
}

-(int)sum:(int) x y:(int)y
{
    return x + y;
}

#pragma mark 对象被释放时自动调用
- (void)dealloc
{
    NSLog(@"DemoObj被释放");
}

大家阅读完上述代码,请问创建的对象可以被正常销毁吗?

答案是否定的,产生问题的原因就是

代码语言:javascript
复制
int(^sum)(int, int) = ^(int x, int y) {
    return [self sum:x y:y];
};

此时sum的block对self强引用,在加上self对myBlocks强引用:

代码语言:javascript
复制
@property (nonatomic, strong) NSMutableArray *myBlocks;

以及sum block被添加到数组时,会被数组强引用:

代码语言:javascript
复制
[self.myBlocks addObject:sum];

这三个引用之间形成了循环引用,如下图:

那我们如何解除循环引用呢?

1. 在block代码中不要引用self以及其他局部变量

代码语言:javascript
复制
int(^sum)(int, int) = ^(int x, int y) {
    return x + y;
};

2. 使用__weak关键字,可以将局部变量声明为弱引用

代码语言:javascript
复制
- (instancetype)init
{
    self = [super init];
    if (self) {
        __weak DemoObj *weakSelf = self;
        int(^sum)(int, int) = ^(int x, int y) {
            return [weakSelf sum:x y:y];
        };
        [self.myBlocks addObject:sum];
    }
    return self;
}

结语

在开发代码中,Block的使用特别的频繁,因此我们在做代码分析时也要重点关注其代码中的常见问题,以免将这类问题遗漏到测试末期,造成产品delay或产生更大的工作量。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 搜狗测试 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档