如何在curried函数中指定`weak` self

时间:2016-03-28 04:29:06

标签: swift closures

我有一个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 。道歉!

2 个答案:

答案 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)
   }        
}