iOS Interview Prep 3 - Blocchi e chiusure

May 01 2023
Panoramica Chiusura o blocco è un'unità di codice che può essere passata ed eseguita in un secondo momento. Si comportano in modo molto simile alle funzioni, ma sono più flessibili e potenti.

Panoramica

Chiusura o blocco è un'unità di codice che può essere passata ed eseguita in un secondo momento. Si comportano in modo molto simile alle funzioni, ma sono più flessibili e potenti. Perché possono essere passati come argomenti a funzioni, archiviati in variabili e persino usati come valori di ritorno.

Domande di un'intervista

  • I blocchi possono acquisire variabili dal loro ambito circostante? Spiega con un esempio.
  • Come evitare di creare un forte ciclo di riferimento quando si utilizza una chiusura o un blocco?
  • In quali situazioni useresti la weakparola chiave e in quali situazioni useresti la unownedparola chiave?
  • Qual è lo scopo del __blockmodificatore nei blocchi Objective-C? Spiega con un esempio.

I blocchi sono implementati come oggetti Objective-C di tipo NSBlocksono solo strutture con un isapuntatore. I letterali vengono tradotti direttamente in struct dal compilatore. Ogni oggetto blocco contiene un puntatore al codice eseguibile, nonché qualsiasi variabile catturata a cui si fa riferimento nel blocco. Quando viene creato un blocco, acquisisce una copia dei valori di qualsiasi variabile all'interno del blocco. Questi valori acquisiti vengono mantenuti fino a quando il blocco non viene deallocato.

^ { 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 }

Una chiusura/blocco può acquisire e memorizzare riferimenti a qualsiasi costante e variabile dal contesto circostante in cui è definita. Capture consente loro di "impacchettare" e acquisire un'"istantanea" dell'attuale stato dell'ambito di inclusione.

Questo è potente perché consente a una chiusura di accedere e modificare i valori nel contesto in cui è definita. Ciò può essere particolarmente utile quando si lavora con codice asincrono, perché consente a una chiusura di accedere e modificare valori che potrebbero essere cambiati al momento dell'esecuzione della chiusura.

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 fornisce una funzionalità di sintassi chiamata elenco di acquisizione che consente di specificare in che modo una chiusura acquisisce i valori delle variabili utilizzate all'interno della chiusura.

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()

In Objective-C, i tipi primitivi vengono acquisiti come tipi const immutabili. Per aggiornare i valori delle variabili acquisite, è possibile utilizzare la __blockparola chiave. Per i tipi di riferimento, block manterrà un forte riferimento a quegli oggetti, impedendo loro di essere deallocati mentre il blocco è ancora in uso.

Quando si lavora con oggetti mutabili, ad esempio un NSMutableArray

  • Se è necessario modificare il contenuto della variabile, non è necessario utilizzare __block
  • Se hai bisogno di cambiare la variabile stessa, allora devi usare __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()
        }
    }
}

E quando il blocco viene invocato, creiamo un forte riferimento al debole autoreferenzialità. Ciò ti consente di accedere selfdall'interno del blocco, senza preoccuparti che venga deallocato prima del completamento del blocco.

__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
  }
};