iOS/Swift - 闭包/完成块和委托/函数之间有什么区别?

Sur*_*ezz 3 delegates closures ios completionhandler swift

我不清楚这两个,现在世界正在转向封闭类型。但我不太清楚这一点。有人可以用一个实时的例子来解释我吗?

Mat*_*lak 5

所以现实生活中两者的例子是这样的:

protocol TestDelegateClassDelegate: class {
    func iAmDone()
}

class TestDelegateClass {
    weak var delegate: TestDelegateClassDelegate?

    func doStuff() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
            self.delegate?.iAmDone()
        }
    }
}

class TestClosureClass {
    var completion: (() -> Void)?

    func doStuff() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
            self.completion?()
        }
    }
}


class ViewController: UIViewController, TestDelegateClassDelegate {

    func iAmDone() {
        print("TestDelegateClassDelegate is done")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let testingDelegate = TestDelegateClass()
        testingDelegate.delegate = self
        testingDelegate.doStuff()

        let testingClosure = TestClosureClass()
        testingClosure.completion = {
            print("TestClosureClass is done")
        }
        testingClosure.doStuff()

    }

}
Run Code Online (Sandbox Code Playgroud)

这里我们有 2 个类TestDelegateClassTestClosureClass。他们每个人都有一个方法doStuff,等待 3 秒,然后向正在监听的人报告,其中一个使用委托过程,另一个使用关闭过程。

尽管他们除了等待什么也不做,但你可以很容易想象他们会将图像上传到服务器并在完成时发出通知。例如,您可能希望在上传过程中运行一个活动指示器,并在完成后停止它。它看起来像这样:

class ViewController: UIViewController, TestDelegateClassDelegate {

    @IBOutlet private var activityIndicator: UIActivityIndicatorView?

    func iAmDone() {
        print("TestDelegateClassDelegate is done")
        activityIndicator?.stopAnimating()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        activityIndicator?.startAnimating()
        let testingDelegate = TestDelegateClass()
        testingDelegate.delegate = self
        testingDelegate.doStuff()

        activityIndicator?.startAnimating()
        let testingClosure = TestClosureClass()
        testingClosure.completion = {
            self.activityIndicator?.stopAnimating()
            print("TestClosureClass is done")
        }
        testingClosure.doStuff()

    }

}
Run Code Online (Sandbox Code Playgroud)

当然,您只会使用这两个过程之一。

您可以看到代码存在巨大差异。要执行委托过程,您需要创建一个协议,在本例中为TestDelegateClassDelegate。协议定义了侦听器的接口。由于iAmDone定义了方法,因此必须在 中定义它ViewController,并且只要将其定义为TestDelegateClassDelegate。否则无法编译。因此,任何声明为的TestDelegateClassDelegate内容都将具有该方法,并且任何类都可以调用它。在我们的例子中,我们有weak var delegate: TestDelegateClassDelegate?. 这就是为什么我们可以调用delegate?.iAmDone()而不关心委托实际上是什么。例如我们可以创建另一个类:

class SomeClass: TestDelegateClassDelegate {
    func iAmDone() {
        print("Something cool happened")
    }
    init() {
        let testingDelegate = TestDelegateClass()
        testingDelegate.delegate = self
        testingDelegate.doStuff()
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,一个很好的例子是UITableView使用delegateand dataSource(两者都是委托,只是属性的命名不同)。表视图将调用您为这些属性设置的任何类的方法,而无需知道该类是什么,只要它对应于给定的协议即可。

通过闭包也可以实现同样的效果。表视图可以使用给出闭包的属性来定义,例如:

tableView.onNumberOfRows { section in
    return 4
}
Run Code Online (Sandbox Code Playgroud)

但这很可能会导致代码一片混乱。在这种情况下,由于潜在的内存泄漏,闭包也会给许多程序员带来麻烦。这并不是说闭包不太安全或者什么,它们只是做了很多你看不到的代码,这些代码可能会产生循环引用。在这种特定情况下,最有可能的泄漏是:

tableView.onNumberOfRows { section in
    return self.dataModel.count
}
Run Code Online (Sandbox Code Playgroud)

解决它的方法很简单

tableView.onNumberOfRows { [weak self] section in
    return self?.dataModel.count ?? 0
}
Run Code Online (Sandbox Code Playgroud)

现在看起来过于复杂。

我不会深入探讨闭包,但最终当您重复调用回调时(例如表视图的情况),您将需要weak在委托或闭包中提供链接。但是,当闭包仅被调用一次(例如上传图像)时,闭包中不需要链接weak(在大多数但不是所有情况下)。

回顾时尽可能多地使用闭包,但一旦将闭包用作属性(具有讽刺意味的是,这就是我给出的示例),就避免或谨慎使用。但你宁愿这样做:

func doSomethingWithClosure(_ completion: @escaping (() -> Void)) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
        completion()
    }
}
Run Code Online (Sandbox Code Playgroud)

并将其用作

    doSomethingWithClosure {
        self.activityIndicator?.stopAnimating()
        print("TestClosureClass is done")
    }
Run Code Online (Sandbox Code Playgroud)

现在这已经消除了所有潜在的风险。我希望这能为您澄清一两件事。