В OS X 10.6 и iOS 4.0 компания Apple анонсировала поддержку блоков, по сути являющиx собою замыкания. Дальше о блоках в контексте разработки под IOS, Objective-C (тоесть работа без gc).
Для использования блоков IOS ver. < 4.0 можно применить ESBlockRuntime или PLBlocks.
Экземпляр блока, тип блока и сам блоковый литерал обозначаются с помощью оператора ^, пример:
или
Вызов блока аналогичен вызову обычной сишной функции. Например, так:
Главной особенностью блоков является их умение хранить контекст в котором они создавались. В примере выше «myBlock» всегда будет умножать число на 7. Как же это все работает?
1. Примитивные типы С и структуры, блоки хранят как константы. Пример:
Печатает — 21, а не 24.
2. Переменные заданные с ключевым словом __block являются изменяемыми. Работает это за счет копирования значения такой переменной в кучу и каждый блок хранит ссылку на эту переменную. Пример:
Печатает — 24, а не 21.
3. Переменные — указатели на обьекты с подсчетом ссылок (id, NSObject). Для них вызывается retain при копировании блока в кучу. Пример:
Здесь хочется обратить ваше внимание на то, что retain объекта date происходит именно во время копирования блока в кучу, а не во время его создания. К примеру, этот код упадет с “EXC_BAD_ACCESS”
4. Переменные — указатели на обьекты с подсчетом ссылок (id, NSObject) объявленые с ключевым словом __block. Для них НЕ вызывается retain при копировании блока в кучу. Пример:
Обычно это используется для избегания циклических ссылок. Пример:
Блоки являются экземплярами класса NSObject (конкретные классы этих обьектов не определенны), поэтому мы можем и вынуждены пользоватся методами класса NSObject — copy, retain, release и autorelease для блоков. Но зачем нам это нужно?
По умолчанию экземпляры блоков создаются не в куче, как можно было бы предположить, а в стеке. Поэтому при необходимости сделать отложеный вызов блока сначала его нужно скопировать в кучу.
Допустим, существует расширение класса NSObject c методом «performAfterDelay:», который выполняет заданный блок с задержкой.
И, собственно, вызов:
Такой код «свалит» наше приложение, потому как стековый блок будет к моменту вызова разрушен, и мы обратимся в месте вызова блока к случайной памяти. Хотя при этом такой код:
будет прекрасно работать. В чем же причина? Обратите внимание — последний блок не ссылается на внешние переменные следовательно нет и необходимости создавать его копию. В этом случае компилятор создает так называемый Global блок. В программе существует всего один экземпляр такого блока, время жизни которого ограничено временем жизни приложения. Таким образом, GlobalBlock можно рассматривать как singletone-объект.
И так, подведем итоги. Существует три вида блоков: глобальные( без состояния ), локальные или они же стековые, и блоки в куче (MallocBlock). Следовательно методы copy, retain, release и autorelease глобального блока ничего не делают. Метод retain так же ничего не делает для стекового блока. Для Malloc блока метод copy в свою очередь работает как retain для NSObject.
И конечно же исправленная версия предыдущего примера с добавлением метода copy:
Продолжение: «О блоках и их использовании в Objective-C часть 2-ая»
Для использования блоков IOS ver. < 4.0 можно применить ESBlockRuntime или PLBlocks.
Кратко о теории
Экземпляр блока, тип блока и сам блоковый литерал обозначаются с помощью оператора ^, пример:
typedef int (^MyBlock)(int);
int multiplier = 7;
MyBlock myBlock = ^(int num) {
return num * multiplier;
};
* This source code was highlighted with Source Code Highlighter.
или
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
* This source code was highlighted with Source Code Highlighter.
Вызов блока аналогичен вызову обычной сишной функции. Например, так:
myBlock( 3 )
* This source code was highlighted with Source Code Highlighter.
Главной особенностью блоков является их умение хранить контекст в котором они создавались. В примере выше «myBlock» всегда будет умножать число на 7. Как же это все работает?
Виды переменных контекста блока
1. Примитивные типы С и структуры, блоки хранят как константы. Пример:
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
multiplier = 8;
NSLog( @"%d", myBlock( 3 ) );
* This source code was highlighted with Source Code Highlighter.
Печатает — 21, а не 24.
2. Переменные заданные с ключевым словом __block являются изменяемыми. Работает это за счет копирования значения такой переменной в кучу и каждый блок хранит ссылку на эту переменную. Пример:
__block int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
multiplier = 8;
NSLog( @"%d", myBlock( 3 ) );
* This source code was highlighted with Source Code Highlighter.
Печатает — 24, а не 21.
3. Переменные — указатели на обьекты с подсчетом ссылок (id, NSObject). Для них вызывается retain при копировании блока в кучу. Пример:
NSDate* date = [ [ NSDate alloc ] init ];
void (^printDate)() = ^() {
NSLog( @"date: %@", date );
};
//копируем блок в кучу
printDate = [ [ printDate copy ] autorelease ];
[ date release ];
printDate();
* This source code was highlighted with Source Code Highlighter.
Здесь хочется обратить ваше внимание на то, что retain объекта date происходит именно во время копирования блока в кучу, а не во время его создания. К примеру, этот код упадет с “EXC_BAD_ACCESS”
NSDate* date = [ [ NSDate alloc ] init ];
void (^printDate)() = ^() {
NSLog( @"date: %@", date );
};
[ date release ];
//копируем блок в кучу и падаем
printDate = [ [ printDate copy ] autorelease ];
printDate();
* This source code was highlighted with Source Code Highlighter.
4. Переменные — указатели на обьекты с подсчетом ссылок (id, NSObject) объявленые с ключевым словом __block. Для них НЕ вызывается retain при копировании блока в кучу. Пример:
__block NSDate* date = [ [ NSDate alloc ] init ];
void (^printDate)() = ^() {
//здесь падаем при обращении к date
NSLog( @"date: %@", date );
};
//копируем блок в кучу, для объекта date retain не вызывается
printDate = [ [ printDate copy ] autorelease ];
[ date release ];
printDate();
* This source code was highlighted with Source Code Highlighter.
Обычно это используется для избегания циклических ссылок. Пример:
@interface SomeClass : NSObject
//копируем блок проперти
@property ( nonatomic, copy ) SimpleBlock block;
@end
@implementation SomeClass
@synthesize block = _block;
-(void)dealloc
{
[ _block release ];
[ super dealloc ];
}
-(void)methodB
{
}
-(void)methodA
{
__block SomeClass* self_ = self;
//потенциально циклическая ссылка( утечка ) - класс хранит блок, а блок ссылается на класс
self.block = ^()
{
//здесь retain для self_ не вызывается
[ self_ methodB ];
};
}
@end
* This source code was highlighted with Source Code Highlighter.
Блоки являются экземплярами класса NSObject (конкретные классы этих обьектов не определенны), поэтому мы можем и вынуждены пользоватся методами класса NSObject — copy, retain, release и autorelease для блоков. Но зачем нам это нужно?
Блоки и управление памятью
По умолчанию экземпляры блоков создаются не в куче, как можно было бы предположить, а в стеке. Поэтому при необходимости сделать отложеный вызов блока сначала его нужно скопировать в кучу.
Допустим, существует расширение класса NSObject c методом «performAfterDelay:», который выполняет заданный блок с задержкой.
@implementation NSObject (BlocksExtensions)
-(void)callSelfBlock
{
void* self_ = self;
ESSimpleBlock block_ = (ESSimpleBlock)self_;
block_();
}
-(void)performAfterDelay:( NSTimeInterval )delay_
{
[ self performSelector: @selector( callSelfBlock ) withObject: nil afterDelay: delay_ ];
}
@end
* This source code was highlighted with Source Code Highlighter.
И, собственно, вызов:
NSDate* date = [ NSDate date ];
void (^printDate)() = ^() {
NSLog( @"date: %@", date );
};
[ printDate performAfterDelay: 0.3 ];
* This source code was highlighted with Source Code Highlighter.
Такой код «свалит» наше приложение, потому как стековый блок будет к моменту вызова разрушен, и мы обратимся в месте вызова блока к случайной памяти. Хотя при этом такой код:
void (^printDate)() = ^() {
NSLog( @"date: %@", [ NSDate date ] );
};
[ printDate performAfterDelay: 0.3 ];
* This source code was highlighted with Source Code Highlighter.
будет прекрасно работать. В чем же причина? Обратите внимание — последний блок не ссылается на внешние переменные следовательно нет и необходимости создавать его копию. В этом случае компилятор создает так называемый Global блок. В программе существует всего один экземпляр такого блока, время жизни которого ограничено временем жизни приложения. Таким образом, GlobalBlock можно рассматривать как singletone-объект.
Виды блоковых переменных
И так, подведем итоги. Существует три вида блоков: глобальные( без состояния ), локальные или они же стековые, и блоки в куче (MallocBlock). Следовательно методы copy, retain, release и autorelease глобального блока ничего не делают. Метод retain так же ничего не делает для стекового блока. Для Malloc блока метод copy в свою очередь работает как retain для NSObject.
И конечно же исправленная версия предыдущего примера с добавлением метода copy:
@implementation NSObject (BlocksExtensions)
-(void)callSelfBlock
{
void* self_ = self;
ESSimpleBlock block_ = (ESSimpleBlock)self_;
block_();
}
-(void)performAfterDelay:( NSTimeInterval )delay_
{
//копируем блок в кучу, так как отложеный вызов - afterDelay:
self = [ [ self copy ] autorelease ];
[ self performSelector: @selector( callSelfBlock ) withObject: nil afterDelay: delay_ ];
}
@end
* This source code was highlighted with Source Code Highlighter.
Продолжение: «О блоках и их использовании в Objective-C часть 2-ая»