Swift @escaping and Completion Handler

99

Estou tentando entender 'Closure' de Swift com mais precisão.

Mas @escapinge Completion Handlersão muito difíceis de entender

Pesquisei muitas postagens e documentos oficiais do Swift, mas senti que ainda não era o suficiente.

Este é o exemplo de código de documentos oficiais

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)

Ouvi dizer que existem duas maneiras e razões para usar @escaping

O primeiro é para armazenar um fechamento, o segundo é para fins operacionais Async.

A seguir estão minhas perguntas :

Primeiro, se doSomethingexecuta, então someFunctionWithEscapingClosureexecutará com o parâmetro de fechamento e esse fechamento será salvo no array de variável global.

Acho que o fechamento é {self.x = 100}

Como selfem {self.x = 100} que salvo na variável global completionHandlerspode se conectar a instanceesse objeto de SomeClass?

Em segundo lugar, eu entendo someFunctionWithEscapingClosureassim.

Para armazenar o fechamento da variável local na palavra-chave completionHandler'completeHandlers we using@ escaping` da variável global !

sem o retorno da @escapingpalavra-chave someFunctionWithEscapingClosure, a variável local completionHandlerserá removida da memória

@escaping é manter esse fechamento na memória

Isto está certo?

Por último, apenas me pergunto sobre a existência dessa gramática.

Talvez esta seja uma questão muito rudimentar.

Se quisermos que alguma função seja executada após alguma função específica. Por que simplesmente não chamamos alguma função após uma chamada de função específica?

Quais são as diferenças entre usar o padrão acima e usar uma função de retorno de chamada de escape?

Dongkun Lee
fonte

Respostas:

123

Escaping & Non-escape do manipulador de conclusão rápida:

Como Bob Lee explica em sua postagem no blog Completion Handlers in Swift with Bob :

Suponha que o usuário esteja atualizando um aplicativo enquanto o usa. Definitivamente, você deseja notificar o usuário quando terminar. É possível que você queira exibir uma caixa que diz: “Parabéns, agora você pode aproveitar totalmente!”

Então, como você executa um bloco de código somente após o download ter sido concluído? Além disso, como você anima certos objetos somente depois que um controlador de visualização foi movido para o próximo? Bem, vamos descobrir como projetar um como um chefe.

Com base na minha extensa lista de vocabulário, manipuladores de conclusão significam

Faça coisas quando as coisas tiverem sido feitas

A postagem de Bob fornece clareza sobre os manipuladores de conclusão (do ponto de vista do desenvolvedor, define exatamente o que precisamos entender).

@escaping closures:

Quando alguém passa um encerramento em argumentos de função, usá-lo depois que o corpo da função é executado e retorna o compilador de volta. Quando a função termina, o escopo do fechamento passado existe e tem existência na memória, até que o fechamento seja executado.

Existem várias maneiras de escapar do encerramento na função de contenção:

  • Armazenamento: Quando você precisa armazenar o encerramento na variável global, propriedade ou qualquer outro armazenamento que existe no passado da memória da função de chamada seja executado e retorne o compilador de volta.

  • Execução assíncrona: quando você está executando o fechamento de forma assíncrona na fila de despacho, a fila manterá o fechamento na memória para você, podendo ser usada no futuro. Nesse caso, você não tem ideia de quando o fechamento será executado.

Quando você tenta usar o encerramento nesses cenários, o compilador Swift mostrará o erro:

captura de tela do erro

Para mais clareza sobre este assunto você pode conferir este post no Medium .

Adicionando mais um ponto, que todo desenvolvedor ios precisa entender:

  1. Fechamento de escape: Um fechamento de escape é um fechamento que é chamado depois que a função para a qual foi passada retorna. Em outras palavras, ele sobrevive à função para a qual foi passado.
  2. Fechamento sem escape : Um fechamento que é chamado dentro da função para a qual foi passado, ou seja, antes de retornar.
Shobhakar Tiwari
fonte
@shabhakar, e se armazenarmos um fechamento, mas não chamá-lo mais tarde. Ou se o método foi chamado duas vezes, mas chamamos o fechamento apenas uma vez. Já que sabemos que o resultado é o mesmo.
user1101733
@ user1101733 Acho que você está falando sobre como escapar do fechamento, o fechamento não será executado até que você não chame. No exemplo acima, se a chamada doSomething método 2 vezes 2 completeHandler, o objeto irá adicionar a matriz preparoHandlers. Se você pegar o primeiro objeto da matriz completedHandlers e chamá-lo, ele será executado, mas a contagem da matriz completHandlers permanecerá a mesma (2).
Deepak
@Deepak, Sim sobre o fechamento de fuga. suponha que não usamos array e usamos variável normal para armazenar a referência de fechamento, já que devemos executar a chamada mais recente. Será que alguma memória será ocupada por fechamentos anteriores que nunca chamarão?
user1101733
1
@ user1101733 Fechamentos são do tipo de referência (como classe), quando você atribui novos fechamentos à variável, a propriedade / variável apontará para um novo fechamento, então ARC desalocará memória para fechamentos anteriores.
Deepak
28

Aqui está uma pequena classe de exemplos que uso para me lembrar de como funciona o @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 precedes/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 `runAsyncTask()` and tells us we need to allow our own completion
        //closure to be @escaping too. `runAsyncTask`'s completion block will be retained in memory until
        //it is executed, so our completion closure must explicitly do the same.
        runAsyncTask {
            completion()
        }
    }





    func runAsyncTask(completion: @escaping (() -> Void)) {
        DispatchQueue.global().async {
            completion()
        }
    }

}
JamesK
fonte
2
Este código não está correto. Está faltando as @escapingeliminatórias.
Rob
Eu gostei mais dissoi.e. escape the scope of this function.
Gal