Listas de captura rápidas: qual é a diferença entre links fracos, fortes e não proprietários?


Joseph Wright, “Prisioneiro” - Ilustração da Captura “Forte”

A lista de valores "capturados" está na frente da lista de parâmetros de fechamento e pode "capturar" valores do escopo de três maneiras diferentes: usando os links "forte", "fraco" ou "sem dono". Costumamos usá-lo, principalmente para evitar ciclos de referência fortes ("ciclos de referência fortes", também conhecidos como "ciclos de retenção").
Pode ser difícil para um desenvolvedor iniciante decidir qual método usar, para que você possa gastar muito tempo escolhendo entre "forte" e "fraco" ou entre "fraco" e "sem dono", mas com o tempo, você perceberá que a escolha certa apenas um.

Primeiro, crie uma classe simples:

class Singer { func playSong() { print("Shake it off!") } } 

Em seguida, escrevemos uma função que cria uma instância da classe Singer e retorna um fechamento que chama o método playSong () da classe Singer :

 func sing() -> () -> Void { let taylor = Singer() let singing = { taylor.playSong() return } return singing } 

Por fim, podemos chamar Sing () em qualquer lugar para obter o resultado da reprodução de playSong ()

 let singFunction = sing() singFunction() 


Como resultado, a linha “Shake it off!” Será exibida.

Captura forte


A menos que você especifique explicitamente um método de captura, o Swift usa uma captura "forte". Isso significa que o fechamento captura os valores externos usados ​​e nunca os liberta.

Vamos dar uma olhada na função cantar () novamente

 func sing() -> () -> Void { let taylor = Singer() let singing = { taylor.playSong() return } return singing } 

A constante de taylor é definida dentro de uma função, portanto, em circunstâncias normais, seu lugar será liberado assim que a função terminar seu trabalho. No entanto, essa constante é usada dentro do fechamento, o que significa que o Swift garantirá automaticamente sua presença enquanto o fechamento existir, mesmo após o término da função.
Esta é uma captura "forte" em ação. Se o Swift permitir que o taylor seja liberado, a chamada do fechamento seria insegura - o método taylor.playSong () não é mais válido.

Captura "fraca" (captura fraca)


Swift nos permite criar uma " lista de captura " para determinar como os valores usados ​​são capturados. Uma alternativa à captura "forte" é "fraca" e sua aplicação leva às seguintes consequências:

1. Os valores capturados “fracamente” não são mantidos pelo fechamento e, portanto, podem ser liberados e ajustados para zero .

2. Como conseqüência do primeiro parágrafo, os valores capturados "fracamente" no Swift são sempre opcionais .
Modificamos nosso exemplo usando uma captura "fraca" e vemos imediatamente a diferença.

 func sing() -> () -> Void { let taylor = Singer() let singing = { [weak taylor] in taylor?.playSong() return } return singing } 

[taylor fraco] - esta é a nossa " lista de capturas ", uma parte especial da sintaxe de fechamento na qual damos instruções sobre como os valores devem ser capturados. Aqui dizemos que o taylor deve ser capturado "fracamente", por isso precisamos usar o taylor? .PlaySong () - agora é opcional , pois pode ser definido como nulo a qualquer momento.

Se você agora executar esse código, verá que a chamada de singFunction () não resulta mais em uma mensagem. A razão para isso é que taylor existe apenas dentro de sing () , e o fechamento retornado por esta função não mantém taylor "fortemente" dentro de si.

Agora tente alterar taylor? .PlaySong () para taylor! .PlaySong () . Isso levará à descompactação forçada do taylor dentro do fechamento e, consequentemente, a um erro fatal (descompactar o conteúdo contendo nada )

Captura "sem dono" (captura sem dono)


Uma alternativa à captura "fraca" é "sem dono".

 func sing() -> () -> Void { let taylor = Singer() let singing = { [unowned taylor] in taylor.playSong() return } return singing } 

Esse código terminará de forma anormal de maneira semelhante ao opcional opcional implantado, que é um pouco mais alto - diz Taylor : "Eu tenho certeza de que Taylor existirá o tempo todo em que o circuito for fechado, então não preciso mantê-lo na memória". De fato, o taylor será lançado quase imediatamente e esse código falhará.

Portanto, use sem dono com muito cuidado.

Problemas comuns


Existem quatro problemas que os desenvolvedores enfrentam ao usar a captura de valor nos fechamentos:

1. Dificuldades com a localização da lista de captura no caso em que o fechamento usa parâmetros


Essa é uma dificuldade comum que você pode encontrar no início do estudo dos fechamentos, mas, felizmente, a Swift nos ajudará nesse caso.

Ao usar a lista de captura e os parâmetros de fechamento juntos, a lista de captura é primeiro entre colchetes, depois os parâmetros de fechamento e depois a palavra-chave in, marcando o início do "corpo" do fechamento.

 writeToLog { [weak self] user, message in self?.addToLog("\(user) triggered event: \(message)") } 

Tentar colocar uma lista de captura após os parâmetros de fechamento resultará em um erro de compilação.

2. O surgimento de um ciclo de vínculos fortes, levando a um vazamento de memória


Quando uma entidade A possui uma entidade B e vice-versa, você tem uma situação chamada “ciclo de retenção”.

Como exemplo, considere o código:

 class House { var ownerDetails: (() -> Void)? func printDetails() { print("This is a great house.") } deinit { print("I'm being demolished!") } } 

Definimos a classe House , que contém uma propriedade (encerramento), um método e uma desinicialização que exibirá uma mensagem quando uma instância da classe for destruída.

Agora crie uma classe Owner semelhante à anterior, exceto que sua propriedade de fechamento contém informações sobre a casa.

 class Owner { var houseDetails: (() -> Void)? func printDetails() { print("I own a house.") } deinit { print("I'm dying!") } } 

Agora crie instâncias dessas classes dentro do bloco do. Não precisamos de um bloco catch, mas usar um bloco do destruirá as instâncias logo após}

 print("Creating a house and an owner") do { let house = House() let owner = Owner() } print("Done") 

Como resultado, serão exibidas mensagens: “Criando uma casa e um proprietário”, “Estou morrendo!”, “Estou sendo demolido!”, Depois “Concluído” - tudo funciona como deveria.

Agora crie um loop de links fortes.

 print("Creating a house and an owner") do { let house = House() let owner = Owner() house.ownerDetails = owner.printDetails owner.houseDetails = house.printDetails } print("Done") 

Agora a mensagem "Criando uma casa e um proprietário" será exibida e, em seguida, "Concluído". Os desinicializadores não serão chamados.

Isso aconteceu como resultado do fato de a casa ter uma propriedade que aponta para o proprietário e o proprietário ter uma propriedade que aponta para a casa. Portanto, nenhum deles pode ser liberado com segurança. Em uma situação real, isso leva a vazamentos de memória, que levam a um desempenho ruim e até a uma falha do aplicativo.

Para corrigir a situação, precisamos criar um novo fechamento e usar uma captura "fraca" em um ou dois casos, como este:

 print("Creating a house and an owner") do { let house = House() let owner = Owner() house.ownerDetails = { [weak owner] in owner?.printDetails() } owner.houseDetails = { [weak house] in house?.printDetails() } } print("Done") 

Não há necessidade de declarar os dois valores capturados, basta fazê-lo em um único local - isso permitirá que o Swift destrua as duas classes quando necessário.

Em projetos reais, a situação de um ciclo tão óbvio de vínculos fortes raramente surge, mas isso fala ainda mais da importância do uso de captura "fraca" com desenvolvimento competente.

3. O uso inadvertido de links fortes, geralmente ao capturar vários valores


Por padrão, o Swift usa uma forte aderência, o que pode levar a um comportamento inesperado.
Considere o seguinte código:

 func sing() -> () -> Void { let taylor = Singer() let adele = Singer() let singing = { [unowned taylor, adele] in taylor.playSong() adele.playSong() return } return singing } 

Agora, temos dois valores capturados pelo fechamento e usamos os dois da mesma maneira. No entanto, apenas taylor é capturado como não proprietário - adele é capturado fortemente porque a palavra-chave sem dono deve ser usada para cada valor capturado.

Se você fez isso de propósito, está tudo bem, mas se você deseja que os dois valores sejam capturados " sem dono ", é necessário o seguinte:

 [unowned taylor, unowned adele] 

4. Copie fechamentos e compartilhe valores capturados


O último caso em que os desenvolvedores se deparam é como as falhas são copiadas porque os dados que eles capturam ficam disponíveis para todas as cópias da falha.
Considere um exemplo de um fechamento simples que captura a variável inteira numberOfLinesLogged declarada fora do fechamento, para que possamos aumentar seu valor e imprimi-lo sempre que o fechamento for chamado:

 var numberOfLinesLogged = 0 let logger1 = { numberOfLinesLogged += 1 print("Lines logged: \(numberOfLinesLogged)") } logger1() 

Isso exibirá a mensagem "Linhas registradas: 1".
Agora, criaremos uma cópia do fechamento que compartilhará os valores capturados junto com o primeiro fechamento. Assim, se chamarmos o fechamento original ou sua cópia, veremos o valor crescente da variável.

 let logger2 = logger1 logger2() logger1() logger2() 

Isso imprimirá as mensagens “Linhas registradas: 1” ... “Linhas registradas: 4” porque logger1 e logger2 apontam para a mesma variável numberOfLinesLogged capturada.

Quando usar uma captura "forte", "fraca" e "sem dono"


Agora que entendemos como tudo funciona, vamos tentar resumir:

1. Se tiver certeza de que o valor capturado nunca será nulo ao executar o fechamento, você poderá usar a “captura não proprietária” . Essa é uma situação pouco frequente em que o uso da captura "fraca" pode causar dificuldades adicionais, mesmo quando o uso da proteção deixa um valor pouco capturado dentro do fechamento.

2. Se você tiver um caso de ciclo de vínculos fortes (a entidade A possui a entidade B e a entidade B possui a entidade A), em um dos casos você precisará usar a “captura fraca” . É necessário levar em consideração qual das duas entidades será liberada primeiro; portanto, se o controlador de visualização A representar o controlador de visualização B, o controlador de visualização B poderá conter um link "fraco" para "A".

3. Se a possibilidade de um ciclo de links fortes for excluída, você poderá usar a captura "forte" ( "captura forte" ). Por exemplo, a execução de uma animação não se bloqueia dentro do fechamento que contém a animação, para que você possa usar uma ligação forte.

4. Se você não tiver certeza, comece com uma ligação "fraca" e altere-a somente se necessário.

Opcional - Guia Swift oficial:
Curto-circuito
Contagem automática de links

Source: https://habr.com/ru/post/pt444336/


All Articles