允许完成处理程序在创建它的本地范围内存活

Allowing a completion handler to outlive the local scope where it's created

我有一个 class 实现了 XMLParserDelegate 协议,并且在初始化期间它获取一个字符串和一个完成处理程序作为参数。我试图在解析字符串后调用完成处理程序,但由于 class 的释放,XMLParserDelegate 方法未被访问。

class MyXMLParser: NSObject, XMLParserDelegate {

private (set) var parser: XMLParser?
private (set) var completion: ((String?) -> Void)?

public init(_ xml: String, _ completion: @escaping ((String?) -> Void)) {
    let data = xml.data(using: String.Encoding.utf8)
    self.parser = XMLParser(data: data ?? Data())
    self.completion = completion
    self.parser?.delegate = self
    self.parser?.parse()
}

deinit {
    // Being called before Parser methods
}

// MARK: - Parser delegate methods

func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
    // Custom implementation
}

func parserDidEndDocument(_ parser: XMLParser) {
    // Custom implementation
    self.completion?("Test")
}

func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
    self.completion?(nil)
}

}

我是这样称呼它的:

func someFunc() {
    let parser = MyXMLParser(someXMLString) { text in
        // Custom implementation
    }
}

我希望闭包在获取值之前保持活动状态,而不是在函数内部的局部作用域结束后取消初始化。我想要实现的一个很好的例子是 UIView.animate() 完成块,即使它位于某个函数内部也不会被释放。

您需要将 MyXMLParser 存储在局部变量以外的其他地方。就这么简单。

注意 UIView.animate(...) 是一个 class 方法。这意味着 class 以某种方式参与了生命周期管理。

一个简单的模仿方法是稍微更改您的界面,这样您就可以让 class 为您完成,而不是直接创建 MyXMLParser

class MyXMLParser : NSObject, XMLParserDelegate {

    private static var createdParsers: Set<MyXMLParser> = []

    static func parse(_ xml: String, _ completion: @escaping (String?) -> Void) {
        let newParser = MyXMLParser(xml, completion)
        self.createdParsers.insert(newParser)
    }

    private static func parserDidEndParsing(_ parser: MyXMLParser) {
        self.createdParsers.remove(parser)
    }

    private let parser: XMLParser
    private let completion: (String?) -> Void

    private init(_ xml: String, _ completion: @escaping (String?) -> Void) {
        // Same as existing code
    }

    //...

    func parserDidEndDocument(_ parser: XMLParser) {
        //...
        MyXMLParser.parserDidEndParsing(self)
    }
}

此处,一个单独的解析器由其 class 拥有,您可以通过调用 MyXMLParser.parse(myXmlString) { (text) in /* whatever */ } 创建一个解析器。重要的是要注意 parserDidEndDocument 中的清理步骤,从 class 的存储集合中删除实例,这样您就不会留下未使用的实例。

您需要确保您的 parser 对象保持不变。现在它会在 someFunc 结束后立即消失。您可能希望将 parser 声明为 class 级变量。