Блок представляет собой расширение языка С, введенное в OS X 10.6 и доступное начиная с iOS 4.0.

Это способ связывания некоторого кода в единую сущность и передачи его как аргумента функции С или методу Objective-C. Это похоже на то, что мы делали в примере 3.1, передавая в качестве аргумента указатель на функцию; однако теперь мы будем передавать сам код. Этот способ имеет некоторые важные преимущества перед передачей указателя, о чем мы поговорим чуть позже.

Для иллюстрации я перепишу пример 3.1 с использованием блока вместо указателя на функцию. Вместо вызова метода sortedArrayUsingFunction: context: я вызываю метод sortedArrayUsingComparator:, который получает блок в качестве параметра. Этот блок типизирован как

NSComparisonResult (Л)(id objl, id obj2)

Это выражение аналогично синтаксису определения типа указателя на функцию, но с символом Л вместо *. Здесь описывается блок, который получает два параметра типа id и возвращает NSComparisonResult (который представляет собой просто NSInteger, с тем же смыслом, что и в примере 3.1). Мы можем определить блок прямо в качестве аргумента в вызове sortedArrayUsingComparator:, как в примере 3.2.

 

Пример 3.2. Использование встроенного блока вместо функции

NSArray* arr2 = [arr sortedArrayUsingComparator: Л(id objl, id obj2) (
NSString* si = objl;
NSString* s2 = obj2;
NSString* stringlend = [si substringFromlndex:[si length] - 1];
NSString* string2end = [s2 substringFromlndex:[s2 length] - 1]; return [stringlend compare:string2end];
)];

Синтаксис определения встроенного блока имеет вид

A0(id objl, id obj2)® {в
//...
)

Символ А

Скобки с параметрами, аналогично параметрам в определении функции С.

Наконец, содержимое блока в фигурных скобках. Фигурные скобки образуют область видимости.

 

Возвращаемый тип в определении встраиваемого блока обычно опускается. Если он включен, то находится перед символом Л, а не в круглых скобках. Если же он опущен, то вы можете использовать выражение приведения типа в строке return, чтобы возвращаемый тип соответствовал ожидаемому.

 

Встроенный блок, как показанный в примере 3.2, не может использоваться повторно; если бы у нас было два вызова sortedArrayUsingComparator:, то мы должны были писать этот блок в полном объеме дважды. Чтобы избежать такого повторения, или просто для ясности, блок может быть присвоен переменной, которая затем передается методу как аргумент (пример 3.3).

 

Пример 3.3. Присваивание блока переменной

NSComparisonResult (AsortByLastCharacter)(id, id) = ~(id objl, id obj2) (
NSString* si = objl;
NSString* s2 = obj2;
NSString* stringlend = [si substringFromlndex:[si length] - 1];
NSString* string2end = [s2 substringFromlndex:[s2 length] - 1]; return [stringlend compare:string2end];
) ;
NSArray* arr2 = [arr sortedArrayUsingComparator: sortByLastCharacter];
NSArray* arr4 = [arr3 sortedArrayUsingComparator: sortByLastCharacter];

Возможно, наиболее примечательной возможностью блоков является следующая: переменные в области видимости в точке, где определен блок, сохраняют свои значения в блоке на этот момент, даже несмотря на то, что блок может быть выполнен в некоторый более поздний момент. (Технически мы говорим, что блок является замыканием и что значения переменных вне блока могут захватываться блоком.) Этот аспект блоков делает их полезными для определения функциональности, которая будет выполнена в более позднее время или даже в некотором другом потоке.

Вот пример из реальной практики:

CGPoint р = [v center];
CGPoint pOrig = p;
p.x += 100;
void (Aanim) (void) = л{
[v setCenter: p];
I;
void rafter) (BOOL) = я (BOOL f) {
[v setCenter: pOrig];
};
NSUInteger opts = UIViewAnimationOptionAutoreverse;
[UlView animateWithDuration:1 delay:0 options:opts animations:anim completion:after];

Этот код делает нечто достаточно удивительное. Il6ia animateWithDuration:delay: options : animations : completion: настраивает анимацию с помощью блоков. Но сам вывод осуществляется позже; анимация, а следовательно, и блоки, будет выполняться в неопределенный момент в будущем, после завершения вызова метода, когда ваш код будет заниматься совсем другими вещами. Сейчас в области видимости имеется объект v типа UlView, наряду с CGPoint р и еще одним CGPoint pOrig. Переменные р и pOrig — локальные автоматические; они выходят из области видимости и прекращают существование до начала анимации и выполнения блоков. Тем не менее значения этих переменных используются внутри блоков в качестве параметров сообщений, отправляемых v.

Поскольку блок может выполняться позже, не совсем корректно выполнять присваивание значений локальным автоматическим переменным, определенным вне блока; поэтому компилятор постарается остановить вас сообщением “variable is not assignable” (переменная не допускает присваивание):

CGPoint р;
void (ЛаВ1оск) (void) = л{
р = CGPointMake(1,2); // Ошибка
};

Локальная автоматическая переменная может быть сделана подчиняющейся специальным

правилам сохранения путем объявления переменной с помощью квалификатора block.

Этот квалификатор обеспечивает существование переменной вместе с блоком, который ее использует, и имеет два основных применения. Вот первое из них: если блок будет выполняться

немедленно, квалификатор block позволит ему установить переменную вне блока равной

значению, которое будет необходимо после завершения блока.

Например, метод enumerateObjectsUsingBlock: класса NSArray принимает блок и немедленно вызывает его для каждого элемента массива. Это основанный на блоке эквивалент цикла for. . . in, который циклически проходит по элементам перечислимой коллекции (глава 1). Здесь мы предлагаем циклический проход по циклу до тех пор, пока не будет найдено искомое значение; когда мы найдем его, мы устанавливаем переменную (dir) равной этому значению. Однако эта переменная должна быть объявлена вне блока, поскольку она должна использоваться после выполнения блока — нам надо, чтобы ее область видимости выходила за пределы фигурных скобок блока. Поэтому мы объявляем ее с квалификатором  block, так что можем присваивать ей значение и внутри блока:

CGFloat h = newHeading.magneticHeading;
block NSString* dir = @"N";
NSArray* cards = @[@"N", @"NE”, 0"E", @"SE",
@"S", @MSW", @"W", @"NW"];
[cards enumerateObjectsUsingBlock:A(id obj,
NSUInteger idx, BOOL *stop) { if (h < 45.0/2.0 + 45*idx) { dir = obj;
*stop = YES;
}
}]; // Теперь мы можем использовать dir

 

Обратите внимание, что присваивание выполняется разыменованному указателю на BOOL. Это способ преждевременного завершения цикла; если мы уже нашли интересующее нас значение, нет смысла продолжать работу цикла. Мы не можем использовать инструкцию break, поскольку на самом деле это не цикл for. Поэтому метод enumerateObjectsUsingBlock: передает блоку параметр, который представляет собой указатель на BOOL и которому блок может косвенно присвоить значение YES как сигнал методу о том, что можно остановить выполнение. Это одна из немногих ситуаций в программировании для iOS, где требуется разыменование указателя.)

Второе из двух применений квалификатора block обратно первому. Оно реализуется тогда, когда блок будет выполняться через некоторое время после его определения, и мы хотим, чтобы блок использовал значение, которое переменная имеет в момент выполнения, а не захватывал ее значение в момент определения блока. Обычно это связано с тем, что тот же вызов метода, который получает блок (для последующего выполнения) одновременно и устанавливает значение этой переменной (сейчас).

Например, метод beginBackgroundTaskWithExpirationHandler: получает блок, который будет выполнен в некоторый будущий момент времени. Он также генерирует и возвращает UIBackgroundTaskldentif ier, который в действительности представляет собой просто целое число, — и мы хотим использовать это целое число в блоке, если и когда этот блок будет выполнен. Так что мы пытаемся действовать следующим образом: блок передается методу как аргумент, метод вызывается, метод возвращает значение, блок использует это значение. Квалификатор block делает такую последовательность возможной:

block UIBackgroundTaskldentifier bti =
[[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler: A{
[[UIApplication sharedApplication] endBackgroundTask:bti];
}];

Тогда же, когда в Objective-C были введены блоки, Apple предоставила системную библиотеку функций С под названием Grand Central Dispatch (GCD), которая интенсивно их использует. Главной целью GCD является управление потоками, но она также оказывается удобной для аккуратного и компактного выражения определенных понятий о том, когда должен быть выполнен код. К примеру, GCD может помочь задержать выполнение нашего кода (отложенное выполнение). В приведенном далее коде блок используется для того, чтобы сказать “измени границы UlView vl, но не прямо сейчас, а через две секунды”:

dispatch_time_t popTime =
dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), A(void){
CGRect r = [vl bounds]; r.size.width += 40; r. size.height -= 50;
[vl setBounds: r];
});

Последний пример блока в действии представляет собой переписанный код из конца главы 1, где метод класса предоставляет объект-синглтон. Функция GCD dispatch_once — очень быстрая и (в отличие от примера из главы 1) безопасная в смысле потоков, — обеспечивает выполнение ее блока (который в данном случае создает синглтон) только один раз в течение всей работы программы. Таким образом, она гарантирует, что синглтон действительно является синглтоном:

+ (CardPainter*) sharedPainter { static CardPainter* sp = nil; static dispatch_once_t onceToken; dispatch_once(SonceToken, л { sp = [CardPainter new] ;
));
return sp;
}

Блок в состоянии выполнить присваивание локальной переменной sp без квалификатора  block, потому что sp имеет квалификатор static, который дает тот же результат, но

даже еще более сильный: так же, как block продляет время жизни переменной до времени

жизни блока, static продляет время жизни переменной до времени жизни самой программы. Таким образом, его побочный эффект — обеспечение возможности присваивания значений переменной sp внутри блока, так же как если бы она была объявлена с квалификатором  block.


 

 

 

Добавить комментарий