【问题标题】:Capture a method weakly弱捕获方法
【发布时间】:2020-01-11 02:20:20
【问题描述】:

注意:这个问题与this one基本相同,但针对Swift 4或5。

假设我有一个捕获闭包的类:

class CallbackHolder {
  typealias Callback = (String) -> Void

  var callback: Callback

  init(_ callback: @escaping Callback) {
    self.callback = callback
  }

  func useCallback() {
    self.callback("Hi!")
  }
}

这个类只是在一个变量中保存一个回调,并且有一个使用该回调的函数。

现在,假设我有一个拥有这样一个回调持有者的客户端类。此类希望回调持有者调用其方法之一作为回调:

class Client {
  func callback(string: String) {
    print(string)
  }

  lazy var callbackOwner = CallbackOwner(callback: callback)

  deinit {
    print("deinit")
  }
}

这个类有一个回调,一个调用该回调的回调所有者,以及一个打印一些东西的 deinit,以便我们知道我们是否有一个保留循环(没有 deinit = 保留循环)。

我们可以使用以下测试函数来测试我们的设置:

func test() {
   Client().callbackOwner.useCallback()
}

我们希望测试函数同时打印Hi!deinit,以便我们知道回调有效,并且客户端不会受到保留周期的影响。

上面的Client实现其实确实有一个retain循环——把callback方法传给回调的owner会导致owner强retain client,造成循环。

当然,我们可以通过替换来修复循环

  lazy var callbackOwner = CallbackOwner(callback: callback)

  lazy var callbackOwner = CallbackOwner(callback: { [weak self] in 
    self?.callback($0) 
  })

这可行,但是:

  • 很乏味(比较我们现在需要的代码量)
  • 这很危险(我的 CallbackOwner 类的每个新客户端都必须记住这样做,即使原来的方式是完全有效的语法,否则他们会得到一个保留循环)

所以我正在寻找更好的方法。我想在 CallbackOwner 上弱捕获回调,而不是在客户端。这样,新客户就不必意识到保留周期的危险,他们可以以最直观的方式使用我的 CallbackOwner。

所以我尝试将CallbackOwner的回调属性改为

  weak var callback: Callback?

当然,Swift 只允许我们弱捕获类类型,而不是闭包或方法。

在写this 答案的时候,似乎没有办法实现我正在寻找的东西,但那是 4 年前的事了。在最近的 Swift 版本中是否有任何进展可以让我将方法传递给闭包捕获对象而不会导致保留循环?

【问题讨论】:

  • 前面的答案还是正确的。什么都没有改变。

标签: swift swift4 swift5


【解决方案1】:

一个明显的方法是在Client中不引用CallbackHolder,即

class Client {
    func callback(string: String) {
        print(string)
    }

    var callbackOwner: CallbackHolder { return CallbackHolder(callback) }

    deinit {
        print("deinit")
    }
}

在上述情况下,我可能会将 CallbackHolder 设为 struct 而不是类。

就我个人而言,我认为将回调封装在一个类中根本没有任何价值。只需传递回调,甚至是Client。也许制定一个协议让它遵守

protocol Callbackable
{
    func callback(string: String)
}

extension Callbackable
{
    func useCallback() {
        self.callback(string: "Hi!")
    }
}


class Client: Callbackable {
    func callback(string: String) {
        print(string)
    }

    deinit {
        print("deinit")
    }
}

func test(thing: Callbackable) {
    thing.useCallback()
}

test(thing: Client())

【讨论】:

  • 在我的简单示例中,是的,消除回调持有者可能会更好。我的真实世界场景是一个去抖器类。单个客户端类可能有多个去抖动器用于去抖动各种操作,并且每个去抖动器应该运行不同的回调。因此,重要的是客户端实际初始化并引用那些去抖动器对象(否则它们将不能作为去抖动器工作),并且去抖动器可以使用任何方法或闭包作为回调,而不是单个、固定名称的函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-04
  • 1970-01-01
  • 1970-01-01
  • 2018-07-11
  • 1970-01-01
相关资源
最近更新 更多