iOS / Swift - В чем разница между блоками закрытия / завершения и делегатами / функциями? - PullRequest
0 голосов
/ 09 июля 2019

Я не понимаю этих двух. В настоящее время мир переключается на типы закрытия. Но я не совсем понимаю это. Может кто-нибудь объяснить мне пример в реальном времени?

Ответы [ 3 ]

1 голос
/ 09 июля 2019

Таким образом, реальный пример того и другого будет примерно таким:

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")
    }

Теперь это устраняет все потенциальные риски.Надеюсь, это прояснит для вас одну или две вещи.

1 голос
/ 09 июля 2019

В 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)")
  })
 }
}

Однако, с замыканиями, вы должны знать, что намного легче выстрелить себе в ногу, сильно захватив переменные (например, себя), что приведет к утечкам памяти даже в режиме ARC.

0 голосов
/ 09 июля 2019

Замыкания являются первоклассными объектами, поэтому их можно вкладывать и передавать Просто, В swift функции имеют тип данных premitive, такой как int, double или символ, поэтому вы можете передать функцию в параметре функции. В swift механизм упрощает синтаксис clousers, как выражение lymbda в других языках.

например. Если вы хотите вызывать rest api через URSSession или Alamofire и возвращать ответные данные, тогда вы должны использовать завершениеHandler (это clouser).

Пустота clouser: - {(paramter:DataType)->Void}

Возврат clouser: - {(paramter:DataType)->DataType} например (int, int) -> (int) https://docs.swift.org/swift-book/LanguageGuide/Closures.html

...