【问题标题】:Closure recursion and retain cycles闭包递归和保留循环
【发布时间】:2018-11-14 19:31:11
【问题描述】:

我的闭包会自行保留。它会导致捕获内部的所有其他对象。我可以使用弱引用传递这样的对象,但它不能解决保留周期的问题。在没有保留循环的情况下使用闭包进行递归的正确方法是什么?

class Foo {
  var s = "Bar"
  deinit {
    print("deinit") // Won't be executed!
  }
}

class TestVC: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    let foo = Foo() // Weak works, but not the right solution.
    var closure: () -> Void = { return }
    closure = {
      print(foo.s)
      if true {
        return
      } else {
        closure()
      }
    }
  }

}

【问题讨论】:

  • @matt 我已经更改了代码。只有当 foo 很弱时才会调用 Deinit。但它并没有解决闭包保留周期问题。

标签: ios swift


【解决方案1】:

您有一个不寻常的设置,您的闭包会自行保留。请注意,Swift 不允许您创建对闭包的弱引用。

要中断保留循环,请在递归的基本情况下将closure 设置为{ }。这是一个测试 macOS 命令行程序:

func test() {
    var closure: ((Int) -> ()) = { _ in }
    closure = { i in
        if i < 10 {
            closure(i + 1)
        } else {
            // Comment out this line for unbounded memory consumption.
            closure = { _ in }
        }
    }
    closure(0)
}

while true {
    test()
}

如果你运行它,它的内存消耗是平稳的。

如果您注释掉基本情况中重置closure 的行,它的内存消耗会无限增长。

【讨论】:

  • 然而闭包是一种引用类型,所以弱引用似乎是可能的和可取的。参见例如bugs.swift.org/browse/SR-8205
  • closure = { _ in } 对我有帮助!谢谢!
【解决方案2】:

您的closure 持有foo 实例引用。 foo 将在closure 发布后立即发布。

closure 正在调用自己。如果我们在closure 中传递弱self,那应该没问题。或者通过重置closure

下面的代码应该可以正常工作。

var closure: () -> Void = { return }

override func viewDidLoad() {
    super.viewDidLoad()

    let foo = Foo()

    closure = { [weak self] in
        print(foo.s)
        if true {
            return
        } else {
            self?.closure()
        }
    }
}

或在closure 中初始化foo

override func viewDidLoad() {
    super.viewDidLoad()

    var closure: () -> Void = { return }
    closure = { [weak self] in
        let foo = Foo()
        print(foo.s)
        if true {
            return
        } else {
            self?.closure()
        }
    }
}

【讨论】:

  • 不,不会。我已经更改了上面的代码以使其更清晰。
【解决方案3】:

把你的闭包变成a nested function:

class Foo {
  var s = "Bar"
  deinit {
    print("deinit")
  }
}

class TestVC: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    let foo = Foo()
    func nestedFunction() {
      print(foo.s)
      if true {
        return
      } else {
        nestedFunction()
      }
    }
    nestedFunction()
  }

}

在 Swift 中,嵌套函数可以同步(递归函数)或异步(通常用于异步迭代)引用自己,可以在没有任何引用循环的情况下这样做,并且可以像闭包一样捕获变量。您甚至可以拥有相互递归的嵌套函数。

可以在完成后将包含闭包的变量重置为虚拟闭包,我并不是说这不起作用,但这很容易出错,尤其是当闭包异步调用自身时:在这种情况下,重置也必须异步完成。最好静态地确保没有引用循环,这在 Swift 中的大多数其他地方都可以做到。

(由于 gcc 在 C 语言中的实现引入了安全漏洞,因为尝试将闭包引用压缩到 C 函数指针(即代码地址)中,这个概念曾经有一个不好的说法,但是 Swift 嵌套函数与此无关)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-21
    • 2012-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-15
    • 1970-01-01
    • 2013-11-17
    相关资源
    最近更新 更多