- PVSM.RU - https://www.pvsm.ru -
В 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/
Нажмите здесь для печати.