iOS/Swift - 闭包/完成块和委托/函数有啥区别?
Posted
技术标签:
【中文标题】iOS/Swift - 闭包/完成块和委托/函数有啥区别?【英文标题】:iOS/Swift - What is the difference between Closure/Completion blocks and Delegates/functions?iOS/Swift - 闭包/完成块和委托/函数有什么区别? 【发布时间】:2019-07-09 06:51:16 【问题描述】:我不清楚这两个,现在世界正在转向闭包类型。但我并没有清楚地理解这一点。有人可以用实时示例解释一下吗?
【问题讨论】:
【参考方案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 个类 TestDelegateClass
和 TestClosureClass
。他们每个人都有一个方法 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()
所以一个很好的例子是 UITableView
使用 delegate
和 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()
并将其用作
doSomethingWithClosure
self.activityIndicator?.stopAnimating()
print("TestClosureClass is done")
这已经消除了所有潜在的风险。我希望这能为您解决一两件事。
【讨论】:
【参考方案2】:在 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 中 - 视图控制器)我们可能会这样做:
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 机制下也会导致内存泄漏。
【讨论】:
【参考方案3】:闭包是一流的对象,因此它们可以嵌套和传递 简单地, 在 swift 中,函数是原始数据类型,如 int、double 或 character,这就是为什么您可以在函数参数中传递函数的原因。在 swift 机制中简化闭包语法,就像其他语言中的 lambda 表达式一样。
例如如果你想通过URSSession或Alamofire调用rest API并返回响应数据,那么你应该使用completionHandler(它是闭包)。
无效关闭:-(paramter:DataType)->Void
返回关闭:-(paramter:DataType)->DataType
例如(int, int) -> (int)
https://docs.swift.org/swift-book/LanguageGuide/Closures.html
【讨论】:
以上是关于iOS/Swift - 闭包/完成块和委托/函数有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章
iOS Swift 如何访问在完成处理程序闭包中创建的数据——在闭包之外
在 uiview 中有超过 1 个 uitextfield 的 iOS swift 委托