Устройство блоков в Objective-C

в 9:57, , рубрики: Блог компании e-Legion Ltd., разработка под iOS

image
В Objective-C есть такая штука, как блоки, которая является реализацией концепции замыканий.

Есть много статей о том, как правильно использовать блоки (когда вызывать copy, как избавиться от retain циклов и т. д.), но при этом устройства блоков обычно не затрагивают. Собственно, давайте восполним этот пробел.

Инструменты

Не все знают, но в клэнге есть опция -rewrite-objc, которая преобразует код с Objective-C на C++. Именно на C++, а не C потому, что помимо Obective-C поддерживается и Objective-C++.

Используется она следующим образом:

clang -rewrite-objc -ObjC main.m -o out.cpp

Собственно, идея в том, чтобы используя эту опцию, понять, во что превращаются блоки, и таким образом, избавиться от анализа ассемблерного кода, полученного на выходе компилятора.

Преобразуем код

Итак, рассмотрим следующий код:

#import <Foundation/Foundation.h>

typedef int (^blk_t)(int intVar);

int main(int argc, const char * argv[])
{
    __block NSString *blockString;
    NSString *string;
    
    blk_t blk = ^(int intVar) {
        blockString = @"Hello";
        NSLog(@"%@", string);
        return intVar;
    };
    
    blk(0);
  
    return 0;
}

После преобразование код будет выглядеть так (незначимые части опущены):

#ifndef BLOCK_IMPL
#define BLOCK_IMPL
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
// Runtime copy/destroy helper functions (from Block_private.h)
#ifdef __OBJC_EXPORT_BLOCKS
extern "C" __declspec(dllexport) void _Block_object_assign(void *, const void *, const int);
extern "C" __declspec(dllexport) void _Block_object_dispose(const void *, const int);
extern "C" __declspec(dllexport) void *_NSConcreteGlobalBlock[32];
extern "C" __declspec(dllexport) void *_NSConcreteStackBlock[32];
#else
__OBJC_RW_DLLIMPORT void _Block_object_assign(void *, const void *, const int);
__OBJC_RW_DLLIMPORT void _Block_object_dispose(const void *, const int);
__OBJC_RW_DLLIMPORT void *_NSConcreteGlobalBlock[32];
__OBJC_RW_DLLIMPORT void *_NSConcreteStackBlock[32];
#endif
#endif
#define __block
#define __weak

typedef int (*blk_t)(int intVar);

struct __Block_byref_blockString_0 {
  void *__isa;
__Block_byref_blockString_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *blockString;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSString *string;
  __Block_byref_blockString_0 *blockString; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString *_string, __Block_byref_blockString_0 *_blockString, int flags=0) : string(_string), blockString(_blockString->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int intVar) {
  __Block_byref_blockString_0 *blockString = __cself->blockString; // bound by ref
  NSString *string = __cself->string; // bound by copy

        (blockString->__forwarding->blockString) = (NSString *)&__NSConstantStringImpl__var_folders_v1_v8wjvjd96vl0nhqsjtxs_c2h0000gn_T_main_1cea37_mi_0;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_v1_v8wjvjd96vl0nhqsjtxs_c2h0000gn_T_main_1cea37_mi_1, string);
        return intVar;
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->blockString, (void*)src->blockString, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->string, (void*)src->string, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->blockString, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->string, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[])
{
    __attribute__((__blocks__(byref))) __Block_byref_blockString_0 blockString = {(void*)0,(__Block_byref_blockString_0 *)&blockString, 33554432, sizeof(__Block_byref_blockString_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131};
;
    NSString *string;

    blk_t blk = (int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, string, (__Block_byref_blockString_0 *)&blockString, 570425344);

    ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 0);

    return 0;
}

Что же такое блок

На первый взгляд C++ код выглядит запутанно, но давайте по порядку.

Создание блока:

blk_t blk = (int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, string, (__Block_byref_blockString_0 *)&blockString, 570425344);

Данная строка создает структуру посредством вызова ее конструктора по умолчанию (да, в C++ есть такая штука) и присваивает ее переменной, хранящей блок. Таким образом, выходит, что блок является структурой.

Давайте теперь взглянем на эту структуру.

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSString *string;
  __Block_byref_blockString_0 *blockString; // by ref

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString *_string, __Block_byref_blockString_0 *_blockString, int flags=0) : string(_string), blockString(_blockString->__forwarding) 
{
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

Как можно заметить, структура __block_impl является основой для блока. И содержит поле isa, которое знакомо каждому программисту на Objective-C.
На всякий случай напомню: каждый объект Objective-C содержит в себе атрибут isa — указатель на class object для данного объекта.

Основа любого объекта в Objective-C выглядит так:

typedef struct objc_class *Class;

struct objc_object {
    Class isa ;
};

Таким образом, приходим к неожиданному выводу — блоки являются классами!
Хотя, чего тут неожиданного, если во времена MRC блокам регулярно приходилось слать copy.

- (void)someMethod {
    return [^() {
        ...
    } copy];
}

Если взглянуть на конструктор структуры __main_block_impl_0, то можно увидеть, чем инициализируются поля структуры блока при его создании.

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString *_string, __Block_byref_blockString_0 *_blockString, int flags=0) : string(_string), blockString(_blockString->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

Т.е получается, что в данном случае наш блок является объектом типа _NSConcreteStackBlock.
FuncPtr — хранит ссылку на C функцию, являющуюся телом блока
flags — равно нулю
desc — содержит информацию о размере структуры __main_block_impl_0 и функции для копирования блоков

Переменные

Помимо стандартных для любого блока полей, структура __main_block_impl_0 хранит переменные, захватываемые блоком

  NSString *string;
  __Block_byref_blockString_0 *blockString; 

Как можно заметить, переменная, захватываемая с модификатором __block оборачивается в структуру __Block_byref_blockString_0

struct __Block_byref_blockString_0 {
  void *__isa;
__Block_byref_blockString_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *blockString;
};

Инициализация которой выглядит так:

__attribute__((__blocks__(byref))) __Block_byref_blockString_0 blockString = {(void*)0,(__Block_byref_blockString_0 *)&blockString, 33554432, sizeof(__Block_byref_blockString_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131};
;

Т.е получается, что оригинальный объект хранится в поле blockString. А поле __forwarding указывает само на себя.
Кстати в __main_block_impl_0 поле blockString инициализируется значением_blockString->__forwarding.
Собственно поле __forwarding сделано для того, чтобы несколько блоков могли ссылаться на одну переменную, которая находится на стеке или в куче.

Заключение

Итак, можно сказать, что блок — это экземпляр особого класса (NSMallocBlock, NSGlobalBlock, NSStackBlock) который обязательно содержит ссылку (поле FuncPtr) на C функцию, являющуюся телом блока, и, опционально, переменные, захватываемые блоком.

То, что блок является классом, значит то, что у него можно вызвать всякие интересные методы (в том числе добавленные самостоятельно через категории). Список уже имеющихся можно поискать тут.

Все блоки (Global, Stack, Malloc) наследуются от базового NSBlock, описание которого выглядит так:

@interface NSBlock : NSObject <NSCopying> {
}

- (id)copy;
- (id)copyWithZone:(struct _NSZone { }*)arg1;
- (void)invoke;
- (void)performAfterDelay:(double)arg1;

@end

Так как блок является наследником NSObject, то у него можно вызвать всеми любимый метод description, который поможет вам понять, где хранится ваш блок. Например,

NSLog(@"%@", [^{} description]);

выведет <__NSGlobalBlock__: 0x1000010c0>. Что нам намекает, что используемый нами блок хранится в глобальной памяти.
В качестве альтернативы description можно использовать метод class.

Бонус

Давайте шутки ради добавим блоку метод repeat.

#import <Foundation/Foundation.h>

@interface NSBlock : NSObject <NSCopying>

- (void)invoke;

@end

@interface NSBlock (Ext)

- (void)repeat:(NSUInteger)count;

@end

@implementation NSBlock (Ext)

- (void)repeat:(NSUInteger)count {
    for (int i = 0; i < count; i++) {
        [(NSBlock *)self invoke];
    }
}

@end

int main(int argc, const char * argv[])
{
    [^{
        NSLog(@"Hello");
    } repeat:3];
    
    return 0;
}

Постскриптум

Я сознательно опустил информацию о деталях, касающихся управления памятью (копирование блоков, ...).
Если кому-то будет интересно, то пишите. Постараюсь осветить эту тему.

Автор: Fanruten

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js