Когда на Mac OS Х и iOS появились блоки кода, программисты сказали: «о, круто», но продолжили писать код привычным способом. Может быть, они просто не поняли, насколько это круто?

Что такое «блоки кода»? Блоки кода — это типичные объекты Objective-C. Исторически официально они появились в Mac OS X 10.6 и одновременно в iOS 4, но неофициально были доступны немногим ранее. Это не функции, не методы, это просто несколько операторов, которые объединены, как нам подсказывает Капитан Очевидность, в блок. Блоки кода очень похожи на анонимные функции или лямбда-выражения. Вместе с тем они имеют некоторое сходство с указателями на функции, при этом, когда используешь первые, код получается более элегантным. Они очень удобны при создании анимации или любой другой асинхронной работе. Между тем первоначальное их предназначение — сократить объем необходимого для написания кода, совместив логически связанные куски, например код регистрации оповещения и реализации обрабатывающего метода. Давай посмотрим на несколько программистских трюков, которые покажут все прелести блоков кода.

 

Как и когда?

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

В дальнейшем мы будем работать над консольным приложением, чтобы не отвлекаться от главной темы повествования.

 

Объявление блока кода

Запусти на своем маке Xcode, создай проект командной строки — Command Line для OS X.

Создаем консольный проект
Создаем консольный проект

Так как мы будем кодить на Objective-C, на следующей странице мастера выбери тип проекта Foundation. Это название повелось от используемой базовой библиотеки.

При объявлении блока кода вместо имени ставится «знак вставки» — ^. Подобно обычным функциям, блоки кода могут получать параметры и возвращать аргумент. К примеру, вот как может выглядеть блок кода, получающий три параметра:

^(double slag1, double slag2, double power) {
  double res = (slag1 + slag2) * power;
  return res;
}

Собственно, комментарии по выполнению данного блока кода будут излишни. Заметь, пока данный код не обязан компилиться.

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

Замечательная особенность блоков кода — это возможность их сохранения в переменные особого типа. Чтобы объявить блочную переменную, надо написать:

double (^expression) (double, double, double);

Здесь на первом месте указан возвращаемый тип, затем символ «домик» предваряет имя объявляемой переменной, дальше в скобках указаны параметры, принимаемые блоком кода. Обрати внимание, объявление блочной переменной соответствует привязываемому к ней блоку кода по параметрам и аргументам.

Таким образом, имя блочной переменной является подобием имени функции, только с переменной ты можешь выполнить более широкий круг задач.

Теперь, когда блок кода и блочная переменная сходятся по аргументам и параметрам, мы можем присвоить блок переменной. Для этого надо ввести следующий код:

expression = ^double(double slag1, double slag2, double power) {
  double res = (slag1 + slag2) * power;
  return res;
};

Чтобы воспользоваться вычислениями, хранящимися в блочной переменной, достаточно написать:

NSLog(@ "%f",expression(2.0,1.0,2.0));

Программа выведет на консоль результат выражения, вычисляемого в блоке кода.

 

Блоки кода и куча

По своей природе блоки кода хранятся в стеке, поэтому при выходе из области видимости они удаляются, так же как и локальные переменные. Чтобы убрать это ограничение, другими словами переместить блок кода в кучу, его надо туда скопировать, отправив блочной переменной соответствующее сообщение. Например, для приведенного случая данный оборот будет выглядеть следующим образом:

expression = [expression copy];

На месте принимающей значение переменной должен быть либо член класса, либо глобальная переменная, рамки жизни которой шире, чем копируемой, иначе не было бы смысла в копировании. При этом блок кода, по сути, выполняет действия с локальными переменными, поэтому во время копирования блока в кучу вместе с ним копируются также переменные. Однако попавшие при копировании вместе с блоком кода в кучу переменные становятся немодифицируемыми. Для того чтобы они не превратились в константы, при объявлении внешних переменных тип данных надо предварять ключевым словом __block (заметь, с двумя символами подчеркивания в начале слова).

Такая же участь ждет указатели: объекты, на которые они указывают, будут существовать, пока существует данный блок кода.

 

Внешние переменные

В блоках кода могут использоваться внешние по отношению к ним переменные, то есть объявленные не только внутри блока, но и на том уровне, на котором объявлен сам блок. Пример:

int a = 10;
int b = 5;
int (^abc) ();
abc = ^(int n) {
int c = a + b;
      NSLog(@"%d", c);
      return c;
};
abc();

В данном случае переменные a и b, объявленные за пределами блока, без проблем используются внутри блока для вычисления значения выражения. В разделе о функциях обратного вызова эта возможность будет обсуждаться снова.

Блоки работают даже в классическом C
Блоки работают даже в классическом C
 

Функции обратного вызова

Далее для экономии места я не буду так подробно приводить описания блоков кода.

Классический пример — функция обратного вызова. Подобные вещи встречаются очень часто, особенно в многопоточных программах. Простейший вариант создания функции обратного вызова может выглядеть так:

void callbackFunc( void (*func)() ) {
    func();
}

void WeWillCallYou() {
    printf("Classic callback\n");
}

Сначала мы объявляем функцию callbackFunc, в качестве параметра она принимает другую функцию, которую мы и будем вызывать из своего потока или просто из своего кода.

Потом мы создаем функцию, которую мы и будем вызывать, — WeWillCallYou. А вот сам вызов будет выглядеть так:

callbackFunc(WeWillCallYou);

Немного громоздко, не правда ли? Станет еще хуже, если нужно будет передавать в функцию параметр, что бывает очень часто. Изменим нашу функцию и добавим в нее параметр:

void WeWillCallYou(int* param) {
    printf("Classic callback %d\n", *param);
}

Теперь вызов этой функции может выглядеть так:

void callbackFunc( void (*func)(int *), int* param) {
    func(param);
}

Изменилось объявление, потому что нам теперь нужно передавать указатель на число.

Теперь мы можем вызвать функцию и передать ей значение:

int param = 10;
callbackFunc(WeWillCallYou, &param);

Теперь все выглядит еще страшнее, и ситуация будет ухудшаться, если придется передавать больше параметров. А теперь посмотрим, как та же задача решается с помощью блоков. Первое, что нужно сделать, — описать функцию:

void callbackBlockFunc( void (^func)(void) ) {
    func();
}

В данном случае мы создаем callbackBlockFunc, которая принимает в качестве параметра функцию и вызывает ее.

Все. Больше ничего делать не нужно. Теперь можно использовать наше описание следующим образом:

int variable = 1;

callbackBlockFunc( ^{
  printf("Block %d\n", variable);
});

Я сразу же усложнил пример и добавил переменную, которая должна быть доступна внутри функции. И она доступна, хотя мы ее не передавали, потому что блок кода будет выполняться в том же контексте. Чудо? Именно так!

 

Многозадачность и блоки кода

Движемся к чему-то более современному и интересному — многозадачности. С помощью блоков очень просто создавать отдельные задачи, которые будут выполняться параллельно с основной.

Теперь создание отдельной задачи, которая может выполняться на отдельном ядре, может выглядеть так:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10; i++ ) {
  [queue addOperationWithBlock:^{
    NSLog(@"test %d\n", i);
   }];
}

NSLog(@"test");
[queue waitUntilAllOperationsAreFinished];

Здесь мы сначала инициализируем объект NSOperationQueue, который как раз умеет создавать отдельные операции. У этого объекта есть метод addOperationWithBlock, принимающий блок кода, который должен выполняться отдельно от основного потока. В этом примере я ограничился созданием только одного блока, но их может быть несколько.

Чтобы дождаться окончания выполнения операций, которые выполняются в очереди, я вызываю метод waitUntilAllOperationsAreFinished.

В результате выполнения данного кода в консоли может оказаться что-то типа

2013-12-09 18:14:37.191 Test[1068:1a07] test 0
2013-12-09 18:14:37.191 Test[1068:303] test
2013-12-09 18:14:37.191 Test[1068:2d0f] test 2
2013-12-09 18:14:37.191 Test[1068:350b] test 1
2013-12-09 18:14:37.191 Test[1068:1003] test 3
2013-12-09 18:14:37.195 Test[1068:2d0f] test 5
2013-12-09 18:14:37.195 Test[1068:1a07] test 4
2013-12-09 18:14:37.195 Test[1068:350b] test 6
2013-12-09 18:14:37.196 Test[1068:1003] test 7
2013-12-09 18:14:37.196 Test[1068:2d0f] test 8
2013-12-09 18:14:37.196 Test[1068:1a07] test 9

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

И обрати внимание, что из блока кода мы получаем доступ к переменной i, которая на самом деле объявлена за пределами этого блока. То есть выполнение опять же идет в контексте родительского кода, что является объективным плюсом.

 

Итого

В своих софтверных продуктах Apple все чаще и чаще использует блоки кода. И хотя сразу прийти к их использованию довольно трудно, они действительно помогают в кодировании разных событийных механизмов. Когда блоки кода появились, я не очень хорошо их воспринял. Но потом попробовал один раз, второй раз и понял, что они реально упрощают мою жизнь. Короче говоря, советую!

 

Check Also

DDoS на Bluetooth. Разбираем трюк, который поможет отключить чужую колонку

На свете существует не так много вещей, которые бесят практически всех без исключения. Это…

2 комментария

  1. Аватар

    08.10.2014 at 12:23

    почему нигде не написано что это перевод?

  2. Аватар

    22.10.2014 at 10:12

    Статья полезная, но немного устаревшая. Блоки появились в iOS4, чуть ли не 4 года назад и сейчас ими уже все пользуются. Могу допустить, что их игнорируют самые традиционные разработчики, но Apple все больше и больше их использует в своей системе. Я уже молчуо Swift, там блоки везде.

Оставить мнение