Swift @escaping и обработчик завершения
[Swift]
Привет, я пытаюсь понять "Закрытие" Swift более точно.
Но @escaping
а также Completion Handler
слишком сложно понять
Я искал много сообщений Свифта и официальных документов, но чувствовал, что этого все еще недостаточно.
Это пример кода официальных документов
var completionHandlers: [()->Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping ()->Void){
completionHandlers.append(completionHandler)
}
func someFunctionWithNoneescapingClosure(closure: ()->Void){
closure()
}
class SomeClass{
var x:Int = 10
func doSomething(){
someFunctionWithEscapingClosure {
self.x = 100
//not excute yet
}
someFunctionWithNoneescapingClosure {
x = 200
}
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
completionHandlers.first?()
print(instance.x)
Я слышал, что есть два способа и причины использования @escaping
Первый предназначен для хранения замыкания, второй - для рабочих целей Async.
Вот мои вопросы:
Во-первых, если doSomething
выполняет тогда someFunctionWithEscapingClosure
будет выполняться с параметром закрытия, и это закрытие будет сохранено в массиве глобальных переменных.
Я думаю, что закрытие {self.x = 100}
Как self
в {self.x = 100}, который сохраняется в глобальной переменной completionHandlers
можно подключиться к instance
этот объект SomeClass
?
Во-вторых, я понимаю someFunctionWithEscapingClosure
как это.
Хранить закрытие локальной переменной completionHandler
в глобальную переменнуюwe using
@ escapeing ключевое слово!
без @escaping
ключевое слово someFunctionWithEscapingClosure
возвращает, локальная переменная completionHandler
удалит из памяти
@escaping
это держать это закрытие в памяти
Это правильно?
Наконец, мне просто интересно узнать о существовании этой грамматики.
Может быть, это очень элементарный вопрос.
Если мы хотим, чтобы какая-то функция выполнялась после какой-то конкретной функции. Почему бы нам просто не вызвать какую-либо функцию после вызова конкретной функции?
Каковы различия между использованием вышеупомянутого шаблона и использованием функции обратного вызова?
спасибо за чтение длинных вопросов и извините за то, что я не родной
5 ответов
Прежде всего, я хочу сказать "Очень хороший вопрос:)"
Обработчик завершения:
Предположим, что пользователь обновляет приложение во время его использования. Вы определенно хотите уведомить пользователя, когда это будет сделано. Возможно, вы захотите открыть окно с надписью: "Поздравляю, теперь вы можете полностью насладиться!"
Итак, как вы запускаете блок кода только после завершения загрузки? Кроме того, как вы анимируете определенные объекты только после того, как контроллер представления был перемещен к следующему? Ну, мы собираемся выяснить, как спроектировать его как босса. Исходя из моего обширного словарного списка, обработчики завершения обозначают
Делай вещи, когда дела сделаны
Для получения более подробной информации, пожалуйста, посетите этот блог.
Эта ссылка дает мне полную ясность о обработчиках завершения (с точки зрения разработчика, она точно определяет то, что нам нужно понять).
@escaping замыкания:
Когда вы передаете замыкание в аргументах функции, используйте его после того, как тело функции будет выполнено и вернет компилятор обратно. Когда функция завершается, область действия переданного замыкания существует и существует в памяти, пока замыкание не будет выполнено.
Есть несколько способов избежать замыкания в содержащей функции:
Хранение: когда вам нужно сохранить замыкание в глобальной переменной, выполняется свойство или любое другое хранилище, которое существует в памяти после вызывающей функции, и возвращает компилятор обратно.
Асинхронное выполнение: Когда вы выполняете закрытие асинхронно в очереди отправки, очередь будет держать закрытие в памяти для вас, может быть использовано в будущем. В этом случае вы не знаете, когда будет выполнено замыкание.
Когда вы попытаетесь использовать замыкание в этих сценариях, компилятор Swift покажет ошибку:
Для большей ясности по этой теме вы можете проверить этот пост на Medium.
Я надеюсь, что вы получите хорошее понимание по этой ссылке.
Если у вас все еще есть какие-либо вопросы (но сначала обязательно прочитайте эту ссылку построчно; она очень хорошо объяснена), то не стесняйтесь поделиться своим комментарием.
Спасибо, продолжайте писать, если этот ответ нужно обновить
Вот небольшой класс примеров, которые я использую, чтобы напомнить себе, как работает @escaping.
class EscapingExamples: NSObject {
var closure: (() -> Void)?
func storageExample(with completion: (() -> Void)) {
//This will produce a compile time error because `closure` is outside the scope of this
//function - it's a class-instance level variable - and so it could be called by any other method at
//any time, even after this function has completed. We need to tell `completion` that it may remain in memory, i.e. `escape` the scope of this
//function.
closure = completion
//Run some function that may call `closure` at some point, but not necessary for the error to show up.
//runOperation()
}
func asyncExample(with completion: (() -> Void)) {
//This will produce a compile time error because the completion closure may be called at any time
//due to the async nature of the call which preceeds/encloses it. We need to tell `completion` that it should
//stay in memory, i.e.`escape` the scope of this function.
DispatchQueue.global().async {
completion()
}
}
func asyncExample2(with completion: (() -> Void)) {
//The same as the above method - the compiler sees the `@escaping` nature of the
//closure required by `anotherThing()` and tells us we need to allow our own completion
//closure to be @escaping too. `anotherThing`'s completion block will be retained in memory until
//it is executed, so our completion closure must explicitely do the same.
runAsyncTask {
completion()
}
}
func runAsyncTask(completion: @escaping (() -> Void)) {
DispatchQueue.global().async {
completion()
}
}
}
/*the long story short is that @escaping means that don't terminate the function life time until the @escaping closure has finished execution in the opposite of nonEscaping closure the function can be terminated before the closure finishes execution Ex:
*/
func fillData(completion: @escaping: () -> Void){
/// toDo
completion()
}
//___________________________
//The call for this function can be in either way's @escaping or nonEscaping :
fillData{
/// toDo
}
/* again the deference between the two is that the function can be terminated before finish of execution nonEscaping closure in the other hand the @escaping closure guarantees that the function execution will not be terminated before the end of @escaping closure execution. Hope that helps ***#(NOTE THAT THE CLOSURE CAN BE OF ANY SWIFT DATA TYPE EVEN IT CAN BE TYPEALIAS)*/
функция
Вы определяете функцию с func
ключевое слово. Функции могут принимать много параметров и не возвращать ни одного, один или несколько параметров
закрытие
Замыкания - это автономные блоки функциональности, которые можно передавать и использовать в вашем коде. Замыкания в Swift похожи на блоки в C и Objective-C и на лямбды в других языках программирования.
Функции и замыкания и типы первого класса в swift:
- назначить функцию / замыкание локальной переменной
- передать функцию / замыкание в качестве аргумента
- вернуть функцию / закрытие
Экранирующее закрытие против не экранирующего закрытия
non-escaping closure
@noescape
это замыкание, которое вызывается внутри функции, в которую оно было передано, то есть до его возврата.escaping closure
@escaping
это закрытие, которое вызывается после того, как функция была передана в return. Другими словами, он переживает функцию, которой он был передан. Типичные случаи использования этого:- Асинхронные вызовы; сетей.
- Функции хранятся в виде переменных; продумать действия и предоставить обратные вызовы.
- Планирование задач в очереди отправки.
*@noescape
был атрибутом в swift2. Это устарело от swift3. @noescape
атрибут применяется по умолчанию в Swift3. Поскольку замыкания по умолчанию не являются экранирующими в Swift3, экранирующие замыкания должны быть помечены как таковые. И @escaping
Атрибут позволяет нам сделать это.
Обработчик завершения
Хороший пример escaping closure
это completion handler
, Многие функции, которые запускают асинхронную операцию, принимают в качестве обработчика завершения аргумент замыкания. Функция возвращается после запуска операции, но замыкание не вызывается до тех пор, пока операция не будет завершена - замыкание необходимо закрыть, чтобы вызвать его позже.
Подробнее читайте здесь - Средний пост, Средний пост, документы
import UIKit
import Alamofire
// Модель
class ShortlistCountResponse : Decodable {
var response : String?
var data : ShortlistcountData?
}
class ShortlistcountData : Decodable {
var totalpropFavcount : Int?
var totalprojFavcount : Int?
}
// Определение общего класса......
static func fetchGenericData<T: Decodable>(urlString: String,params : [String:Any], completion: @escaping (T) -> ()) {
let url = urlString
let headers = ["Content-Type": "application/x-www-form-urlencoded", "Accept":"application/json"]
Alamofire.request(url, method: .post, parameters:params, encoding: URLEncoding.default, headers: headers).responseJSON { response in
print(response.request?.urlRequest ?? "")
print(params)
print(response.data ?? "")
print(response.value ?? "")
switch(response.result) {
case .success(_):
if let data = response.data{
do {
let gotData = try JSONDecoder().decode(T.self, from: data)
completion(gotData)
}
catch let jsonErr {
print("Error serializing json:", jsonErr)
ActivityIndicator.dismissActivityView()
}
DispatchQueue.main.async {
ActivityIndicator.dismissActivityView()
}
}
break
case .failure(_):
print(response.result.error ?? "")
ActivityIndicator.dismissActivityView()
break
}
}
}
// забавный звонок
override func viewDidLoad() {
super.viewDidLoad()
let userID = ""
let languageID = ""
let params = ["userID":userID,"languageID":languageID]
var appDelegate: AppDelegate?
Service.fetchGenericData(urlString: "your url...", params: params) { (shortlistCountResponse : ShortlistCountResponse) in
print(shortListCountResponse.data.totalprojFavcount ?? 0)
}
}