В этой статье я расскажу о расположении блоков (__NSStackBlock__/__NSGlobalBlock__/__NSMallocBlock__), о том, как происходит захват переменных и как это связано с тем, во что компилируется блок.
В данный момент, применение блоков в Objective-C начинается практически с первых дней изучения этого языка. Но в большинстве случаев разработчики не задумываются, как блоки работают внутри. Здесь не будет магии, я просто расскажу об этом подробнее.
Начнем с самого начала, как выглядит блок в Objective-C
Для чего применяются блоки я расписывать не буду, речь идет не об этом, поэтому давайте сразу рассмотрим интересные моменты на практике.
Что такое блок? В первую очередь, блок — это объект.
id thisIsBlock = ^{ };
Для понимания рассмотрим что такое объект
struct objc_object { Class isa OBJC_ISA_AVAILABILITY; }; /// A pointer to an instance of a class. typedef struct objc_object *id;
А у блока есть метод
- (Class)class
который возвращает isa
Воспользуемся тем, что знаем теперь и посмотрим какие классы имеет блок в какой ситуации
int foo = 3; Class class = [^{ int foo1 = foo + 1; } class]; NSLog(@"%@", NSStringFromClass(class));
2015-11-29 22:30:21.054 block_testing[99727:13189641] __NSStackBlock__
int foo = 3; Class class = [[^{ int foo1 = foo + 1; } copy] class]; NSLog(@"%@", NSStringFromClass(class));
2015-11-29 22:33:45.026 block_testing[99735:13190778] __NSMallocBlock__
Class class = [^{ } class]; NSLog(@"%@", NSStringFromClass(class));
2015-11-29 22:34:49.645 block_testing[99743:13191389] __NSGlobalBlock__
Но рассмотрим еще один вариант __NSMallocBlock__
int foo = 3; id thisIsBlock = ^{ int foo1 = foo + 1; }; Class class = [thisIsBlock class]; NSLog(@"%@", NSStringFromClass(class));
2015-11-29 22:37:27.638 block_testing[99751:13192462] __NSMallocBlock__
Как видно, если блок не захватывает внешние переменные, то мы получаем __NSGlobalBlock__
Class class = [[^{ } copy] class]; NSLog(@"%@", NSStringFromClass(class));
id thisIsBlock = ^{ }; Class class = [thisIsBlock class]; NSLog(@"%@", NSStringFromClass(class));
__NSGlobalBlock__
Если же блок захватывает внешние переменные, то блок __NSStackBlock__ (на стеке). Однако если же послать блоку ‘copy’, то блок переместится в кучу (__NSMallocBlock__).
ARC нам в этом помогает, и при присваивание блока в переменную (__strong) произойдет копирование блока в кучу, что можно заметить на примере выше. В общем, еще раз скажем ARC спасибо, ведь в MRC мы могли получить крайне неприятные баги
/*! * @typedef dispatch_block_t * * @abstract * The type of blocks submitted to dispatch queues, which take no arguments * and have no return value. * * @discussion * When not building with Objective-C ARC, a block object allocated on or * copied to the heap must be released with a -[release] message or the * Block_release() function. * * The declaration of a block literal allocates storage on the stack. * Therefore, this is an invalid construct: * <code> * dispatch_block_t block; * if (x) { * block = ^{ printf("true\n"); }; * } else { * block = ^{ printf("false\n"); }; * } * block(); // unsafe!!! * </code> * * What is happening behind the scenes: * <code> * if (x) { * struct Block __tmp_1 = ...; // setup details * block = &__tmp_1; * } else { * struct Block __tmp_2 = ...; // setup details * block = &__tmp_2; * } * </code> * * As the example demonstrates, the address of a stack variable is escaping the * scope in which it is allocated. That is a classic C bug. * * Instead, the block literal must be copied to the heap with the Block_copy() * function or by sending it a -[copy] message. */ typedef void (^dispatch_block_t)(void);
Для чего тогда проперти обозначаются как ‘copy’?
Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior. For more information, see Blocks Programming Topics.
ссылка
Поэтому продолжайте и дальше писать property copy для блоков.
Так же рассмотрим еще один небольшой пример о __NSStackBlock__
typedef void(^EmptyBlock)(); void fooFunc(EmptyBlock block) { Class class = [block class]; NSLog(@"%@", NSStringFromClass(class)); } int main(int argc, const char * argv[]) { @autoreleasepool { int foo = 1; fooFunc(^{ int foo1 = foo + 1; }); } return 0; }
2015-11-29 22:52:16.905 block_testing[99800:13197825] __NSStackBlock__
В статье столько раз было сказано о захвате внешних переменных, однако мы все еще не сказали, как это происходит. В этом нам поможет документация clang. Не будем ее расписывать полностью, выделим лишь нужные идеи.
Блок превращается в структуру, а захваты превращаются в поля.
Это не сложно проверить практически
clang -rewrite-objc -ObjC main.m -o out.cpp
и можно посмотреть весь процесс в C++ коде
MyObject *myObject = [[MyObject alloc] init]; NSLog(@"object %p, ptr myObject %p", myObject, &myObject); ^{ NSLog(@"object %p, ptr myObject %p", myObject, &myObject); }();
2015-11-29 23:12:37.297 block_testing[99850:13203592] object 0x100111e10, ptr myObject 0x7fff5fbff798
2015-11-29 23:12:37.298 block_testing[99850:13203592] object 0x100111e10, ptr myObject 0x7fff5fbff790
Как видно, указатель myObject снаружи и внутри блока указывает на одну и ту же область памяти, однако сам указатель отличается.
Блок создает внутри себя константные локальные переменные и указатели того, что захватывает. Что при использовании объектов увеличит кол-во указателей, по обычной для ARC логике (при копировании блока в кучу).
Однако при использовании __block произойдет inout, поэтому счетчик ссылок не увеличиться (однако не нужно использовать всегда и везде, const — это хорошее слово)
Для закрепления рассмотрим небольшой пример
#import <Foundation/Foundation.h> typedef NSInteger (^IncrementBlock)(); IncrementBlock createIncrementBlock(const NSInteger start, const NSInteger incrementValue) { __block NSInteger acc = start; return ^NSInteger{ acc += incrementValue; return acc; }; } int main(int argc, const char * argv[]) { @autoreleasepool { IncrementBlock incrementBlock = createIncrementBlock(0, 2); NSLog(@"%ld", incrementBlock()); NSLog(@"%ld", incrementBlock()); NSLog(@"%ld", incrementBlock()); IncrementBlock incrementBlock1 = createIncrementBlock(0, 2); NSLog(@"%ld", incrementBlock1()); NSLog(@"%ld", incrementBlock1()); NSLog(@"%ld", incrementBlock1()); } return 0; }
2015-11-29 23:31:24.027 block_testing[99910:13209611] 2
2015-11-29 23:31:24.028 block_testing[99910:13209611] 4
2015-11-29 23:31:24.028 block_testing[99910:13209611] 6
2015-11-29 23:31:24.028 block_testing[99910:13209611] 2
2015-11-29 23:31:24.028 block_testing[99910:13209611] 4
2015-11-29 23:31:24.029 block_testing[99910:13209611] 6
Блок IncrementBlock был превращен компилятором в структуру, при возврате блока из функции произошло копирование текущего области, и тем самым мы получили структуру, у которой есть поле, в которой хранится аккумулятор. А на каждый вызов функции createIncrementBlock мы получаем новый экземпляр.
Остановлю так же внимание на случай использования self внутри блока (что такое self). После прочтения статьи должно стать понятно, что использование self внутри __NSMallocBlock__ приведет к увеличению счетчика ссылок, однако вовсе не означает retain cycle. retain cycle это когда объект держит блок, а блок держит объект, который держит блок…
Параноя везде использовать __weak __strong не нужна, для нас блок — это объект. Просто объект, который можно сохранить, передать, использовать, с которым действуют обычные правила управления памятью.
ссылка на оригинал статьи http://habrahabr.ru/post/271255/
Добавить комментарий