Como lidar corretamente com o Self fraco em blocos rápidos com argumentos


151

No meu TextViewTableViewCell, eu tenho uma variável para acompanhar um bloco e um método de configuração onde o bloco é passado e atribuído.
Aqui está a minha TextViewTableViewCellturma:

//
//  TextViewTableViewCell.swift
//

import UIKit

class TextViewTableViewCell: UITableViewCell, UITextViewDelegate {

    @IBOutlet var textView : UITextView

    var onTextViewEditClosure : ((text : String) -> Void)?

    func configure(#text: String?, onTextEdit : ((text : String) -> Void)) {
        onTextViewEditClosure = onTextEdit
        textView.delegate = self
        textView.text = text
    }

    // #pragma mark - Text View Delegate

    func textViewDidEndEditing(textView: UITextView!) {
        if onTextViewEditClosure {
            onTextViewEditClosure!(text: textView.text)
        }
    }
}

Quando eu uso o método configure no meu cellForRowAtIndexPathmétodo, como uso adequadamente o self fraco no bloco em que passo.
Aqui está o que eu tenho sem o self fraco:

let myCell = tableView.dequeueReusableCellWithIdentifier(textViewCellIdenfitier) as TextViewTableViewCell
myCell.configure(text: body, onTextEdit: {(text: String) in
   // THIS SELF NEEDS TO BE WEAK  
   self.body = text
})
cell = bodyCell

UPDATE : trabalhei usando o seguinte [weak self]:

let myCell = tableView.dequeueReusableCellWithIdentifier(textViewCellIdenfitier) as TextViewTableViewCell
myCell.configure(text: body, onTextEdit: {[weak self] (text: String) in
        if let strongSelf = self {
             strongSelf.body = text
        }
})
cell = myCell

Quando eu faço [unowned self], em vez de [weak self]e tirar o ifcomunicado, o aplicativo falha. Alguma idéia de como isso deve funcionar [unowned self]?


Você poderia selecionar uma resposta abaixo como a resposta correta, então? Observe também que, com os não proprietários, você não precisaria se fortalecer dentro do seu fechamento. Não proprietário é melhor do que fraco aqui porque o ciclo de vida do seu celular e do controlador de exibição estão vinculados.
Ikuramedia

1
Sei que [a pessoa sem dono] é a melhor opção, mas meu aplicativo falha quando o uso. Gostaria de ver um exemplo de código usando-o para fechar a resposta.
NatashaTheRobot

1
Na documentação: "Como referências fracas, uma referência não proprietária não mantém uma forte presença na instância a que se refere. Porém, ao contrário de uma referência fraca, presume-se que uma referência não proprietária sempre tenha um valor." Se o aplicativo falhar, é provavelmente porque o proprietário não está sendo aplicado a um valor nulo no tempo de execução.
Bill Patterson

Provavelmente é melhor anunciar aqui uma declaração de guarda do que se deixar vincular a strongSelf. Basta dizer, este é como o :-D perfeito candidato
Daniel Galasko

@NatashaTheRobot, que sintaxe é [eu fraco]? parece uma mensagem que passa no objetivo C. Você pode adicionar um pouco mais sobre a sintaxe na pergunta, por favor.
Vignesh

Respostas:


178

Se o eu puder ser nulo no fechamento, use [eu fraco] .

Se o eu nunca for nulo no fechamento, use [eu sem dono] .

Se estiver falhando quando você usa o [eu não dono], eu acho que esse eu é nulo em algum momento desse fechamento, e é por isso que você teve que usar o [eu fraco] .

Gostei muito de toda a seção do manual sobre o uso de fortes , fracos e sem dono nos fechamentos:

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html

Nota: usei o termo encerramento em vez do bloco, que é o termo Swift mais recente:

Diferença entre bloco (objetivo C) e fechamento (Swift) no ios


7
A Apple chamou os blocos de "fechamento" em seu primeiro documento para uma extensão da linguagem C. (Blocos ou fechamentos são uma extensão de C logo no início. Apenas MM está relacionado ao Objetivo-C.) Mesmo eu prefiro o termo "fechamento" também, porque "blocos" em C está relacionado com declarações compostas com muita frequência. é um tipo de erro nos dois idiomas, porque é chamado de fechamento, mesmo que não seja fechado sobre um objeto (variável ou constante).
Amin Negm-Awad

1
respondeu muito bem :)
iDevAmit 23/05

1
Eu sugeriria nunca usar unowned. Não vale a pena o risco de causar um travamento no seu aplicativo.
Kyle Redfearn

32

Coloque [unowned self]antes (text: String)...no seu encerramento. Isso é chamado de lista de captura e coloca instruções de propriedade sobre os símbolos capturados no fechamento.


2
Obrigado por nomeá-lo, eu queria saber isso!
rob5408

3
Não acho que essa resposta seja útil. [self sem dono]
falhará

3
não há absolutamente nenhuma razão para usar sem dono, exceto (1) em situações extremamente incomuns, para desempenho (isso é totalmente irrelevante aqui e em 99,999% da programação) e (2) como uma questão de aplicação de estilo. A afirmação "Você deve sempre usar fraco, nunca sem dono" é muito razoável.
Gordo

29

** EDITADO para o Swift 4.2:

Como o @Koen comentou, o swift 4.2 permite:

guard let self = self else {
   return // Could not get a strong reference for self :`(
}

// Now self is a strong reference
self.doSomething()

PS: Como estou tendo alguns votos positivos, gostaria de recomendar a leitura sobre como escapar de fechamentos .

EDITADO: Como @ tim-vermeulen comentou, Chris Lattner disse em 22 de janeiro, 19:51:29 no CST de 2016, esse truque não deve ser usado por conta própria, portanto, não o use. Verifique as informações de fechamento sem escape e a resposta da lista de capturas em @gbk. **

Para aqueles que usam [self fraco] na lista de captura, observe que o self pode ser nulo; portanto, a primeira coisa que faço é verificar isso com uma declaração de guarda

guard let `self` = self else {
   return
}
self.doSomething()

Se você está se perguntando o que são as aspas, selfé um truque profissional para usar o self dentro do fechamento sem precisar mudar o nome para este , fracoSelf ou qualquer outra coisa.


2
`` Auto é uma amostra de sombreamento, um artigo sobre ele pode ser encontrado aqui arsenkin.com/swift-closure-without-ugly-strongSelf.html
Cullen SUN

2
Costumo chamar o "self" local de "strongSelf" para garantir que não seja confundido com o self padrão e é mais fácil identificar se você se protegeu para uma forte referência própria.
Justin Stanley

1
Isso não deve ser usado, pois é um bug do compilador: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160118/…
Tim Vermeulen

1
Eu acho que o comentário de Chris Lattner no link acima é sobre não nomear a variável como self(nos backticks). Nomeie algo como nonOptionalSelf e tudo ficará bem.
OutOnAWeekend

1
Hoje em dia (swift 4.2) { [weak self] in guard let self = self else { return }pode ser usado sem backticks e é realmente suportado: github.com/apple/swift-evolution/blob/master/proposals/…
Koen.

26

Usar lista de captura

Definindo uma lista de captura

Cada item de uma lista de captura é um emparelhamento da palavra-chave fraca ou sem dono com uma referência a uma instância de classe (como self) ou uma variável inicializada com algum valor (como delegate = self.delegate!). Esses pares são escritos em um par de colchetes, separados por vírgulas.

Coloque a lista de captura antes da lista de parâmetros de um fechamento e digite o tipo de retorno, se eles forem fornecidos:

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // closure body goes here 
} 

Se um fechamento não especificar uma lista de parâmetros ou tipo de retorno, pois eles podem ser inferidos do contexto, coloque a lista de captura no início do fechamento, seguida pela palavra-chave in:

lazy var someClosure: Void -> String = {
    [unowned self, weak delegate = self.delegate!] in
    // closure body goes here
}

explicações adicionais


3
Você usou o "self" não proprietário, o que significa que você sabe com certeza que "self" não será nulo ao acessá-lo. Então você usou o desembrulhar forçado no "self.delegate" (o que também significa que você tem certeza de que não será nulo) para atribuí-lo a um var fraco. Se você tem certeza de que "self.delegate" não será nulo, por que não usar o sem dono no "delegado" em vez de fraco?
Roni Leshes

26

EDIT: Referência a uma solução atualizada por LightMan

Veja a solução do LightMan . Até agora eu estava usando:

input.action = { [weak self] value in
    guard let this = self else { return }
    this.someCall(value) // 'this' isn't nil
}

Ou:

input.action = { [weak self] value in
    self?.someCall(value) // call is done if self isn't nil
}

Geralmente, você não precisa especificar o tipo de parâmetro, se for inferido.

Você pode omitir o parâmetro completamente se não houver nenhum ou se referir a ele como $0no fechamento:

input.action = { [weak self] in
    self?.someCall($0) // call is done if self isn't nil
}

Apenas por completude; se você estiver passando o fechamento para uma função e o parâmetro não estiver @escaping, não precisará de weak self:

[1,2,3,4,5].forEach { self.someCall($0) }

9

A partir do rápido 4.2, podemos fazer:

_ = { [weak self] value in
    guard let self = self else { return }
    print(self) //👈 will never be nil
}()

Outros têm soluções semelhantes, mas "isso" é C ++ IMHO. "strongSelf" é uma convenção da Apple e qualquer pessoa que olhar seu código saberá o que está acontecendo.
18778 David H

1
@ David H IMO: a frase strongSelfexplica explicitamente as variáveis ​​que significam / efeito colateral, o que é bom se o código for de natureza mais longa. aprecio sua opinião, porém, não sabia que o c ++ usava esse fraseado.
eonist

3
A partir de Swift 4.2 você pode usar guard let self = self else { return }para desembrulhar [weak self]: github.com/apple/swift-evolution/blob/master/proposals/...
Amer Hukic

@AmerHukic 👌.
eonist


3

Você pode usar [self fraco] ou [self não proprietário] na lista de captura antes dos seus parâmetros do bloco. A lista de captura é uma sintaxe opcional.

[unowned self]funciona bem aqui porque a célula nunca será nula. Caso contrário, você pode usar[weak self]


1
a célula não é auto, ele não está na classe de células, ele é provavelmente em um viewcontroller ...
Juan Boero

0

Se você está travando, provavelmente precisará de um "eu fraco"

Meu palpite é que o bloco que você está criando ainda está conectado.

Crie um prepareForReuse e tente limpar o bloco onTextViewEditClosure dentro dele.

func prepareForResuse() {
   onTextViewEditClosure = nil
   textView.delegate = nil
}

Veja se isso impede a falha. (É apenas um palpite).


0

Encerramento e fortes ciclos de referência [Sobre]

Como você sabe, o fechamento de Swift pode capturar a instância. Isso significa que você pode usar selfdentro de um fechamento. Especialmente escaping closure[About] pode criar um strong reference cyclequê. A propósito, você deve usar explicitamente o selfinterior escaping closure.

O fechamento Capture Listrápido possui um recurso que permite evitar essa situação e interromper um ciclo de referência porque não possui uma referência forte à instância capturada. O elemento Capture List é um par de weak/ unownede uma referência à classe ou variável.

Por exemplo

class A {
    private var completionHandler: (() -> Void)!
    private var completionHandler2: ((String) -> Bool)!

    func nonescapingClosure(completionHandler: () -> Void) {
        print("Hello World")
    }

    func escapingClosure(completionHandler: @escaping () -> Void) {
        self.completionHandler = completionHandler
    }

    func escapingClosureWithPArameter(completionHandler: @escaping (String) -> Bool) {
        self.completionHandler2 = completionHandler
    }
}

class B {
    var variable = "Var"

    func foo() {
        let a = A()

        //nonescapingClosure
        a.nonescapingClosure {
            variable = "nonescapingClosure"
        }

        //escapingClosure
        //strong reference cycle
        a.escapingClosure {
            self.variable = "escapingClosure"
        }

        //Capture List - [weak self]
        a.escapingClosure {[weak self] in
            self?.variable = "escapingClosure"
        }

        //Capture List - [unowned self]
        a.escapingClosure {[unowned self] in
            self.variable = "escapingClosure"
        }

        //escapingClosureWithPArameter
        a.escapingClosureWithPArameter { [weak self] (str) -> Bool in
            self?.variable = "escapingClosureWithPArameter"
            return true
        }
    }
}
  • weak- mais preferível, use-o quando for possível
  • unowned - use-o quando tiver certeza de que a vida útil do proprietário da instância é maior que o fechamento
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.