Многопоточное программирование в среде Cocoa, Mac OS, iOS

Циклы выполнения

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

Управление циклом выполнения автоматизировано не полностью. Вы все еще должны разработать код для потока, чтобы начать цикл выполнения в определенное время и реагировать на поступающие события. И Cocoa и Core Foundation обеспечивают объекты циклов выполнения, которые помогут вам настроить и управлять циклом выполнения вашего потока. Вашему приложению нет необходимости, создавать эти объекты явно, для каждого потока, в том числе основного потока приложения, который имеет связанный с ним объект цикла выполнения. Только на вторичных потоках необходимо запускать их цикл выполнения явно. В обоих и Carbon и Cocoa-приложениях, основной поток автоматически устанавливает и запускает свой цикл выполнения, как часть общего процесса запуска приложения.

Анатомия цикла запуска

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

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

thread os x run loop

В дополнение к обработке источников ввода, циклы выполнения также создают уведомления относительно поведения цикла выполнения. Зарегистрированные наблюдателицикла выполнения могут получать такие уведомления и использовать их для дополнительной обработки на потоке. Можно использовать Core Foundation для установки на цикл выполнения наблюдателей на своих потоках.

Режимы цикла выполнения

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

В коде, режимы определяют по имени. И Cocoa и Core Foundation определяют режим по умолчанию и несколько часто используемых режимов, а также строки для указания этих режимов в вашем коде. Вы можете задать пользовательские режимы, просто указав пользовательскую строку в имени режима. Хотя имена для назначения пользовательских режимов являются произвольными, содержание этих режимов нет. Вы должны быть уверены, что добавили один или несколько входных источников, таймеров или наблюдателей цикла выполнения на различные режимы, делая их полезными.

Вы можете использовать режимы, чтобы отфильтровать нежелательные события из источников в течение определенного прохода цикла среды выполнения. Большую часть времени, вы запускаете цикл выполнения в режиме определенном системой "по умолчанию". Модальные панели, однако, могут работать в «модальный» режиме. В этом режиме, только источники, имеющие отношение к модальной панели будут доставлять события в поток. Для вторичных потоков, вы можете использовать специальные режимы для предотвращения доставки сообщений от источников с низким приоритетом во время критически важных операций.

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

Предопределенные режимы цикла выполнения.

РежимИмяОписание
По умолчаниюNSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)По умолчанию используется режим, используемый для большинства операций. Большую часть времени, вы должны использовать этот режим, чтобы начать свой цикл выполнения и настроить источники ввода.
ПодключенияNSConnectionReplyMode (Cocoa)Cocoa использует этот режим в сочетании с NSConnection объектом для контроля ответов. Вы очень редко будете нуждаться в использовании этого режима самостоятельно.
МодальныйNSModalPanelRunLoopMode (Cocoa)Cocoa использует этот режим для выявления событий предназначеных для модальных панелей.
Отслеживание событийNSEventTrackingRunLoopMode (Cocoa)Cocoa использует этот режим, чтобы ограничить входящие события во время цыкла перетаскивания мышью и других видов пользовательских циклов отслеживания интерфейса.
Общие режимыNSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)Это настраиваемая группа наиболее часто используемых режимов. Привязка источника входного сигнала в этом режиме также связывает его с каждым из режимов в группе. Для Cocoa приложений, этот набор включает в себя по умолчанию режимы: Модальный и Отслеживание событий. Core Foundation включает в себя только режим по умолчанию, на начальном этапе. Вы можете добавлять пользовательские режимы, задавая их с помощью функции CFRunLoopAddCommonMode.

Входные источники

Входные источники доставляют события, асинхронно вашему потоку. Источник события зависит от типа источника входного сигнала, который, как правило, принадлежит к одной из двух категорий. Порт-основаные входные источники контролируют Mach порты приложения. Пользовательские входные источники контролируют пользовательские источники событий. Что касается цикла запуска, то для него не должено иметь значения, является ли входной источник на основанным на портах или обычным. Система обычно реализует входные источники обоих типов, которые можно использовать как есть. Единственное различие между этими двумя источниками, как они сигнализируют. Порт- основаннык источники сигнализируют автоматически к ядру, а пользовательские источники должны сигнализировать вручную из другого потока.

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

Далее описаны некоторые входные источники.

Источники основанные на портах

Cocoa и Core Foundation обеспечивает встроенную поддержку для создания на основе порта источника входного сигнала через порт-связанные объекты и функции. Например, в cocoa, вам никогда не придется создавать источник непосредственно. Вы просто создаете объект порта и используете методы NSPort, чтобы добавить, этот порт к циклу выполнения. Порт обрабатывает создание и настройку объекта источника, необходимого для вас.

В Core Foundation, необходимо вручную создать порт и его источник цикла выполнения. В обоих случаях, можно использовать функции, связанные с непрозрачным типом порта (CFMachPortRef, CFMessagePortRef или CFSocketRef), чтобы создать соответствующие объекты.

Пользовательские входные источники

Для создания пользовательского входного источника, вы должны использовать функции, связанные с непрозрачным CFRunLoopSourceRef типом Core Foundation. Можно настроить пользовательский источник входного сигнала с помощью нескольких функций обратного вызова. Core Foundation вызывает эти функции в различных точках для настройки источников, обрабатывает входящие события и разрушает источник, когда он удаляется из цикла выполнения.

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

Cocoa выполнение селекторов источников

В дополнение к источникам на основе портов, Cocoa определяет пользовательский входной источник, что позволяет выполнять селектор на любом потоке. Как и источник на основе порта, выполняемые запросы селектора сериализуются на целевой поток, облегчая многие проблемы с синхронизацией, которые могут возникнуть с несколькими методами, которые работают на одном потоке. В отличие от источников на основе портов, источники выполняемых селекторов удаляют себя из цикла выполнения после того, как выполняется их селектор.

Примечание: До OS X 10.5, выполнение селекторов источников использовалось в основном для отправки сообщений основного потока, но и в OS X 10.5 и выше и в iOS, их можно использовать для отправки сообщений на любые потоки.

При выполнении селектора в другом потоке, целевой поток должен иметь активный цикл выполнения. Для создаваемого потока, это означает, что произойдет ожидание, пока ваш код явно начинает цикл выполнения. Поскольку основной поток начинает свой ​​цикл выполнения, вы можете начать выдачу вызовов на этот поток, как только приложение вызывает метод делегата applicationDidFinishLaunching:. Цикл выполнения обрабатывает все выполняемые очереди селекторов, вызывая каждый раз через цикл, а не во время обработки одного на каждой итерации.

В таблице ниже перечислены методы, определенные в NSObject, который может быть использован для выполнения селекторов на других потоках. Поскольку эти методы объявлены в NSObject, их можно использовать с любым потоком, где у вас есть доступ к Objective-C объектам, в том числе POSIX потоками. Эти методы сами не создают новый поток для выполнения селектора.

МетодыОписание
performSelectorOnMainThread:withObject:waitUntilDone: performSelectorOnMainThread:withObject:waitUntilDone:modes:Выполняет указанный селектор на главном потоке приложения во время следующей итерации цикла выполнения, данного потока. Эти методы дают возможность блокировать текущий поток до того, пока селектор не выполнится.
performSelector:onThread:withObject:waitUntilDone: performSelector:onThread:withObject:waitUntilDone:modes:Выполняет указанный селектор на любом потоке, для которого у вас есть NSThread объект. Эти методы дают возможность блокировать текущий поток до окончания выполнения селектора.
performSelector:withObject:afterDelay: performSelector:withObject:afterDelay:inModes:Выполняет указанный селектор в текущем потоке во время следующей итерации цикла запуска и после дополнительного периода задержки. Поскольку происходит ожидание следующей итерации цикла выполнения для выполнения селектора, эти методы обеспечивают автоматическую мини задержку относительно выполняемого в данный момент кода. Несколько селекторов в очереди выполняются один за другим в том порядке, в котором они были поставлены в очередь.
cancelPreviousPerformRequestsWithTarget: cancelPreviousPerformRequestsWithTarget:selector:object:Позволяет отменить сообщение, отправленное на текущий поток методом performSelector:withObject:afterDelay: или performSelector:withObject:afterDelay:inModes:

Таймер источники

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

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

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

Наблюдатели цикла запуска

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

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

Вы можете добавить наблюдателей цикла выполнения как в Cocoa так и в Carbon приложения, но для определения и добавления его в цикл выполнения, вы должны использовать Core Foundation. Чтобы создать наблюдателя цикла выполнения, необходимо создать новый экземпляр CFRunLoopObserverRef непрозрачного типа. Этот тип отслеживает пользовательскую функцию обратного вызова.

Как и таймеры, наблюдатели цикла выполнения могут быть использованы один или несколько раз. Одиночный наблюдатель удаляет себя из цикла выполнения после того, как сработает, в то время, как повторяющиеся наблюдатели остаются на месте. Вы определяете, является ли наблюдатель одиночным или много раз срабатывающим при его создании.

Последовательность событий цикла выполнения

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

  1. Уведомляет наблюдателей, что выполнен вход в цикл выполнения.
  2. Уведомляет наблюдателей, что таймеры готовы к срабатыванию.
  3. Уведомляет наблюдателей, что входные источники, не основанные на портах готовы к срабатыванию.
  4. Срабатывание не основанныех на портах входных источников, готовых к срабатыванию.
  5. Если основанные на портах источники готовы и ожидают срабатывания, событие обрабатывается немедленно. Далее переход к шагу 9.
  6. Уведомляет наблюдателей, что поток переходит в спящий режим.
  7. Положит поток спать до одного из следующих событий:
    • Событие поступает для основанного на порте входного источника.
    • Срабатывает таймер.
    • Тайм-аут значение, установленное для цикла выполнения истекает.
    • Цикл выполнения явно проснулся.
  8. Уведомляет наблюдателей, что поток только что проснулся.
  9. Процесс в ожидании события.
    • Если определенный пользователем таймер сработал, обработка события таймера и перезапуск цикла. Переход к шагу 2.
    • Если входной источник сработал, доставка события.
    • Если цикл выполнения явно проснулся, но еще не истекло значение тайм-аут, перезагрузка цикла. Переход к шагу 2.
  10. Уведомляет наблюдателей о том, что цикл выполнения завершился.

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

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

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

Когда бы следовало использовать цикл выполнения

Единственный раз, когда вам нужно запустить цикл выполнения явно, это когда вы создаете вторичный поток для вашего приложения. Цикл выполнения для основного потока приложения является ключевой частью инфраструктуры. В результате, как Cocoa, так и Carbon предоставляют код для запуска основного цикла приложения и начинают этот цикл автоматически. Метод run UIApplication в iOS (или NSApplication в OS X) запускает главный цикл приложения как часть нормальной последовательности запуска. Кроме того, RunApplicationEventLoop функция стартует основной цикл для приложений Carbon. Если вы используете шаблон проекта Xcode для создания вашего приложения, вы никогда не должны вызывать эти функции в явном виде.

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

  • Использование портов или пользовательских входных источников для общения с другими потоками.
  • Использование таймеров на потоке.
  • Использование какого либо performSelector… метода в Cocoa приложении.
  • Сохранения потока для переодического выполнения задач.

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

Использование объектов цикла запуска

Цикл выполнения объекта предоставляет основной интерфейс для добавления входных источников, таймеров и наблюдателей цикла выполнения, а затем запуска его. Каждый поток имеет один объект цикла выполнения связанный с ним. В Cocoa, этот объект является экземпляром NSRunLoop класса. В приложении Carbon или BSD, это указатель на непрозрачный тип CFRunLoopRef.

Получение объекта цикла выполнения

Для получения цикла выполнения для текущего потока, используется один из следующих вариантов:

  • В Cocoa приложениях, используйте метод currentRunLoop класса NSRunLoop, чтобы получить NSRunLoop объект.
  • Используя CFRunLoopGetCurrent функцию.

Хотя это будет не бесплатным преобразованием, вы можете получить непрозрачный тип CFRunLoopRef из NSRunLoop объекта в случае необходимости. NSRunLoop класс определяет getCFRunLoop метод, который возвращает CFRunLoopRef тип, таким образом вы можете выполнять Core Foundation процедуры. Потому, как, оба объекта относятся к одному циклу выполнения, вы можете смешивать вызовы объекта NSRunLoop и CFRunLoopRef по мере необходимости.

Конфигурирование цикла выполнения

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

В дополнение к установке источников, вы также можете установить наблюдателей цикла выполнения и использовать их для обнаружения различных стадий выполнения цикла выполнения. Для установки наблюдателя цикла выполнения создают непрозрачный тип CFRunLoopObserverRef и используют функцию CFRunLoopAddObserver, чтобы добавить его к циклу выполнения. Наблюдатель цикла выполнения должен быть создан с помощью Core Foundation, даже для Cocoa приложений.

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

- (void)threadMain
{
    // Приложениям, использующим сборку мусора, нет необходимости в autorelease пуле.

    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];

    //Создение наблюдателя цикла выполнения и присоединениие к нему.

    CFRunLoopObserverContext  context = {0, self, NULL, NULL, NULL};
    CFRunLoopObserverRef    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
            kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);

    if (observer)
    {
        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }

    // Создание и планирование таймера.

    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
                selector:@selector(doFireTimer:) userInfo:nil repeats:YES];

    NSInteger    loopCount = 10;
    do
    {
        // Запуск цикла выполнения десять раз для срабатывания таймера.

        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    }
    while (loopCount);
}

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

Запуск цикла выполнения

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

Есть несколько способов, чтобы запустить цикл выполнения, включая следующие:

  • Безоговорочный
  • В установленные сроки
  • В определенном режиме

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

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

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

Листинг ниже показывает каркас версии главной процедуры вступления в поток. Основной частью этого примера показана основная структура цикла выполнения. По сути, необходимо добавить входной источник и таймеры для выполнения цикла, а затем повторно вызвать по одной из процедур для начала цикла выполнения. Каждый раз, когда возвращается цикл выполнения, вы убеждаетесь в том, что могли возникнуть основания для выхода из потока. В примере используются процедуры Core Foundation цикла выполнения, так что он может проверить обратный результат и определить, почему цикл выполнения завершился. Кроме того, можно использовать методы NSRunLoop класса для запуска цикла выполнения в подобной манере, если вы используете Cocoa и не нужно проверять возвращаемое значение.

- (void)skeletonThreadMain
{

    // Установите autorelease пул здесь, если не используется сборка мусора.
    BOOL done = NO;

    // Добавьте ваши источники и таймеры и выполните прочие настройки.

    do
    {

        // Старт цикла выполнения, с возвратом после каждого выполнения источника

        SInt32    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);

        // Если источник явно остановился по ходу цикла, или, если нет источников или таймеров, идти вперед и выходит.

        if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
            done = YES;

        // Проверка каких-либо критериев для выхода

    }
    while (!done);

    // Очистка ресурсов. Удостоверьтесь в освобождении выделенного autorelease пула.

}

Есть возможность выполнения цикла выполнения рекурсивно. Другими словами, вы можете вызвать CFRunLoopRun, CFRunLoopRunInMode, или любой из NSRunLoop методов для начала выполнения цикла внутри метода обработчика входного источника или таймера. Делая это, вы можете использовать любой режим, на котором вы хотите запустить вложенный цикл, включая режим использования внешнего цикла выполнения.

Выход из цикла выполнения

Есть два пути выхода из цикла выполнения, до обработки событий:

  • Настройка цикла выполнения для работы с тайм-аутом.
  • Сказать циклу выполнения остановиться.

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

Остановка цикла выполнения явно с CFRunLoopStop функцией дает результат, близкий к тайм-ауту. Цикл выполнения посылает остановку в цикл уведомлений, а затем выходит. Разница в том, что вы можете использовать эту технику для цикла выполнения, который вы начали безоговорочно.

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

close

Блок объект Читать более подробно

Блок объекты - синтаксические и выполняемые функций C-уровня, которые позволяют составить функцию выражения, которая может быть переданы в качестве аргумента, при необходимости хранения и использования в многопоточных приложениях. Функция выражения может ссылаться и может сохранить доступ к локальным переменным. В других языках и средах, блок объекта иногда называют закрытием или лямбдой. Вы можете использовать блок, когда вы хотите создать рабочие модули (то есть сегменты кода), которые могут быть переданы как будто они являются значениями. Блоки предлагают более гибкое программирование и больше возможностей. Вы можете использовать их, например, для написания обратных вызовов или для выполнения операции для всех элементов в коллекции.

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

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

float (^aBlock)(const int*, int, float);

Создание блока

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

int (^oneFrom)(int);

oneFrom = ^(int anInt) {
    return anInt - 1;
};

Закрытие точкой с запятой требуется в качестве стандарта C конца линии маркера.

Если вы явно не объявляете возвращаемое значение выражения блока, оно может автоматически выводится из содержимого блока.

Блок-изменяемые переменные

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

Использование блоков

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

printf("%d\n", oneFrom(10));

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

В следующем примере определяется, содержит ли объект NSSet слово, указанное в локальной переменной и задает значение другой локальной переменной (found) в YES (и прекращает поиск), если он выполняется. В этом примере, found объявлена ​​как переменная __block.

__block BOOL found = NO;
NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta", @"Gamma", @"X", nil];
NSString *string = @"gamma";

[aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
    if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {
        *stop = YES;
        found = YES;
    }
}];

// В этой точке, found == YES

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

Операции сравнения

Одной из наиболее распространенных операций, которые вы выполняете с блоками в среде Cocoa сравнение двух объектов, сортировка содержимого массива. Среда выполнения Objective-C определяет блок типа NSComparator для использования этих сравнений.

 
 
homeЗаметили ошибкукарта сайта 
   Made on a Mac