iOS: подготовка к собеседованию 3 — Блоки и замыкания
Обзор
Замыкание или блок — это единица кода, которую можно передать и выполнить позже. Они ведут себя очень похоже на функции, но они более гибкие и мощные. Потому что их можно передавать в качестве аргументов функциям, хранить в переменных и даже использовать в качестве возвращаемых значений.
Вопросы для интервью
- Могут ли блоки захватывать переменные из окружающей их области? Объясните на примере.
- Как избежать создания строгого цикла ссылок при использовании замыкания или блока?
- В каких ситуациях вы бы использовали
weak
ключевое слово, и в каких ситуациях вы бы использовалиunowned
ключевое слово? - Какова цель модификатора
__block
в блоках Objective-C? Объясните на примере.
Блоки реализованы как объекты типа Objective-C, NSBlock
они представляют собой просто структуры с isa
указателем. Литералы напрямую транслируются компилятором в структуры. Каждый объект блока содержит указатель на исполняемый код, а также любые захваченные переменные, на которые ссылался блок. Когда блок создается, он захватывает копию значений любых переменных внутри блока. Эти захваченные значения сохраняются до тех пор, пока блок не будет освобожден.
^ { 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 }
Замыкание/блок может захватывать и хранить ссылки на любые константы и переменные из окружающего контекста, в котором оно определено. Capture позволяет им «упаковать» и сделать «моментальный снимок» текущего состояния области охвата.
Это мощно, потому что позволяет замыканию получать доступ и изменять значения в контексте, в котором оно определено. Это может быть особенно полезно при работе с асинхронным кодом, поскольку позволяет замыканию получать доступ и изменять значения, которые могли измениться к моменту выполнения замыкания.
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 предоставляет синтаксическую функцию, называемую списком захвата, которая позволяет вам указать, как замыкание захватывает значения переменных, которые используются в замыкании.
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()
В Objective-C примитивные типы фиксируются как неизменяемые константные типы. Чтобы обновить значения захваченных переменных, вы можете использовать __block
ключевое слово. Для ссылочных типов блок будет поддерживать строгую ссылку на эти объекты, что предотвращает их освобождение, пока блок все еще используется.
При работе с изменяемыми объектами, например NSMutableArray
- Если вам нужно изменить содержимое переменной, вам не нужно использовать __block
- Если вам нужно изменить саму переменную, тогда вам нужно использовать __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()
}
}
}
И когда блок вызывается, мы создаем сильную ссылку на слабую ссылку на себя. Это позволяет вам получить доступ self
изнутри блока, не беспокоясь о том, что он будет освобожден до завершения блока.
__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
}
};