- PVSM.RU - https://www.pvsm.ru -

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

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

Есть много статей о том, как правильно использовать блоки (когда вызывать 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 функцию, являющуюся телом блока, и, опционально, переменные, захватываемые блоком.

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

Все блоки (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

Источник [3]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/razrabotka-pod-ios/60971

Ссылки в тексте:

[1] замыканий: http://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%BC%D1%8B%D0%BA%D0%B0%D0%BD%D0%B8%D0%B5_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)

[2] тут: https://github.com/nst/iOS-Runtime-Headers/blob/d576a9cc197412e81aa87624b755e59b4f00e3cd/Frameworks/CoreFoundation.framework/

[3] Источник: http://habrahabr.ru/post/224363/