О блоках и их использовании в Objective-C часть 1-ая

    В OS X 10.6 и iOS 4.0 компания Apple анонсировала поддержку блоков, по сути являющиx собою замыкания. Дальше о блоках в контексте разработки под IOS, Objective-C (тоесть работа без gc).
    Для использования блоков 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-ая»
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 12

      0
      … и вот мы видим, во что выливаются замыкания в языках с ручным управлением памятью. Еще 10 метров веревки чтобы уж наверняка ноги-то себе поотстреливать.
        0
        благо они есть :)
          +1
          Не в языках с ручным управлением памятью, а в Obj-C. C++0x лямбды имеют достаточно прозрачную семантику и способ захвата указывается в самом замыкании, а не в объявлении переменной. Сами лямбды тоже можно передавать как по значению так и по ссылке/указателю.

          Причем как для самих лямбд так и для захваченных свободных переменных применяются те же правила, основанные на здравом смысле, что и для всех остальных объектов: делать объект, живущий меньше, чем ссылки/указатели на него — ОЧЕНЬ плохая идея.
            0
            Уже не актуально, Apple представила (точнее представит, пока это под NDA для разработчиков) на днях ARC (Automatic Reference Counting) — теперь ручным управлением памяти занимается компилятор, не нужно больше самому следить за тем, где и когда вставлять захват и освобождение объектов. До кучи еще был и есть сборщик мусора же.

            Ну и по правде говоря сложного в этом ничего нет же, разве что у новичков которые с наскока пытаются перейти с какой-нибудь системы где подобного нет, а так не бывает.
              0
              Не могу найти нормальной информации о ARC, что именно это будет из себя представлять? GC может?
                0
                А вы зарегистрированы в Mac Dev Program? Описание представлено в разде pre-release и не доступно вне программы.

                Вообше суть такая — в процессе компиляции компилятор сам проставит где нужно retain/release/autorelese. Для свойств добавлены новые спецификаторы, собственно как и при объявлении каких-то обычных локальных переменных. NSAutoreleasePool теперь тоже вне игры (хотя на самом деле просто сделали очередной syntactic sugar в виде @autoreleasepool). В общем, никакого GC (я кстати с момента его появления так и не успел его попробовать).
                  0
                  Ну Слава Богу что без GC :), у меня много где логика программы завязана на удаления обьекта и метод dealloc.
                    0
                    Переход будет совершенно безболезненный и незаметный, в меню рефакторинга будет пункт который перелопатит весь проект и все обновит.
                      0
                      Поигрался, впечатлило, что-то подобное давно ждал от Apple, отсутствие dealloc в большинстве случаев — так точно.
                0
                Жаль нет поддержки weak для IOS 4.0, очень надеюсь на то что это дело поправят, если это возможно.
              +1
              Думаю стоит более добавить еще ряд моментов об которым можно и не задуматься сразу:

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

              Отсюда вытекает что
              2. Глобальные и статические переменные доступны полностью.
              int globalInt = 0;
              
              int main () {
              	static int staticInt = 0;
              
              	^ {
              		globalInt++;
              		staticInt++;
              	}();
              	
              	printf("globalInt: %d\n", globalInt); // print "globalInt: 1"
              	printf("staticInt: %d\n", staticInt); // print "staticInt: 1"
              }
              

                0
                Да, спасибо за дельное замечание, я поправлю.

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

              Самое читаемое