我有一个UIViewController
,它在其中一个属性上调用异步函数。对于回调,我想提供一个具有正确参数类型的函数,而不是闭包。
class Fetcher {
func fetch(completion: ([String] -> ())) {
// ... do stuff
completion([ ... ])
}
}
class ViewController: UIViewController {
let fetcher = Fetcher()
func fetch() {
fetcher.fetch(didFetch)
}
func didFetch(result: [String]) {
// handle result
}
}
除了在我完成之后修复的两个对象之间存在保留周期之外,每件事情都可以正常工作:
fetcher.fetch() { [weak self] in
// handle result
}
有没有办法取消初始设置中的保留周期?
修改
我遗漏的东西:Fetcher.fetch
没有说明保留周期将如何发生(例如,它可能会强烈保留闭合),但这是我的意图,因为没有将其标记为@noescape
。道歉!
答案 0 :(得分:3)
为什么你认为有保留周期? Fetcher
未存储传入的completion
引用。如果您在游乐场中运行以下内容:
import UIKit
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
class Fetcher {
func fetch(completion: [String] -> ()) {
dispatch_async(dispatch_get_main_queue()) {
print("fetching")
completion([])
}
}
deinit { print("deinit Fetcher") }
}
class ViewController: UIViewController {
let fetcher = Fetcher()
func fetch() {
fetcher.fetch(didFetch)
}
func didFetch(result: [String]) {
print("did fetch")
}
deinit { print("deinit ViewController") }
}
var vc: ViewController? = ViewController()
vc?.fetch()
vc = nil
...你会看到所有内容都打印出来:
fetching
did fetch
deinit ViewController
deinit Fetcher
但是,您是否出于某种原因存储了参考文献:
class Fetcher {
var completion: ([String] -> ())?
func fetch(completion: [String] -> ()) {
self.completion = completion // causing retain cycle
completion([])
}
}
然后你确实会有一个保留周期,这两个对象永远不会deinit
...
这个问题在我身上越来越多。特别是,由于当我们进入异步执行时,@noescape
目前无法实施,我们能够接近这样的理想吗?有两种方法可以想到,不幸的是,它们都没有为调用者提供编译器支持的保证,尽管它们仍然是原始问题的精神,因为它们不涉及传递闭包:
protocol CompletionHandler : AnyObject { // only needed for `fetch2`
associatedtype Result
func onCompletion(_: Result)
}
extension Fetcher {
func fetch2 <H: CompletionHandler where H.Result == [String]> (handler: H) {
dispatch_async(dispatch_get_main_queue()) { [weak handler] in
print("fetching2")
handler?.onCompletion([])
}
}
func fetch3 <T: AnyObject> (handler: T, curry: T -> [String] -> ()) {
dispatch_async(dispatch_get_main_queue()) { [weak handler] in
print("fetching3")
if let handler = handler {
curry(handler)([])
}
}
}
}
...可以像这样使用(fetch2
假定符合CompletionHandler
):
fetcher.fetch2(self)
fetcher.fetch3(self, curry: ViewController.onCompletion)
......具有以下效果:
var vc: ViewController? = ViewController()
vc?.fetch()
vc = nil
...打印:
deinit ViewController
deinit Fetcher
fetching2
fetching3
(与上面第一个解决方案的控制台输出相比,请参阅下面评论中与@Sulthan的讨论)
答案 1 :(得分:1)
如果要将方法传递给闭包参数,则必须捕获self
,无法解决此问题。
但是,您可以更明确地将方法调用包装在新的闭包中:
func fetch() {
fetcher.fetch { [weak self] in
self?.didFetch($0)
}
}