[Resumen de bloques de iOS]

Prefacio

El blog se centra principalmente en resumir y se actualiza en cualquier momento.

2. Bloquear

Funciones anónimas con variables automáticas (variables locales)

2.1 Especificaciones de uso del bloque

El formato completo de Block es: declaración de variable + definición
Por favor agregue la descripción de la imagen.:,

int(^sumBlk)(int num1, int num2) = ^int(int a, int b) {
    
    
        return a + b;
    };

Las variables de bloque son similares a los punteros de función,

  • Finalidad: variables automáticas (variables locales)
    • Parámetros de función
    • variable estática
    • variables globales estáticas
    • variables globales

Interceptación de variables automáticas : los valores de variables automáticas aparecen en el bloque como "valores de variables automáticas de interceptación".

Punto digno de mención: en los bloques actuales, el método de interceptación de variables automáticas no implementa la interceptación de matrices en lenguaje C.

2.2 modificador __bloque

Los bloques pueden interceptar variables, pero los valores de las variables no se pueden modificar en el bloque .

En este momento, usamos __blockmodificadores para modificar variables y usamos modificadores para modificar variables a las que se les blockdebe asignar valores para garantizar que a las variables se les puedan asignar valores.

// blk不允许修改截获变量的值,需要的时候加上__block修饰符即可
    id tstArray = @[@"blk", @"不允许赋值", @"给截获的变量"];
    __block id array = [[NSMutableArray alloc] init];
    void (^blk)(void) = ^{
    
    
        id obj = [[NSObject alloc] init];

//        [array addObject:obj];
        array = tstArray;
    };

2.3 Tipos de bloques

blockDividido en clasificaciones globales block, 堆 blocky 栈 block
de bloques, se resumen brevemente a continuación

  • A. No se hace referencia a variables externas: blockse almacenan en el área global
  • B. Se hace referencia a las variables externas: aquellas declaradas explícitamente como weaktipos blockse almacenan en el área de la pila; de lo contrario, existen en el área del montón, lo que significa blockque son strongde tipo.
  • NSGlobalBlock:
    No hay referencias a variables externas dentro del bloque. Los tipos de bloques son todos tipos NSGlobalBlock. Se almacenan en el área de datos globales y su memoria es administrada por el sistema. Las operaciones de retención, copia y liberación no son válidas. Este también es el caso si se accede a variables externas estáticas o globales.
  • NSStackBlock:
    Se accede a variables externas, pero no hay una referencia sólida que apunte a este bloque, como el bloque impreso directamente.
  • NSMallocBlock:
    En el entorno ARC, siempre que se acceda a variables externas y haya una referencia sólida que apunte al bloque (o como un valor de retorno de función), el tipo __NSStackBlock se copiará automáticamente al montón.

2.4 Referencia y solución de la circular de bloques

Introducción de escenarios de referencia circulares.

La referencia circular significa que el objeto no se puede liberar normalmente debido a que el objeto está en manos de la parte y se producirán pérdidas de memoria.

Las referencias fuertes entre sí en BLock pueden provocar referencias circulares.

typedef void(^TBlock)(void);

@interface ViewController ()
@property (nonatomic, strong) TBlock block;
@property (nonatomic, copy) NSString *name;
@end

@implementation ViewController

- (void)viewDidLoad {
    
    
    [super viewDidLoad];

    // 循环引用
    self.name = @"Hello";
    self.block = ^(){
    
    
        NSLog(@"%@", self.name);
    };
    self.block();
}

Aquí self contiene el bloque y el bloque se contiene a sí mismo, lo que da como resultado una referencia circular.
Mientras una de las partes no haga una referencia contundente, la referencia circular puede publicarse.

- (void)viewDidLoad {
    
    
    [super viewDidLoad];

    // 循环引用
    self.name = @"Hello";

    void(^block)(void);
    block = ^(){
    
    
        NSLog(@"%@", self.name);
    };
    block();
}

¿Por qué no hay ninguna referencia circular en este caso? Debido a que el yo actual, es decir, ViewController, no retiene el bloque con fuerza, el ciclo de vida del bloque está solo dentro del método viewDidLoad. Después de ejecutar el método viewDidLoad, el bloque se liberará.

Resolver referencias circulares

  1. __weak Porque __weak no realiza una operación de recuento de referencias en el objeto, es decir, no realiza una referencia fuerte.
  2. El caso anterior se modifica a débil para evitar referencias circulares de la siguiente manera:
    // 循环引用
    self.name = @"Hello";
    __weak typeof(self) weakSelf = self;
    self.block = ^(){
    
    
        NSLog(@"%@", weakSelf.name);
    };
    self.block();

En este momento, self sostiene el bloque y el bloque se refiere débilmente a self. La referencia débil automáticamente se volverá nula y la retención fuerte se interrumpirá, por lo que no provocará una referencia circular. Sin embargo, este método puede tener el problema de ser liberado a mitad de camino (retraso manual, el nombre puede haberse liberado cuando se llama a self.name) Si self se destruye, el bloque no puede obtener el nombre.

  1. La danza de la fuerza y ​​la debilidad:
    self.name = @"Hello";

    __weak typeof(self) weakSelf = self;
    self.block = ^(){
    
    
        __strong __typeof(weakSelf)strongWeak = weakSelf;

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    
            NSLog(@"%@", strongWeak.name);
        });
    };
    self.block();

Resuelve completamente el problema anterior de autoliberación a mitad de camino.
Principio: el método dealloc se llama después de completar las operaciones en el bloque. Después de agregar strongWeak, la relación de retención es: self -> block -> strongWeak -> débilSelf -> self.

Si se hace referencia fuerte a débilSelf, no se liberará automáticamente, porque strongWeak es solo una variable temporal y su período de declaración es solo dentro del bloque. Después de ejecutar el bloque, se liberará strongWeak y la referencia débil débilSelf también será liberado automáticamente.

  1. interrupción manual
    self.name = @"Hello";

    __block ViewController * ctrl = self;
    self.block = ^(){
    
    
        __strong __typeof(weakSelf)strongWeak = weakSelf;

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    
            NSLog(@"%@", ctrl.name);
            ctrl = nil;
        });
    };
    self.block();

Después de usar Ctrl, la relación de retención es: self -> block -> ctrl -> self. Ctrl está vacío después de usar el bloque. En este punto, la retención del bloque sobre self se libera y no constituye una referencia circular.

  1. El formulario de parámetro resuelve la transferencia de parámetros del bloque de referencia circular (copia de puntero)
    // 循环引用
    self.name = @"Hello";
    self.block = ^(ViewController * ctrl){
    
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    
            NSLog(@"%@", ctrl.name);
        });
    };
    self.block(self);

Inserte self en el bloque como parámetro y copie el puntero sin mantener self.

Escenario de referencia circular de bloques

Retenciones de variables estáticas

    // staticSelf_定义:
    static ViewController *staticSelf_;

    - (void)blockWeak_static {
    
    
        __weak typeof(self) weakSelf = self;
        staticSelf_ = weakSelf;
    }

Aunque débilSelf es una referencia débil, staticSelf_ es una variable estática y contiene débilSelf. staticSelf_ no se puede liberar, por lo que débilSelf tampoco se puede liberar. ¡Causa referencia circular!

2.5 Implementación y esencia de Block

La implementación de BLock se basa en punteros y punteros de función, y el atributo Block es un puntero a una estructura.

	//这是正常的block流程
    void(^myBlock)(void) = ^{
    
    
        printf("myBlock");
    };
    myBlock();

Obtenga la definición del código fuente de la estructura de bloque (C++):

    void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

La sintaxis del bloque en la parte de definición de inicialización se convierte en: &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
El proceso de llamada del bloque se convierte en:(__block_impl *)myBlock)->FuncPtr

2.5.1 Parte de inicialización

La parte de inicialización es la estructura del bloque.

//Block结构体
struct __main_block_impl_0 {
    
    
  struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
  struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    
    //__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
    impl.isa = &_NSConcreteStackBlock; // 
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

La estructura __main_block_impl_0, que es la estructura de bloque, contiene tres partes:

  • implicación de variable miembro
  • Puntero de descripción de variable miembro
  • __main_block_impl_0 constructor

struct __block_impl estructura : una estructura que contiene el puntero de función real de Block

struct __block_impl {
    
    
  void *isa;//用于保存Block结构体的实例指针
  int Flags;//标志位
  int Reserved;//今后版本升级所需的区域大小
  void *FuncPtr;//函数指针
};
  • _block_implContiene el puntero de función real FuncPtr del bloque. El puntero FuncPtr apunta a la parte principal del bloque, que es la parte del bloque correspondiente a ^{...} en el código OC.
  • También contiene indicadores que pueden usarse al implementar las operaciones internas del bloque.
  • Tamaño de área requerido para futuras actualizaciones de versionesReserved
  • __block_implpuntero de instancia de estructuraisa

estructura __main_block_desc_0 estructura:

Estructura de información adicional del bloque: contiene el tamaño del área requerida para futuras actualizaciones de la versión, el tamaño del bloque

static struct __main_block_desc_0 {
    
    
  size_t reserved;//今后版本升级所需区域大小
  size_t Block_size;//Block大小
} __main_block_desc_0_DATA = {
    
     0, sizeof(struct __main_block_impl_0)};

Constructor de bloques __main_block_impl_0 :

Como constructor, tenga en cuenta que tiene el mismo nombre que la estructura Block.
Responsable de inicializar las variables miembro de la estructura __main_block_impl_0 (es decir, la estructura Block struct __block_impl)

  //可以看到里面都是一些赋值操作
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    
    
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

2.5.2 Parte llamante

  • prototipo de función((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

Analice este código paso a paso:

  1. ((__block_impl *)myBlock)->FuncPtr: esta parte myBlockse convierte al __block_impltipo de puntero y accede FuncPtra los miembros. Obtiene el puntero de función almacenado dentro de la implementación del bloque.
  2. ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr): Aquí, el puntero de función se convierte en un tipo de función que acepta un __block_impl*parámetro de tipo y lo devuelve void. Convierte el puntero de función al tipo de función real que se puede llamar.
  3. ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock): Finalmente, myBlockel puntero de función resultante se llama usando como argumento. Llama a la función utilizando el objeto de implementación del bloque.

En resumen, este código obtiene el puntero de función almacenado dentro de la implementación del bloque y luego llama a la función, pasando el objeto de implementación del bloque como parámetro .

2.5.3 Captura de variables

Cuando Block captura variables, hay un * NSObject obj adicional en la estructura para que Block capture.

Esencia de bloque

  1. En una oración, Block es un objeto (su primer miembro interno es el puntero isa)
  2. ¿Por qué Block nació en la pila?
  • En su función de inicialización, Por favor agregue la descripción de la imagen.
    impl.isa = &_NSConcreteStackBlock;
    _NSConcreteStackBlock es equivalente a la clase principal de la instancia del bloque . Cuando se llama a Block como un objeto OC, la información sobre la clase se coloca en _NSConcretestackBlock, lo que también demuestra que el bloque nace en la pila. .superior

2.6 Bloque captura variables y objetos

variable

  • Variables globales: no capturar
  • Variables locales: valores de captura
  • Variables globales estáticas: no capturar
  • Variables locales estáticas: puntero de captura
  • constante constante local modificada: valor de captura
  • Constantes locales estáticas modificadas const: captura de punteros

Object
BLOCK puede capturar objetos, de los cuales es necesario conocer dos métodos.

Al capturar el objeto aparece el código _main_block_copy_0y _main_block_depose_0.

  • __main_block_copy_0La función es llamar _Block_object_assign, lo que equivale a retainasignar el objeto a la variable de estructura del tipo de objeto __main_block_impl_0. Se llama cuando el bloque de la pila se copia al montón.
  • __main_block_dispose_0Llamar _Block_object_disposeequivale releasea liberar el objeto asignado a la variable de estructura del tipo de objeto. Se llamará cuando se descarte el bloque en el montón.
    Por favor agregue la descripción de la imagen.

2.7 Gestión de memoria de bloques

Análisis de memoria de bloques

Al usar bloques, los bloques de montón deben prestar atención al problema del alcance. La asignación de puntero de referencia débil se liberará cuando salga del alcance. Esto se puede resolver mediante una referencia fuerte.

3 Resumen de los problemas de Blcok

1. Block está modificando NSMutableArray, ¿necesito agregar __block?

int main(int argc, const char * argv[]) {
    
    
    @autoreleasepool {
    
    
        NSMutableArray *array = [NSMutableArray array];
        Block block = ^{
    
    
            [array addObject: @"20"];
            [array addObject: @"30"];
            NSLog(@"%@",array);
        };
        block();
    }
    return 0;

Se puede ejecutar correctamente porque blockla dirección de memoria de la matriz solo se usa en el bloque y el contenido se agrega a la dirección de memoria sin modificar la dirección de memoria de arry, por lo que la matriz se puede compilar correctamente sin usar la modificación __block.

️Entonces , cuando solo use la dirección de memoria de las variables locales en lugar de modificarlas, intente no agregar __block. A través del análisis anterior, sabemos que una vez que se agrega el modificador __block, el sistema creará automáticamente la estructura correspondiente, ocupando espacio innecesario. espacio de memoria.

2. ¿Cómo modifica __block el valor interno?

El bloque puede capturar valores, consulte el siguiente código
Por favor agregue la descripción de la imagen.

Por favor agregue la descripción de la imagen.

Principio: Primero, la variable de edad modificada por __block se declara en una estructura __Block_byref_age_0 llamada edad, es decir, si se agrega la modificación de __block, las variables capturadas en el bloque serán una estructura de tipo __Block_byref_age_0

__estructura Block_byref_age_0

  • Puntero __isa: también hay un puntero isa en __Block_byref_age_0, lo que significa que __Block_byref_age_0 es esencialmente un objeto.
  • * __forwarding: __forwarding es del tipo de estructura __Block_byref_age_0 y el valor almacenado en __forwarding es (__Block_byref_age_0 )&age, que es la dirección de memoria de la estructura misma.
  • __banderas: 0
  • __size: sizeof(__Block_byref_age_0) es el espacio de memoria ocupado por __Block_byref_age_0.
  • edad: donde realmente se almacenan las variables, aquí se almacenan las variables locales 10.

Luego almacene la edad de la estructura __Block_byref_age_0 en la estructura __main_block_impl_0 y asígnela a __Block_byref_age_0 *age,
luego llame al bloque, primero saque la edad en __main_block_impl_0 y obtenga el puntero __forwarding a través de la estructura de edad. Lo que se guarda en __forwarding es la estructura __Block_byref_age_0 En sí, aquí está age(__Block_byref_age_0), obtiene la variable age(10) en la estructura a través de __forwarding y modifica su valor.

__forwarding es un puntero a sí mismo.
Por favor agregue la descripción de la imagen.

Resumen: La razón por la que __block puede modificar el valor de una variable es porque __block empaqueta la variable en un objeto con un puntero y luego encapsula la edad en la estructura. Las variables almacenadas dentro del bloque son punteros de estructura y la memoria también se puede encontrar a través de el puntero La dirección modifica el valor de la variable.

3. Resumen de __block y Block

Un bloque es esencialmente un objeto, implementado mediante una estructura.

//Block结构体
struct __main_block_impl_0 {
    
    
  struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
  struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    
    //__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
    impl.isa = &_NSConcreteStackBlock; // 
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

Función __block: Puede obtener el puntero de la variable correspondiente para que pueda modificarse dentro del bloque.

La estructura de datos de __block es la siguiente

struct __Block_byref_a_0 {
    
    
  void *__isa;//有isa,是一个对象
__Block_byref_a_0 *__forwarding;//指向自身类型对象的指针
 int __flags;//不用关心
 int __size;//自己所占大小
 int a;//被封装的 基本数据类型变量
};

4. ¿Por qué se modifica Block con copia?

Debido a que la dirección de memoria del Bloque se muestra en el área de la pila, la característica del área de la pila es que el objeto creado se destruye en cualquier momento. Una vez destruido, las llamadas posteriores al objeto vacío provocarán que el programa se bloquee.
Después de realizar la operación de copia en el bloque, el bloque existe en el área del montón, por lo que se utiliza la modificación Copiar cuando se utiliza el atributo Bloque.

El bloque en el montón es el bloque modificado por copia. Su ciclo de vida termina con la destrucción del objeto. Mientras el objeto no se destruya, podemos llamar al bloque en el montón.

Es por eso que usamos copiar para modificar el bloque. Porque el bloque que accede a variables externas sin modificación de copia solo se puede utilizar en el momento en que se llama a la función en la que se encuentra. Luego desapareció.

Nota: Los bloques ARC generalmente están en el montón, porque el sistema utiliza de forma predeterminada los bloques en el montón para las operaciones de copia. Independientemente de ARC o MRC, modificar Block con copia Strong es una pila de Bloques.

5. ¿Cuál es el principio de captura de variables en bloque?

  • Cuando se ejecuta la gramática Block, Blockel valor de la variable automática utilizada en la expresión gramatical se guarda en Blockla instancia de la estructura, es decir, el bloque mismo.
  • Un punto que vale la pena explicar aquí es que si hay muchas variables automáticas, variables estáticas, etc. fuera del Bloque, estas variables no se utilizarán dentro del Bloque. Entonces estas variables no serán capturadas por Block, es decir, sus valores no serán pasados ​​en el constructor.
  • El bloque captura variables externas solo para capturar los valores que se utilizarán en el cierre del bloque. No capturará otros valores no utilizados.
    Insertar descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/weixin_61639290/article/details/131802720
Recomendado
Clasificación