Таким образом, реальный пример того и другого будет примерно таким:
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")
}
Теперь это устраняет все потенциальные риски.Надеюсь, это прояснит для вас одну или две вещи.