Objective-C阻止来龙去脉

注意 :此博客可作为在Objective-C块上的学习和共享经验,如果有不正确的地方,请随时发表评论。 另外,这个博客的很大一部分不是原创的,而是由我从其他博客或SO答案中挑选并验证的。 本文末尾提供了参考。

大多数iOS开发人员都处理了Objective-C块。 当您执行异步操作或要同步其他操作时,阻止功能特别有用。 那么,遮挡如何在后台工作呢?

一位同事写这个博客给我带来了麻烦。 这是一个简化的代码片段:

  func recreate(){ 
sessionDelegate.sessionDidBecomeInvalidWithError = {
[弱自我]会话,发生错误
守卫让strongSelf =自我else {return}
strongSelf.sessionDelegate = SessionDelegate()
session.finishTasksAndInvalidate()
}
}

问题是:该程序会崩溃吗?

可能导致崩溃的原因是: sessionDidBecomeInvalidWithError块归sessionDelegate所有。 如果将self的sessionDelegate重新分配给新的SessionDelegate()实例,则原始委托将被释放。 那么,原始代表的阻止仍会存在吗? 根据我们的实验,答案是: 它不会崩溃

但为什么? 我在网上做了很多研究,以下是我在网上发现的一些声明,并试图进行验证:

  1. 默认情况下,块是Objective-C中唯一在堆栈中分配的对象。
  2. 块的大小是固定的,一旦创建了给定的块就无法对其进行修改。 块在整个执行过程中是恒定的。
  3. 每次将复制消息发送到块时,都会将其移动到堆中(如果尚未复制)。
  4. 仅用作函数或方法调用参数的块可以保留为堆栈块,但是其他任何ARC保留该块的地方都将复制该块。
  5. 当一个块不捕获变量时,它就像一个普通函数。 Clang通过使此类块成为“全局块”来实现这一点

对于iOS块的详细信息,StackOverflow有一个很好的答案,调试非常有趣。 我重做了他的实验,这是该实验的摘要。

 当评估Block文字表达式时,基于堆栈的结构将按以下方式初始化: 
  1.静态描述符结构的声明和初始化如下: 
 一种。  invoke函数指针设置为一个函数,该函数将Block结构作为其第一个参数,并将其余参数(如果有)作为Block的参数,并执行Block复合语句。 
  b。 大小字段设置为以下块文字结构的大小。 
  C。 如果Block常量需要将copy_helper和dispose_helper函数指针设置为相应的助手函数。 
  2.创建并初始化堆栈(或全局)Block文字数据结构,如下所示: 
 一种。  isa字段设置为外部_NSConcreteStackBlock的地址,该地址是libSystem中提供的未初始化内存的块,或者是_NSConcreteGlobalBlock(如果这是静态或文件级的Block文字)。 
  b。 除非有导入到Block中的变量需要程序级Block_copy()和Block_release()操作的辅助函数(在这种情况下,设置(1 << 25)标志位),否则将flags字段设置为零。声明和初始化如下: 

调试和探索的步骤:

  1. _Block_copy_internal的 符号断点放在Xcode中。
  2. 使用以下代码创建示例项目:

另一个有趣的发现是,当您将其传递给函数时,ARC将会启动:

因此,每次保留一个块变量时,都将调用_Block_copy_internal方法。

要验证#3 ,请使用以下代码:

  int x0 =参数 
^ {int i = x0; i ++;}

没有_Block_copy_internal调用,但是,如果在该块上调用copy,则将调用_Block_copy_internal

  int x0 =参数 
[^ {int i = x0; i ++;}复制];

为了验证#5 ,让我们看一下源代码:

这是用于检查某个块是否可以是全局块的代码-该块是引用还是复制到其他变量。

  /// CanBlockBeGlobal-给定一个BlockInfo结构,确定是否可以将一个块 
///声明为全局变量,而不是在堆栈上。
静态布尔CanBlockBeGlobal(const CodeGenFunction :: BlockInfo&Info){
返回Info.ByRefDeclRefs.empty()&& Info.ByCopyDeclRefs.empty();
}

如果是这样,它将调用:

  llvm ::常数* 
BlockModule :: GetAddrOfGlobalBlock(const BlockExpr * BE,const char * n);

要生成全局块,无论是否在其上调用复制 ,都不会在堆中分配全局块。 以下代码不会触发_Block_copy_internal

  [^ {int i;  i ++;}复制]; 

但是,如果您引用外部范围:

  int x0 =参数 
[^ {int i; i ++;}复制];

_Block_copy_internal将被触发。

下一个实验是:#2执行期间Block是否恒定?

块的结构:

  struct Block_layout { 
无效* isa;
volatile int32_t标志; //包含引用计数
int32_t保留;
void(* invoke)(无效*,...);
struct Block_descriptor_1 *描述符;
//导入的变量
};

但是,从未像在clang中那样显式声明结构,而是以编程方式生成。

当评估Block文字表达式时,基于堆栈的结构将按以下方式初始化:

1.静态描述符结构的声明和初始化如下:

一种。 invoke函数指针设置为一个函数,该函数将Block结构作为其第一个参数,并将其余参数传递给Block并执行Block复合语句。

b。 大小字段设置为以下块文字结构的大小。

C。 如果Block常量需要将copy_helper和dispose_helper函数指针设置为相应的助手函数。

2.创建并初始化堆栈(或全局)Block文字数据结构,如下所示:

一种。 isa字段设置为外部_NSConcreteStackBlock的地址,该地址是libSystem中提供的未初始化内存的块,或者是_NSConcreteGlobalBlock(如果这是静态或文件级的Block文字)。

b。 除非有导入到Block中的变量需要程序级Block_copy()和Block_release()操作的帮助函数,否则flags字段将设置为零。

例如,Block文字表达式:

  ^ {printf(“ hello world \ n ”);  } 

会产生:

  struct __block_literal_1 { 
无效* isa;
整数标志;
保留
void(* invoke)( struct __block_literal_1 *);
struct __block_descriptor_1 *描述符;
};

无效__block_invoke_1( 结构 __block_literal_1 * _block){
printf(“ hello world \ n ”);
}

静态 结构 __block_descriptor_1 {
unsigned long int保留;
unsigned long int Block_size;
} __block_descriptor_1 = {0, sizeof结构 __block_literal_1),__block_invoke_1};

块文字本身出现的位置:

  struct __block_literal_1 _block_literal = { 
&_NSConcreteStackBlock,
(1 << 29),,
__block_invoke_1,
&__ block_descriptor_1
};

如果它是全局或静态局部变量,则将其初始化如下:

  struct __block_literal_1 __block_literal_1 = { 
&_NSConcreteGlobalBlock,
(1 << 28)|(1 << 29),,
__block_invoke_1,
&__ block_descriptor_1
};

这代的代码在这里。

因此,由于这个原因,必须测试块本身。

(未完待续)

参考

块和内存管理(堆栈与堆)
使用ARC(自动引用计数),使用块已成为一项较容易的任务,但是仍然存在一些问题…… www.solstice.com 是否仍应在ARC下复制/ Block_copy块?
编辑:原来,检查“捕获的”变量的地址很难解释,而且并非总是如此… stackoverflow.com 如何实施区块(及其后果)
这篇文章介绍了clang如何实现块以及这种实现如何导致许多奇怪的行为…… www.cocoawithlove.com 块实施规范– Clang 5文档
编辑描述 clang.llvm.org