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

标签 ios swift delegates closures completionhandler

我不清楚这两个,现在世界正在转向闭包类型。但我不是很清楚这一点。有人可以用实时示例向我解释吗?

最佳答案

因此,两者的现实生活示例如下:

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()

    }

}

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

虽然他们除了等待什么都不做,但您可以很容易地想象他们将图像上传到服务器并在完成时通知。因此,例如,您可能希望在上传过程中运行一个事件指示器,并在完成后停止它。它看起来像这样:

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()

    }

}

自然地,您只会使用这两个过程中的一个。

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

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

一个很好的例子是 UITableView,它使用了 delegatedataSource(两者都是委托(delegate),只是属性命名不同) . TableView 将调用您为这些属性设置的任何类的方法,而无需知道该类是什么,只要它符合给定的协议(protocol)即可。

同样可以用闭包来实现。可以使用提供闭包的属性来定义 TableView ,例如:

tableView.onNumberOfRows { section in
    return 4
}

但这很可能会导致代码困惑。在这种情况下,由于潜在的内存泄漏,闭包也会让许多程序员头疼。这并不是说闭包不太安全或其他什么,它们只是做了很多你看不到的代码,这些代码可能会产生保留循环。在这种特定情况下,最有可能的泄漏是:

tableView.onNumberOfRows { section in
    return self.dataModel.count
}

修复它就是简单地做

tableView.onNumberOfRows { [weak self] section in
    return self?.dataModel.count ?? 0
}

现在看起来过于复杂。

我不会深入探讨闭包,但最后当您重复调用回调时(例如在 TableView 的情况下),您将需要在委托(delegate)或闭包中使用 weak 链接。但是当闭包只被调用一次时(比如上传图片),闭包中就不需要 weak 链接(在大多数情况下,但不是所有情况下)。

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

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

并将其用作

    doSomethingWithClosure {
        self.activityIndicator?.stopAnimating()
        print("TestClosureClass is done")
    }

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

关于iOS/Swift - 闭包/完成 block 和委托(delegate)/函数之间有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56946789/

相关文章:

ios - 在搜索栏的左侧和右侧添加按钮

ios - MFMailComposeViewController 需要很长时间才能发送电子邮件

ios - 为什么我在 Swift5 中收不到推送消息?

ios - Apple Pay 演示应用程序 - 错误 : 'Thread 1: signal SIGABRT'

ios - 委托(delegate)返回零

ios - 获取一个section中的所有indexPaths

ios - 减少 SpriteKit 游戏的电池使用量

ios - 在单独的文件中设置 UITableView 数据源和委托(delegate) - swift

ios - 通过 UITableViewCell 中的 UIButton 更新模型

iphone - 在多个 View Controller 中使用委托(delegate)