iOS / Swift-闭包/完成框和委托/功能之间有什么区别?

时间:2019-07-09 06:51:16

标签: ios swift delegates closures completionhandler

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

3 个答案:

答案 0 :(得分:1)

在Swift / obj-c中,术语delegate用于表示对特定选择器做出响应的协议。

关于它的事情就像在对象上调用方法一样。

例如

protocol CalculatorDelegate : class { // : class so it can be made 'weak'
 func onCalculation(result: Int) -> Void
}

现在,如果我们有一个Calculator类,要使用委托,我们会做类似的事情

class Calculator() {
  weak var delegate: CalculatorDelegate?

  func calculate(_ a: Int, _ b: Int) -> Int {
    let result = a + b

    self.delegate?.onCalculation(result: result)
    return result
  }
}

然后在其他一些类中(例如,在iOS中-一个View Controller),我们可以这样做:

class MyClass : CalculatorDelegate {
 func onCalculation(result: Int) {
   print("Delegate method on calculation called with result \(result)")
 }

 func someButtonPress() {
  let calculator = Calculator()
  calculator.delegate = self
  calculator.calculate(42, 66)
 }
}

所以您可以看到设置非常复杂。

现在闭包只是可以在其他地方调用的代码块,因此您可以像这样更改所有代码:

class Calculator2() {
  weak var delegate: CalculatorDelegate?

  func calculate(_ a: Int, _ b: Int, onCalculation: (@escaping (Int) -> Void) -> Int)?) {
    let result = a + b

    onCalculation?(result)
    return result
  }
}
class MyClass {
 func someButtonPress() {
  let calculator = Calculator2()
  calculator.calculate(42, 66, onCalculation: { (result: Int) in 
    print("Closure invoked with \(result)")
  })
 }
}

但是,在使用闭包时,您需要意识到,通过强力捕获变量(例如self)来使自己陷入困境要容易得多,即使在ARC体制下,这也会导致内存泄漏。

答案 1 :(得分:1)

所以两者的真实示例都是这样的:

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秒钟,然后向正在侦听的人报告,其中一个使用委托过程,另一个使用关闭过程。

尽管他们什么也没做,但请稍等,您可以轻松地想象它们例如将图像上传到服务器并在完成时通知。因此,例如,您可能希望在上传过程中运行活动指示器,并在完成时停止它。看起来像这样:

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

    }

}

自然,您将只使用两个过程之一。

您可以看到代码之间存在巨大差异。要执行委托过程,您需要创建一个协议,在这种情况下为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()
    }
}

例如,一个很好的例子是使用UITableViewdelegate的{​​{1}}(两者都是委托,只是属性的名称不同)。表视图将调用您设置为这些属性的任何类的方法,而无需知道该类是什么,只要它与给定的协议相对应即可。

使用闭包可以实现相同的效果。可以使用提供闭包的属性来定义表视图:

dataSource

但是这很可能导致一大堆代码。在这种情况下,由于潜在的内存泄漏,关闭也会使许多程序员头疼。并不是说闭包不是那么安全或什么,它们只是做了很多您看不到的代码,它们可能会产生保留周期。在这种情况下,最可能的泄漏是:

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 } 链接。但是,当闭包仅被调用一次(例如上传图片)时,闭包中不需要weak链接(在大多数情况下但并非全部)。

在回溯中,尽可能使用闭包,但在将闭包用作属性时应避免使用或谨慎使用(具有讽刺意味的是,我给出的示例)。但是您只想这样做:

weak

并将其用作

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

现在,这消除了所有潜在风险。我希望这能为您解决一两个问题。

答案 2 :(得分:0)

闭包是一流的对象,因此它们可以嵌套并传递 只是, 在swift中,函数是诸如int,double或character之类的特殊数据类型,这就是为什么可以在函数参数中传递函数的原因。在swift机制中,简化了Clousers语法(如其他语言的lymbda表达式)。

例如如果要通过URSSession或Alamofire调用rest api并返回响应数据,则应使用completionHandler(它是clouser)。

无效的clouser:-{(paramter:DataType)->Void}

返回clouser:-{(paramter:DataType)->DataType} 例如(int, int) -> (int)  https://docs.swift.org/swift-book/LanguageGuide/Closures.html