Preparação de entrevista para iOS 3 - bloqueios e fechamentos

May 01 2023
Visão geral Encerramento ou bloco é uma unidade de código que pode ser transmitida e executada posteriormente. Eles se comportam de maneira muito semelhante às funções, mas são mais flexíveis e poderosos.

Visão geral

Encerramento ou bloqueio é uma unidade de código que pode ser transmitida e executada posteriormente. Eles se comportam de maneira muito semelhante às funções, mas são mais flexíveis e poderosos. Porque eles podem ser passados ​​como argumentos para funções, armazenados em variáveis ​​e até mesmo usados ​​como valores de retorno.

Questões de entrevista

  • Os blocos podem capturar variáveis ​​de seu escopo circundante? Explique com um exemplo.
  • Como você pode evitar a criação de um ciclo de referência forte ao usar um fechamento ou bloco?
  • Em que situações você usaria a weakpalavra-chave e em quais situações você usaria a unownedpalavra-chave?
  • Qual é o propósito do __blockmodificador em blocos Objective-C? Explique com um exemplo.

Blocos são implementados como objetos Objective-C do tipo NSBlockque são apenas structs com um isaponteiro. Os literais são traduzidos diretamente em structs pelo compilador. Cada objeto de bloco contém um ponteiro para o código executável, bem como quaisquer variáveis ​​capturadas que foram referenciadas no bloco. Quando um bloco é criado, ele captura uma cópia dos valores de quaisquer variáveis ​​dentro do bloco. Esses valores capturados são retidos até que o bloco seja desalocado.

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

Um fechamento/bloco pode capturar e armazenar referências a quaisquer constantes e variáveis ​​do contexto circundante no qual é definido. A captura permite que eles “empacotem” e tirem um “instantâneo” do estado atual do escopo de inclusão.

Isso é poderoso porque permite que um encerramento acesse e modifique valores no contexto em que é definido. Isso pode ser particularmente útil ao trabalhar com código assíncrono, pois permite que um encerramento acesse e modifique valores que podem ter mudado no momento em que o encerramento é executado.

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

O Swift fornece um recurso de sintaxe chamado lista de captura que permite especificar como um fechamento captura os valores das variáveis ​​que são usadas dentro do fechamento.

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

Em Objective-C, os tipos primitivos são capturados como tipos const imutáveis. Para atualizar os valores das variáveis ​​capturadas, você pode usar a __blockpalavra-chave. Para tipos de referência, o bloco manterá uma referência forte a esses objetos, o que os impedirá de serem desalocados enquanto o bloco ainda estiver em uso.

Ao trabalhar com objetos mutáveis, por exemplo, um NSMutableArray

  • Se você precisar alterar o conteúdo da variável, não precisará usar __block
  • Se você precisar alterar a própria variável, precisará 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()
        }
    }
}

E quando o bloco é invocado, criamos uma referência forte para a auto-referência fraca. Isso permite que você acesse selfde dentro do bloco, sem se preocupar com a desalocação antes que o bloco seja concluído.

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