Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Swift 如何使用同步分派工作保留计数?_Swift_Multithreading_Memory Management_Grand Central Dispatch_Retaincount - Fatal编程技术网

Swift 如何使用同步分派工作保留计数?

Swift 如何使用同步分派工作保留计数?,swift,multithreading,memory-management,grand-central-dispatch,retaincount,Swift,Multithreading,Memory Management,Grand Central Dispatch,Retaincount,我试图解释对象的所有权以及GCD是如何工作的。 以下是我学到的东西: 函数将增加其调用对象的保留计数 除非调度块捕获的self较弱,否则它将增加计数 执行调度块后,它释放捕获的对象,因此self的保留计数应该减少。但这不是我在这里看到的。为什么呢 用法: var c: C? = C() print("before func call", CFGetRetainCount(c)) // 2 c?.foo() print("after func call", CFGetRetainCount(c

我试图解释对象的所有权以及GCD是如何工作的。 以下是我学到的东西:

  • 函数将增加其调用对象的保留计数
  • 除非调度块捕获的
    self
    较弱,否则它将增加计数
  • 执行调度块后,它释放捕获的对象,因此
    self
    的保留计数应该减少。但这不是我在这里看到的。为什么呢
用法:

var c: C? = C()
print("before func call", CFGetRetainCount(c)) // 2
c?.foo()
print("after func call", CFGetRetainCount(c)) // 2
有几点想法:

  • 如果您对ARC在幕后保留和释放的确切位置有疑问,只需在“同步后在func内部”之后添加断点,运行它,当它停止时,使用“调试”»“调试工作流”»“始终显示反汇编”,您就可以看到程序集,以准确地看到正在发生的事情。我还建议使用发布版/优化版来实现这一点

    查看程序集,版本位于
    foo
    方法的末尾

  • 正如您所指出的,如果将
    DispatchQueue.global().sync
    调用更改为
    async
    ,您将看到预期的行为

    同样,毫不奇怪,如果执行功能分解,将GCD
    sync
    调用移动到单独的函数中,您将再次看到预期的行为

  • 你说:

    函数将增加其调用对象的保留计数

    为了澄清发生了什么,我想让你们看一下WWDC 2018,视频中大约12:43,他们在视频中讨论了编译器在哪里插入
    retain
    release
    调用,以及它在Swift 4.2中的变化

    在Swift 4.1中,它使用了“自有”调用约定,调用方将在调用函数之前保留对象,被调用函数负责在返回之前执行释放

    在4.2中(如下面的WWDC屏幕快照所示),他们实现了“保证”调用约定,消除了大量冗余的保留和释放调用:

    这样,至少在优化的构建中,会产生更高效、更紧凑的代码。因此,进行发布构建并查看程序集,您将看到这一点

  • 现在,我们来讨论您的问题的根源,即为什么GCD
    sync
    函数的行为与其他场景不同(例如,它的释放调用插入到与具有非转义闭包的其他场景不同的位置)

    这似乎可能与GCD
    sync
    特有的优化有关。具体地说,当您同步地分派到某个后台队列时,而不是停止当前线程,然后在指定队列的一个工作线程上运行代码,编译器足够聪明,可以确定当前线程将处于空闲状态,并且如果可以的话,它将只在当前线程上运行分派的代码。我可以很容易地想象,这个GCD
    sync
    优化可能会在编译器插入发布调用的逻辑中引入皱纹


  • 事实上,发布是在方法结束时完成的,而不是在闭包结束时完成的,这在某种程度上是一个学术问题。我假设他们有充分的理由(或者至少有实际的理由)将此延迟到函数的末尾。重要的是,当您从
    foo
    返回时,保留计数是应该的。

    保留计数与您期望的不同有很多原因。比较,或——在您的情况下,调试模式和发布模式之间的行为是不同的。@MartinR FWIW我用异步块测试了这一点,异步块以非
    的方式捕获了
    self
    ,异步块捕获了self
    ,或者调用了多个异步块。保留计数的增加/减少,除此之外,我能够为所有保留计数合理化。但我明白你的意思,这是未知的。我在这里问我的问题是因为我想有人可能知道答案。或者我对同步块工作原理的理解是incorrect@MartinR我的期望正确吗?在它被执行后应该减少吗?从这个链接中,bbum还提到:“一般来说,您应该将保留计数视为增量。代码会导致保留计数增加或减少。您不能+分配保留计数为1的对象。相反,您可以+分配保留计数为+1的对象。如果希望该对象消失,则需要执行某些操作-释放,始终和最终-使保留计数减1。“这正是我正在做的。因此,这是一个有效的用例。请注意,如果代码是在发布模式下编译的,即使用优化,则保留计数的行为与您预期的一样。为了避免混淆,您介意在我的问题中将
    sync()
    重命名为
    doSyncronously
    ?事实上,我自己也对此感到困惑。我也可以编辑你的答案…请告诉我。那张截图很棒。我必须看那个视频,但我完全明白你的意思,
    版本可以移动/推迟到功能结束
    
    var c: C? = C()
    print("before func call", CFGetRetainCount(c)) // 2
    c?.foo()
    print("after func call", CFGetRetainCount(c)) // 2