Cómo manejar correctamente el yo débil en bloques rápidos con argumentos

151

En mi TextViewTableViewCell, tengo una variable para realizar un seguimiento de un bloque y un método de configuración donde el bloque se pasa y se asigna.
Aquí está mi TextViewTableViewCellclase:

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

Cuando uso el método de configuración en mi cellForRowAtIndexPathmétodo, ¿cómo utilizo correctamente el yo débil en el bloque que paso? Esto
es lo que tengo sin el yo débil:

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

ACTUALIZACIÓN : Tengo lo siguiente para trabajar usando [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

Cuando lo hago en [unowned self]lugar de [weak self]y saco la ifdeclaración, la aplicación se bloquea. ¿Alguna idea sobre cómo debería funcionar esto [unowned self]?

NatashaTheRobot
fuente
¿Podría seleccionar una respuesta a continuación como la respuesta correcta? También tenga en cuenta que con sin dueño no necesitaría fortalecerse dentro de su cierre. Sin propietario es mejor que débil aquí porque el ciclo de vida de su celda y el controlador de vista están vinculados.
ikuramedia
1
Me doy cuenta de que [yo no poseído] es la mejor opción, pero mi aplicación se bloquea cuando la uso. Me encantaría ver un ejemplo de código que lo usa para cerrar la respuesta.
NatashaTheRobot
1
De los documentos: "Al igual que las referencias débiles, una referencia no propiedad no mantiene un fuerte control sobre la instancia a la que se refiere. Sin embargo, a diferencia de una referencia débil, se supone que una referencia no propiedad siempre tiene un valor". Si su aplicación falla, es probablemente porque no se está aplicando a un valor que es nulo en tiempo de ejecución.
Bill Patterson el
Probablemente sea mejor anunciar una declaración de guardia aquí que si se deja vincular a strongSelf. Solo digo, esto es como el candidato perfecto :-D
Daniel Galasko
@NatashaTheRobot, ¿Qué sintaxis es [yo débil]? parece un mensaje que pasa en el objetivo C. ¿Puede agregar un poco más sobre la sintaxis en la pregunta, por favor?
Vignesh

Respuestas:

178

Si el self puede ser nulo en el cierre, use [self débil] .

Si el self nunca será nulo en el cierre, use [self sin dueño] .

Si se cuelga cuando usas [yo sin propiedad] , supongo que ese yo es nulo en algún momento de ese cierre, por lo que tuviste que ir con [yo débil] .

Realmente me gustó toda la sección del manual sobre el uso de cierres fuertes , débiles y sin propiedad:

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

Nota: utilicé el término cierre en lugar de bloque, que es el término Swift más nuevo:

Diferencia entre bloqueo (Objetivo C) y cierre (Swift) en ios

Tenaz
fuente
77
Apple llamó a los bloques "cierres" en su primer documento para una extensión de lenguaje C. (Los bloques o cierres son una extensión de C al principio. Solo MM está relacionado con el Objetivo-C). Incluso prefiero el término "cierre", porque los "bloques" en C se relacionan muy frecuentemente con declaraciones compuestas. es una especie de error en ambos idiomas, porque se llama cierre incluso si no se cierra sobre un objeto (variable o constante).
Amin Negm-Awad
1
muy bien respondida :)
iDevAmit
1
Sugeriría nunca usar unowned. No vale la pena correr el riesgo de hacer que su aplicación se bloquee.
Kyle Redfearn
32

Poner [unowned self]antes (text: String)...en su cierre. Esto se llama una lista de captura y coloca instrucciones de propiedad sobre los símbolos capturados en el cierre.

ikuramedia
fuente
2
Gracias por nombrarlo, ¡quería saber eso!
rob5408
3
No creo que esta respuesta sea útil. [yo sin dueño] se estrellará si el yo se vuelve nulo durante la ejecución del cierre
Yunus Nedim Mehel
3
no hay absolutamente ninguna razón para usar sin propietario, excepto (1) en situaciones extremadamente inusuales, para el rendimiento (esto es completamente irrelevante aquí y en el 99.999% de la programación) y (2) como una cuestión de estilo. La afirmación "Siempre debes usar débil, nunca sin dueño" es muy razonable.
Fattie
29

** EDITADO para Swift 4.2:

Como comentó @Koen, 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()

PD: Dado que tengo algunos votos positivos, me gustaría recomendar la lectura sobre cómo escapar de los cierres .

EDITADO: Como ha comentado @ tim-vermeulen, Chris Lattner dijo el viernes 22 de enero a las 19:51:29 CST 2016, este truco no debe usarse solo, así que no lo use. Verifique la información de los cierres que no escapan y la respuesta de la lista de captura de @gbk. **

Para aquellos que usan [self débil] en la lista de captura, tenga en cuenta que self podría ser nulo, por lo que lo primero que hago es verificar eso con una declaración de guardia

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

Si se pregunta cuáles son las comillas, selfes un truco profesional para usar uno mismo dentro del cierre sin necesidad de cambiar el nombre a este , débilSelf o lo que sea.

Hombre ligero
fuente
2
`self` es una muestra de sombreado, un artículo al respecto se puede encontrar aquí arsenkin.com/swift-closure-without-ugly-strongSelf.html
Cullen SUN
2
Tiendo a llamar al "self" local "strongSelf" para asegurarme de que no se confunda con el self predeterminado y que sea más fácil detectar si se ha resguardado para una autorreferencia fuerte.
Justin Stanley
1
Esto no debe usarse, ya que es un error del compilador: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160118/…
Tim Vermeulen
1
Creo que el comentario de Chris Lattner en el enlace de arriba trata de no nombrar la variable como self(en backticks). Póngale otro nombre como nonOptionalSelf y estará bien.
OutOnAWeekend
1
Hoy en día (swift 4.2) { [weak self] in guard let self = self else { return }se puede usar sin backticks, y en realidad es compatible: github.com/apple/swift-evolution/blob/master/proposals/…
Koen.
26

Usar lista de captura

Definir una lista de captura

Cada elemento en una lista de captura es un emparejamiento de la palabra clave débil o no propiedad con una referencia a una instancia de clase (como self) o una variable inicializada con algún valor (como delegate = self.delegate!). Estos emparejamientos se escriben dentro de un par de llaves cuadradas, separadas por comas.

Coloque la lista de captura antes de una lista de parámetros de cierre y tipo de retorno si se proporcionan:

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

Si un cierre no especifica una lista de parámetros o un tipo de retorno porque pueden inferirse del contexto, coloque la lista de captura al comienzo del cierre, seguido de la palabra clave in:

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

explicaciones adicionales

gbk
fuente
3
Usó "self" sin propietario, lo que significa que sabe con certeza que "self" no será nulo cuando acceda a él. Luego usó forzar desenvolver en "self.delegate" (lo que también significa que sabe con certeza que no será nulo) para asignarlo a una var débil. Si sabe con certeza que "self.delegate" no será nulo, ¿por qué no usarlo como propio en el "delegado" en lugar de débil?
Roni Leshes
26

EDITAR: referencia a una solución actualizada de LightMan

Ver la solución de LightMan . Hasta ahora estaba usando:

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

O:

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

Por lo general, no necesita especificar el tipo de parámetro si se infiere.

Puede omitir el parámetro por completo si no hay ninguno o si se refiere a él como $0en el cierre:

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

Solo para completar; Si pasa el cierre a una función y el parámetro no lo es @escaping, no necesita un weak self:

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

A partir de swift 4.2 🔸 podemos hacer:

_ = { [weak self] value in
    guard let self = self else { return }
    print(self) //👈 will never be nil
}()
eonista
fuente
Otros tienen soluciones similares, pero "esto" es C ++ en mi humilde opinión. "strongSelf" es una convención de Apple y cualquiera que eche un vistazo a su código sabrá lo que está sucediendo.
David H
1
@ David H IMO, la frase strongSelfexplica explícitamente el significado de las variables / efecto secundario, lo cual es bueno si el código es más extenso. Sin embargo, agradezco su opinión, no sabía que C ++ usaba tal fraseo.
eonist
3
A partir de Swift 4.2 puede usar guard let self = self else { return }para desenvolver [weak self]: github.com/apple/swift-evolution/blob/master/proposals/…
Amer Hukic
@AmerHukic 👌.
eonista
3

Puede usar [yo débil] o [yo no propio] en la lista de captura antes de sus parámetros del bloque. La lista de captura es una sintaxis opcional.

[unowned self]funciona bien aquí porque la celda nunca será nula. De lo contrario, puede usar[weak self]

Rufus
fuente
1
la célula no es propia, él no está en la clase celular, probablemente esté en un controlador de vista ...
Juan Boero
0

Si se estrella de lo que probablemente necesite [yo débil]

Supongo que el bloque que está creando todavía está conectado de alguna manera.

Cree un prepareForReuse e intente borrar el bloque onTextViewEditClosure dentro de eso.

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

Vea si eso previene el choque. (Es solo una suposición).

Michael Gray
fuente
0

Cierre y fuertes ciclos de referencia [Acerca de]

Como saben, el cierre de Swift puede capturar la instancia. Significa que puede usar selfdentro de un cierre. Especialmente escaping closure[Acerca de] puede crear un strong reference cycleque. Por cierto, tienes que usar explícitamente selfdentro escaping closure.

El cierre rápido tiene una Capture Listfunción que le permite evitar tal situación y romper un ciclo de referencia porque no tiene una referencia fuerte a la instancia capturada. El elemento Lista de captura es un par de weak/ unownedy una referencia a clase o variable.

Por ejemplo

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- más preferible, úselo cuando sea posible
  • unowned - Úselo cuando esté seguro de que la vida útil del propietario de la instancia es mayor que el cierre
yoAlex5
fuente