深入理解block

2010年WWDC发布iOS4时Apple对Objective-C进行了一次重要的升级:支持Block。说到底这东西就是闭包,其他高级语音例如Java和C++已有支持,第一次使用Block感觉满简单好用的,但是慢慢也遇到很多坑。本文聊聊ARC和non-ARC下Block使用中的引用循环问题,最近遇到了好几次这种问题,还是深入记录下。先来套题目热热身,貌似能够全部答对的人蛮少的

Block实现原理

首先探究下Block的实现原理,由于Objective-C是C语言的超集,既然OC中的NSObject对象其实是由C语言的struct+isa指针实现的,那么Block的内部实现估计也一样,以下三篇Blog对Block的实现机制做了详细研究:

虽然实现细节看着头痛,不过发现Block果然是和OC中的NSObject类似,也是用struct实现出来的东西。这个是LLVM项目compiler-rt分析的block头文Block_private.h头文件中关于Block的struct声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

我们发现Block_layout中也有一个isa指针,像极了NSobject内部实现struct中的isa指针。这里的isa可能指向三种类型之一的Block:

  • _NSConcreteGlobalBlock:全局类型Block,在编译器就已经确定,直接放在代码段__TEXT上。直接在NSLog中打印的类型为__NSGlobalBlock__。
  • _NSConcreteStackBlock:位于栈上分配的Block,即__NSStackBlock__。
  • _NSConcreteMallocBlock:位于堆上分配的Block,即__NSMallocBlock__。

为什么会有这么多种类呢?首先来看全局类型Block,看例子:

1
2
3
4
5
6
7
8
9
10
11
12
void addBlock(NSMutableArray *array) {
  [array addObject:^{
    printf("global block\n");
  }];
}

void example() {
  NSMutableArray *array = [NSMutableArray array];
  addBlock(array);
  void (^block)() = [array objectAtIndex:0];
  block();
}

为什么addBlock中添加到array中的Block属于全局Block呢?因为它不需要运行时(Runtime)任何的状态来改变行为,不需要放在堆上或者栈上,直接编译后在代码段中即可,就像个c函数一样。这种类型的Block在ARC和non-ARC情况下没有差别。

这个Block访问了作用域外的变量d,在实现上就是这个block会多一个成员变量对应这个d,在赋值block时会将方法exmpale中的d变量值复制到成员变量中,从而实现访问。

1
2
3
4
5
6
7
void example() {
  int d = 5;
  void (^block)() = ^() {
      printf("%d\n", d);
  };
  block();
}

如果要修改d呢?:

1
2
3
4
5
6
7
8
9
void example() {
  int d = 5;
  void (^block)() = ^() {
      d++;
      printf("%d\n", d);
  };
  block();
  printf("%d\n", d);
}

由于局部变量d和这个block的实现不在同一作用域,仅仅在调用过程中用到了值传递,所以不能直接修改,而需要加一个标识符__block int d = 5;,那么block就可以实现对这个局部变量的修改了。如果是这种block标识的变量,在Block实现中不再是简单的一个成员变量,而是对应一个新的结构体表示这个block变量。block的本质是引入了一个新的Block_byref{$var_name}{$index}结构体,被block关键字修饰的变量就被放到这个结构体中。另外,block结构体通过引入Block_byref{$var_name}{$index}指针类型的成员,得以间接访问到Block的外部变量。这样对Block外的变量访问从值传递转变为引用,从而有了修改内容的能力。

正常我们使用Block是在栈上生成的,离开了栈作用域便释放了,如果copy一个Block,那么会将这个Block copy到堆上分配,这样就不再受栈的限制,可以随意使用啦。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef void (^TestBlock)();

TestBlock getBlock() {
  char e = ‘E‘;
  void (^returnedBlock)() = ^{
    printf("%c\n", e);
  };
  return returnedBlock;
}

void example() {
  TestBlock block = getBlock();
  block();
}

函数getBlock中声明并赋值的returnedBlock,一开始是在栈上分配的,属于NSStackBlock,如果是non-ARC情况下return这个NSStackBlock,那么其实已经被销毁了,在函数中example()使用时就会crash。如果是ARC情况下,getBlock返回的block会自动copy到堆上,那么block的类型就是NSMallocBlock,可以在example()中继续使用。要在Non-ARC情况下正常运行,那么就应该修改为:

1
2
3
4
5
6
7
TestBlock getBlock() {
  char e = ‘E‘;
  void (^returnedBlock)() = ^{
    printf("%c\n", e);
  };
  return [[returnedBlock copy] autorelease];
}

Block中的循环引用问题

扯了这么多,回到Block的循环引用问题,由于我们很多行为会导致Block的copy,而当Block被copy时,会对block中用到的对象产生强引用(ARC下)或者引用计数加一(non-ARC下)。

如果遇到这种情况:

1
2
3
4
5
6
7
8
9
@property(nonatomic, readwrite, copy) completionBlock completionBlock;

//========================================
self.completionBlock = ^ {
        if (self.success) {
            self.success(self.responseData);
        }
    }
};

对象有一个Block属性,然而这个Block属性中又引用了对象的其他成员变量,那么就会对这个变量本身产生强应用,那么变量本身和他自己的Block属性就形成了循环引用。在ARC下需要修改成这样:

1
2
3
4
5
6
7
8
9
@property(nonatomic, readwrite, copy) completionBlock completionBlock;

//========================================
__weak typeof(self) weakSelf = self;
self.completionBlock = ^ {
    if (weakSelf.success) {
        weakSelf.success(weakSelf.responseData);
    }
};

也就是生成一个对自身对象的弱引用,如果是倒霉催的项目还需要支持iOS4.3,就用__unsafe_unretained替代__weak。如果是non-ARC环境下就将__weak替换为__block即可。non-ARC情况下,__block变量的含义是在Block中引入一个新的结构体成员变量指向这个__block变量,那么__block typeof(self) weakSelf = self;就表示Block别再对self对象retain啦,这就打破了循环引用。

时间: 2024-10-20 10:29:36

深入理解block的相关文章

08.存储Cinder→1.理解Block Storage

操作系统获得存储空间的方式一般有二种: 通过某种协议(SAS,SCSI,SAN,iSCSI 等)挂接裸硬盘,然后分区.格式化.创建文件系统:或者直接使用裸硬盘存储数据(数据库) 介绍: 第一种裸硬盘的方式叫做Block Storage(块存储),每个裸硬盘通常也称作Volume(卷). 使用lvm划分出的逻辑卷也是块存储,因为操作系统是区分不出到底是逻辑还是物理的,比如,有5个逻辑卷,它一概就认为只是5块裸的物理硬盘而已. 用处:是和主机打交道的, 如插一块硬盘 通过 NFS.CIFS 等 协议

08.存储Cinder→2.理解Block Storage Service

Block Storage Service提供对 volume 从创建到删除整个生命周期的管理.从 instance 的角度看,挂载的每一个 Volume 都是一块硬盘. OpenStack 提供 Block Storage Service 的是 Cinder,其具体功能是: 提供REST API使用户能够查询和管理 volume.volume snapshot 以及 volume type 提供scheduler调度volume 创建请求,合理优化存储资源的分配 通过driver 架构支持多种

Block源码解析和深入理解

Block源码解析和深入理解 Block的本质 Block是"带有自动变量值的匿名函数". 我们通过Clang(LLVM编译器)来将OC的代码转换成C++源码的形式,通过如下命令: clang -rewrite-objc 源代码文件名 下面,我们要转换的Block语法 1 2 3 4 5 6 7 int main(int argc, const char * argv[]) { void (^blk)(void) = ^{ printf("Block\n"); };

关于Block

.是一段代码块,只有调用的时候才执行. 2.是匿名函数,只有函数体,没有函数名. 3.它是一种数据类型.这个类型叫:block类型 4.可以定义成临时变量 5.可以做为参数传递 6.可以定义属性 7.功能强大,使用灵活,难以理解 block回调 回调:可以传递事件,是某个事件发生之后能够通知到其他类

Block、委托、回调函数原理剖析(在Object C语境)——这样讲还不懂,根本不可能!

开篇:要想理解Block和委托,最快的方法是搞明白“回调函数”这个概念. 做为初级选手,我们把Block.委托.回调函数,视为同一原理的三种不同名称.也就是说,现在,我们把这三个名词当成一回事.在这篇文章内,Block就是回调函数,委托也是回调函数,不再作详细的区分了.OK,Action! 那么,什么是回调函数?“回调”概念的主语是谁? 举个栗子(伪代码): 首先有个类,我们姑且称之为A类吧. A.h 文件 //声明回调函数:给指定的员工发放工资 -(void)paySalaryForStaff

OC语言的特性(二)-Block

本篇文章的主要内容 了解何谓block. 了解block的使用方法. Block 是iOS在4.0版本之后新增的程序语法. 在iOS SDK 4.0之后,Block几乎出现在所有新版的API之中,换句话说,如果不了解Block这个概念就无法使用SDK 4.0版本以后的新功能,因此虽然Block本身的语法有点难度,但为了使用iOS的新功能我们还是得硬着头皮去了解这个新的程序概念. 一.看一看什么是Block 我们使用'^'运算符来声明一个Block变量,而且在声明完一个Block变量后要像声明普通

iOS中Block介绍(一)基础

ios开发block的使用指南,以及深入理解block的内存管理,也适用于osx开发.讨论范围:block的使用,内存管理,内部实现.不包含的内容:gc arc下的block内存,block在c++中的使用. AD: 一.概述 Block是C级别的语法和运行时特性.Block比较类似C函数,但是Block比之C函数,其灵活性体现在栈内存.堆内存的引用,我们甚至可以将一个Block作为参数传给其他的函数或者Block. 二.热身 先看一个比较简单的Block例子: int multiplier =

理解 Cinder 架构 - 每天5分钟玩转 OpenStack(45)

从本节开始我们学习 OpenStack 的 Block Storage Service,Cinder 理解 Block Storage 操作系统获得存储空间的方式一般有两种: 通过某种协议(SAS,SCSI,SAN,iSCSI 等)挂接裸硬盘,然后分区.格式化.创建文件系统:或者直接使用裸硬盘存储数据(数据库) 通过 NFS.CIFS 等 协议,mount 远程的文件系统 第一种裸硬盘的方式叫做 Block Storage(块存储),每个裸硬盘通常也称作 Volume(卷) 第二种叫做文件系统存

iOS开发 -------- Block技术中的weak - strong

一 Block是什么? 我们使用^运算符来声明一个Block变量,而且在声明完一个Block变量后要像声明普通变量一样,后面要加; 声明Block变量 int (^block)(int) = NULL; Block变量的语法 数据返回值类型 (^变量名)(参数列表) = NULL 赋值Block变量 block = ^(int m) { return m * m; }; 使用Block变量 // 通过使用block变量,计算整型常量10的平方,并且打印在控制器输出 NSLog(@"10的平方是: