Preparación para la entrevista de iOS 3: bloqueos y cierres

May 01 2023
Descripción general El cierre o bloque es una unidad de código que se puede pasar y ejecutar en un momento posterior. Se comportan de forma muy similar a las funciones, pero son más flexibles y potentes.

Descripción general

El cierre o bloque es una unidad de código que se puede pasar y ejecutar en un momento posterior. Se comportan de forma muy similar a las funciones, pero son más flexibles y potentes. Porque pueden pasarse como argumentos a funciones, almacenarse en variables e incluso usarse como valores de retorno.

Preguntas de entrevista

  • ¿Pueden los bloques capturar variables de su ámbito circundante? Explique con un ejemplo.
  • ¿Cómo puede evitar crear un ciclo de referencia fuerte cuando usa un cierre o un bloque?
  • ¿En qué situaciones usaría la weakpalabra clave y en qué situaciones usaría la unownedpalabra clave?
  • ¿ Cuál es el propósito del __blockmodificador en los bloques Objective-C? Explique con un ejemplo.

Los bloques se implementan como objetos de tipo Objective-C, NSBlockson solo estructuras con un isapuntero. El compilador traduce directamente los literales a estructuras. Cada objeto de bloque contiene un puntero al código ejecutable, así como cualquier variable capturada a la que se haga referencia en el bloque. Cuando se crea un bloque, captura una copia de los valores de cualquier variable dentro del bloque. Estos valores capturados se conservan hasta que se desasigna el bloque.

^ { printf("hello world\\n"); }

struct __block_literal_1 {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(struct __block_literal_1 *);
    struct __block_descriptor_1 *descriptor;
};

void __block_invoke_1(struct __block_literal_1 *_block) {
    printf("hello world\\n");
}

static struct __block_descriptor_1 {
    unsigned long int reserved;
    unsigned long int Block_size;
} __block_descriptor_1 = { 0, sizeof(struct __block_literal_1), __block_invoke_1 }

Un cierre/bloque puede capturar y almacenar referencias a cualquier constante y variable del contexto circundante en el que se define. Capture les permite "empaquetar" y tomar una "instantánea" del estado actual del alcance envolvente.

Esto es poderoso porque permite que un cierre acceda y modifique valores en el contexto en el que se define. Esto puede ser particularmente útil cuando se trabaja con código asíncrono, porque permite que un cierre acceda y modifique valores que pueden haber cambiado en el momento en que se ejecuta el cierre.

let url = URL(string: "<https://www.example.com>")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
  DispatchQueue.main.async {
    self.updateUI(with: data)
  }
}

task.resume()s

Swift proporciona una característica de sintaxis llamada lista de captura que le permite especificar cómo un cierre captura los valores de las variables que se usan dentro del cierre.

let val = 5

// Create a closure that increments the value of val
let myClosure = { [val] in
    // The closure captures the original value of val
    print("Original value of val: \\(val)")
}
val += 10
myClosure()

En Objective-C, los tipos primitivos se capturan como tipos constantes inmutables. Para actualizar los valores de las variables capturadas, puede utilizar la __blockpalabra clave. Para los tipos de referencia, el bloque mantendrá una fuerte referencia a esos objetos, lo que evita que se desasignen mientras el bloque aún está en uso.

Cuando se trabaja con objetos mutables, por ejemplo, un NSMutableArray

  • Si necesita mutar el contenido de la variable, no necesita usar __block
  • Si necesita cambiar la variable en sí, entonces necesita usar __block
  • int val = 5;
    __block int mutableVal = 5;
    
    // Create a block that increments the value of val
    void (^myBlock)(void) = ^{
        val++;
      mutableVal++;
    };
    
    // Call the block, which will increment the value of val
    myBlock();
    
    // Print the value of val to the console
    NSLog(@"Value of val inside block: %d", val);  // This prints 5
    NSLog(@"Value of val inside block: %d", mutableVal); // This prints 6
    

class MyClass {
    var myClosure: (() -> Void)?

    func someMethod() {
        myClosure = { [weak self]
            guard let strongSelf = self else { return }
            strongSelf.doSomething()
        }
    }
}

Y cuando se invoca el bloque, creamos una fuerte referencia a la débil auto referencia. Esto le permite acceder selfdesde dentro del bloque, sin preocuparse de que se desasigne antes de que se complete el bloque.

__weak typeof(self) weakSelf = self;

void (^myBlock)(void) = ^{
  // Maintain a strong reference to self to keep it in memory
  __strong typeof(self) strongSelf = weakSelf;
  // Check if self has been deallocated, if so return
    if (strongSelf) {
    // Do something
  }
};