Выполненные циклы

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

Выполненное управление циклом не является полностью автоматическим. Необходимо все еще разработать код потока, чтобы запустить цикл выполнения в подходящее время и реагировать на входящие события. И Какао и Базовая Основа обеспечивают, выполненный цикл возражает, чтобы помочь Вам сконфигурировать и управлять циклом выполнения своего потока. Ваше приложение не должно создавать эти объекты явно; каждый поток, включая основной поток приложения, имеет связанный объект цикла выполнения. Только вторичные потоки должны выполнить свой цикл выполнения явно, как бы то ни было. Платформы приложения, автоматически установленные и выполненные цикл выполнения на основном потоке как часть приложения, запускают процесс.

Следующие разделы предоставляют больше информации о выполненных циклах и как Вы конфигурируете их для своего приложения. Для получения дополнительной информации о выполненных объектах цикла посмотрите Ссылку Ссылки класса и CFRunLoop NSRunLoop.

Анатомия цикла выполнения

Цикл выполнения очень походит на свои звуки имени. Это - цикл, который Ваш поток вводит и использование для выполнения обработчиков событий в ответ на входящие события. Ваш код обеспечивает, операторы управления раньше реализовывали фактическую часть цикла цикла выполнения — другими словами, Ваш код обеспечивает while или for цикл, управляющий циклом выполнения. В Вашем цикле Вы используете объект цикла выполнения "выполнить” код обработки событий, получающий события и вызывающий установленные обработчики.

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

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

  Структура рисунка 3-1 цикла выполнения и его источников
Structure of a run loop and its sources

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

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

Выполненные режимы цикла

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

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

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

Таблица 3-1 перечисляет стандартные режимы, определенные Какао и Базовой Основой вместе с описанием того, когда Вы используете тот режим. Списки столбцов имени фактические константы Вы используете для указания режима в коде.

Таблица 3-1  Предопределенные режимы цикла выполнения

Режим

Имя

Описание

Значение по умолчанию

NSDefaultRunLoopMode (Какао)

kCFRunLoopDefaultMode (Базовая Основа)

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

Соединение

NSConnectionReplyMode (Какао)

Какао использует этот режим в сочетании с NSConnection объекты контролировать ответы. Необходимо редко должны быть использовать этот режим сами.

Модальный

NSModalPanelRunLoopMode (Какао)

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

Отслеживание события

NSEventTrackingRunLoopMode (Какао)

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

Общие режимы

NSRunLoopCommonModes (Какао)

kCFRunLoopCommonModes (Базовая Основа)

Это - конфигурируемая группа обычно используемых режимов. Соединение входного источника с этим режимом также связывает его с каждым из режимов в группе. Для приложений Какао этот набор включает значение по умолчанию, модальное, и режимы отслеживания события по умолчанию. Базовая Основа включает просто режим по умолчанию первоначально. Можно добавить пользовательские режимы к набору с помощью CFRunLoopAddCommonMode функция.

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

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

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

Следующие разделы описывают некоторые входные источники.

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

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

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

Для примеров того, как установить и сконфигурировать пользовательские основанные на порте источники, посмотрите Конфигурирование Основанного на порте Входного Источника.

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

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

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

Для примера того, как создать пользовательский входной источник, посмотрите Определение Пользовательского Входного Источника. Для справочной информации для пользовательских входных источников см. также Ссылку CFRunLoopSource.

Какао выполняет селекторные источники

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

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

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

Табличные 3-2  селекторы Выполнения на других потоках

Методы

Описание

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: метод.

Для получения дальнейшей информации о каждом из этих методов, см. Ссылку класса NSObject.

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

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

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

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

Для получения дополнительной информации о конфигурировании источников таймера посмотрите Источники Таймера Конфигурирования. Для справочной информации см. Ссылку Ссылки класса или CFRunLoopTimer NSTimer.

Выполненные наблюдатели цикла

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

  • Вход к циклу выполнения.

  • Когда цикл выполнения собирается обработать таймер.

  • Когда цикл выполнения собирается обработать входной источник.

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

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

  • Выход от цикла выполнения.

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

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

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

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

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

  1. Уведомьте наблюдателей, что был введен цикл выполнения.

  2. Уведомьте наблюдателей, что любые готовые таймеры собираются стрелять.

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

  4. Увольте любого не, порт базировал входные источники, которые готовы стрелять.

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

  6. Уведомьте наблюдателей, что поток собирается спать.

  7. Поместите поток для сна, пока одно из следующих событий не будет иметь место:

    • Событие поступает для основанного на порте входного источника.

    • Таймер стреляет.

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

    • Цикл выполнения явно разбужен.

  8. Уведомьте наблюдателей, что просто проснулся поток.

  9. Обработайте незаконченное событие.

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

    • Если входной источник стрелял, поставьте событие.

    • Если цикл выполнения был явно разбужен, но еще не испытал таймаут, перезапустите цикл. Перейдите к шагу 2.

  10. Уведомьте наблюдателей, что вышел цикл выполнения.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В дополнение к установке источников можно также установить выполненных наблюдателей цикла и использовать их для обнаружения различных этапов выполнения цикла выполнения. Для установки наблюдателя цикла выполнения Вы создаете a CFRunLoopObserverRef непрозрачный тип и использование CFRunLoopAddObserver функция для добавления его к циклу выполнения. Выполненные наблюдатели цикла должны быть созданы с помощью Базовой Основы, даже для приложений Какао.

Перечисление 3-1 показывает основную подпрограмму для потока, присоединяющего наблюдателя цикла выполнения к его циклу выполнения. Цель примера состоит в том, чтобы показать Вам, как создать наблюдателя цикла выполнения, таким образом, код просто устанавливает наблюдателя цикла выполнения для контроля всех действий цикла выполнения. Основная подпрограмма обработчика (не показанный) просто регистрирует действие цикла выполнения, поскольку это обрабатывает запросы таймера.

Перечисление 3-1  , Создающее наблюдателя цикла выполнения

- (void)threadMain
{
    // The application uses garbage collection, so no autorelease pool is needed.
    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
    // Create a run loop observer and attach it to the run loop.
    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);
    }
 
    // Create and schedule the timer.
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
                selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
 
    NSInteger    loopCount = 10;
    do
    {
        // Run the run loop 10 times to let the timer fire.
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    }
    while (loopCount);
}

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

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

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

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

  • Безусловно

  • С ограничением по времени набора

  • В определенном режиме

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

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

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

Перечисление 3-2 показывает скелетную версию основной подпрограммы записи потока. Ключевая часть этого примера показывает базовую структуру цикла выполнения. В сущности Вы добавляете свои входные источники и таймеры к циклу выполнения и затем неоднократно вызываете одну из подпрограмм для запуска цикла выполнения. Каждый раз возвраты подпрограммы цикла выполнения, Вы проверяете, чтобы видеть, возникли ли какие-либо условия, который мог бы гарантировать выход из потока. Пример использует Базовую Основу выполненные подпрограммы цикла так, чтобы это могло проверить, что возврат заканчивается и определяет, почему вышел цикл выполнения. Вы могли также использовать методы NSRunLoop класс для выполнения цикла выполнения подобным образом, если Вы используете Какао и не должны проверять возвращаемое значение. (Для примера цикла выполнения, вызывающего методы NSRunLoop класс, см. Перечисление 3-14.)

Перечисление 3-2  , Выполняющее цикл выполнения

- (void)skeletonThreadMain
{
    // Set up an autorelease pool here if not using garbage collection.
    BOOL done = NO;
 
    // Add your sources or timers to the run loop and do any other setup.
 
    do
    {
        // Start the run loop but return after each source is handled.
        SInt32    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
 
        // If a source explicitly stopped the run loop, or if there are no
        // sources or timers, go ahead and exit.
        if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
            done = YES;
 
        // Check for any other exit conditions here and set the
        // done variable as needed.
    }
    while (!done);
 
    // Clean up code here. Be sure to release any allocated autorelease pools.
}

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

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

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

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

  • Скажите циклу выполнения останавливаться.

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

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

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

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

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

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

Конфигурирование выполненных источников цикла

Следующие разделы показывают примеры того, как установить различные типы входных источников и в Какао и в Базовой Основе.

Определение пользовательского входного источника

Создание пользовательского входного источника включает определение следующего:

  • Информация Вы хотите, чтобы Ваш входной источник обработал.

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

  • Подпрограмма обработчика для выполнения запросов, отправленных любыми клиентами.

  • Подпрограмма отмены для лишения законной силы входного источника.

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

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

Рисунок 3-2  , Управляющий пользовательским входным источником
Operating a custom input source

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

Определение входного источника

Определение пользовательского входного источника требует, чтобы использование Базовых подпрограмм Основы сконфигурировало Ваш источник цикла выполнения и присоединило его к циклу выполнения. Несмотря на то, что основные обработчики являются функциями на базе С, который не устраняет Вас от записи оберток для тех функций и использования Objective C или C++ для реализации организации кода.

Входной источник, представленный на рисунке 3-2, использует объект Objective C управлять буфером команд и координатой с циклом выполнения. Перечисление 3-3 показывает определение этого объекта. RunLoopSource объект управляет буфером команд и использованием, буферизующим для получения сообщений из других потоков. Это перечисление также показывает определение RunLoopContext объект, который является действительно просто контейнерным объектом, раньше передавал a RunLoopSource возразите и ссылка цикла выполнения на основной поток приложения.

Перечисление 3-3  пользовательское входное определение исходного объекта

@interface RunLoopSource : NSObject
{
    CFRunLoopSourceRef runLoopSource;
    NSMutableArray* commands;
}
 
- (id)init;
- (void)addToCurrentRunLoop;
- (void)invalidate;
 
// Handler method
- (void)sourceFired;
 
// Client interface for registering commands to process
- (void)addCommand:(NSInteger)command withData:(id)data;
- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runloop;
 
@end
 
// These are the CFRunLoopSourceRef callback functions.
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
void RunLoopSourcePerformRoutine (void *info);
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
 
// RunLoopContext is a container object used during registration of the input source.
@interface RunLoopContext : NSObject
{
    CFRunLoopRef        runLoop;
    RunLoopSource*        source;
}
@property (readonly) CFRunLoopRef runLoop;
@property (readonly) RunLoopSource* source;
 
- (id)initWithSource:(RunLoopSource*)src andLoop:(CFRunLoopRef)loop;
@end

Несмотря на то, что код Objective C управляет пользовательскими данными входного источника, присоединение входного источника к циклу выполнения требует функций обратного вызова на базе С. Первая из этих функций вызвана, когда Вы фактически присоединяете источник цикла выполнения к своему циклу выполнения, и показан в Перечислении 3-4. Поскольку этот входной источник имеет только один клиент (основной поток), это использует функцию планировщика для отправки сообщения для регистрации себя в делегате приложения на том потоке. Когда делегат хочет связаться с входным источником, он использует информацию в RunLoopContext объект сделать так.

Перечисление 3-4  Планируя источник цикла выполнения

void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
    RunLoopSource* obj = (RunLoopSource*)info;
    AppDelegate*   del = [AppDelegate sharedAppDelegate];
    RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
 
    [del performSelectorOnMainThread:@selector(registerSource:)
                                withObject:theContext waitUntilDone:NO];
}

Когда входной источник сообщен, одна из самых важных подпрограмм обратного вызова является той, используемой для обработки пользовательских данных. Перечисление 3-5 показывает выполнять подпрограмму обратного вызова, связанную с RunLoopSource объект. Эта функция просто передает запрос для выполнения работы к sourceFired метод, тогда обрабатывающий любое настоящее команд в буфере команд.

Перечисление 3-5  , Выполняющее работу во входном источнике

void RunLoopSourcePerformRoutine (void *info)
{
    RunLoopSource*  obj = (RunLoopSource*)info;
    [obj sourceFired];
}

Если Вы когда-нибудь удаляете свой входной источник из его цикла выполнения с помощью CFRunLoopSourceInvalidate функция, системные вызовы Ваша входная подпрограмма отмены источника. Можно использовать эту подпрограмму, чтобы уведомить клиенты, что входной источник больше не действителен и что они должны удалить любые ссылки на него. Перечисление 3-6 показывает подпрограмму обратного вызова отмены, зарегистрированную в RunLoopSource объект. Эта функция отправляет другого RunLoopContext возразите против делегата приложения, но на сей раз просит, чтобы делегат удалил ссылки на источник цикла выполнения.

Перечисление 3-6  , Лишающее законной силы входной источник

void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
    RunLoopSource* obj = (RunLoopSource*)info;
    AppDelegate* del = [AppDelegate sharedAppDelegate];
    RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
 
    [del performSelectorOnMainThread:@selector(removeSource:)
                                withObject:theContext waitUntilDone:YES];
}

Установка входного источника на цикле выполнения

Перечисление 3-7 показывает init и addToCurrentRunLoop методы RunLoopSource класс. init метод создает CFRunLoopSourceRef непрозрачный тип, который должен фактически быть присоединен к циклу выполнения. Это передает RunLoopSource сам объект как контекстная информация так, чтобы подпрограммы обратного вызова имели указатель на объект. Установка входного источника не происходит, пока рабочий поток не вызывает addToCurrentRunLoop метод тот, в который точка RunLoopSourceScheduleRoutine функцию обратного вызова вызывают. Как только входной источник добавляется к циклу выполнения, поток может выполнить свой цикл выполнения для ожидания на нем.

Перечисление 3-7  , Устанавливающее источник цикла выполнения

- (id)init
{
    CFRunLoopSourceContext    context = {0, self, NULL, NULL, NULL, NULL, NULL,
                                        &RunLoopSourceScheduleRoutine,
                                        RunLoopSourceCancelRoutine,
                                        RunLoopSourcePerformRoutine};
 
    runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);
    commands = [[NSMutableArray alloc] init];
 
    return self;
}
 
- (void)addToCurrentRunLoop
{
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
}

Координирование с клиентами входного источника

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

Когда Ваш входной источник сначала установлен на его цикле выполнения, один способ уведомить клиенты о Вашем входном источнике состоит в том, чтобы отослать регистрационные запросы. Можно зарегистрировать входной источник в стольких клиентах, сколько Вы хотите, или можно просто зарегистрировать его в некотором центральном агентстве, тогда продающем входной источник заинтересованным клиентам. Перечисление 3-8 показывает регистрационный метод, определенный делегатом приложения и вызванный когда RunLoopSource функция планировщика объекта вызвана. Этот метод получает RunLoopContext объект, предоставленный RunLoopSource возразите и добавляет его к его списку источников. Это перечисление также показывает подпрограмму, используемую, чтобы не зарегистрировать входной источник, когда это удалено из его цикла выполнения.

  Регистрация перечисления 3-8 и удаление входного источника с делегатом приложения

- (void)registerSource:(RunLoopContext*)sourceInfo;
{
    [sourcesToPing addObject:sourceInfo];
}
 
- (void)removeSource:(RunLoopContext*)sourceInfo
{
    id    objToRemove = nil;
 
    for (RunLoopContext* context in sourcesToPing)
    {
        if ([context isEqual:sourceInfo])
        {
            objToRemove = context;
            break;
        }
    }
 
    if (objToRemove)
        [sourcesToPing removeObject:objToRemove];
}

Сигнализация входного источника

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

Перечисление 3-9 показывает fireCommandsOnRunLoop метод RunLoopSource объект. Клиенты вызывают этот метод, когда они готовы к источнику обработать команды, они добавили к буферу.

Перечисление 3-9  , Будящее цикл выполнения

- (void)fireCommandsOnRunLoop:(CFRunLoopRef)runloop
{
    CFRunLoopSourceSignal(runLoopSource);
    CFRunLoopWakeUp(runloop);
}

Конфигурирование источников таймера

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

В Какао можно создать и запланировать таймер одновременно с помощью любого из этих методов класса:

Эти методы создают таймер и добавляют его к циклу выполнения текущего потока в режиме по умолчанию (NSDefaultRunLoopMode). Если Вы захотите путем создания Вашего, можно также запланировать таймер вручную NSTimer возразите и затем добавление его к циклу выполнения с помощью addTimer:forMode: метод NSRunLoop. Оба метода делают в основном ту же вещь, но дают Вам разные уровни управления конфигурацией таймера. Например, если Вы создаете таймер и добавляете его к циклу выполнения вручную, можно сделать настолько использующий режим кроме режима по умолчанию. Перечисление 3-10 показывает, как создать таймеры с помощью обоих методов. Новичок имеет начальную задержку 1 секунды, но тогда стреляет регулярно каждую 0.1 секунды после этого. Второй таймер начинает стрелять после начальных 0,2 вторых задержек и затем стреляет каждые 0.2 секунды после этого.

Перечисление 3-10  Создающие и планирующие таймеры с помощью NSTimer

NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
// Create and schedule the first timer.
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
                        interval:0.1
                        target:self
                        selector:@selector(myDoFireTimer1:)
                        userInfo:nil
                        repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
 
// Create and schedule the second timer.
[NSTimer scheduledTimerWithTimeInterval:0.2
                        target:self
                        selector:@selector(myDoFireTimer2:)
                        userInfo:nil
                        repeats:YES];

Перечисление 3-11 показывает, что код должен был сконфигурировать таймер с помощью Базовых функций Основы. Несмотря на то, что этот пример не передает определяемой пользователем информации в структуре контекста, Вы могли использовать эту структуру для раздавания любых пользовательских данных, в которых Вы нуждались для своего таймера. Для получения дополнительной информации о содержании этой структуры, см. ее описание в Ссылке CFRunLoopTimer.

  Создание перечисления 3-11 и планирование таймера с помощью Базовой Основы

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,
                                        &myCFTimerCallback, &context);
 
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);

Конфигурирование основанного на порте входного источника

И Какао и Базовая Основа обеспечивают основанные на порте объекты для передачи между потоками или между процессами. Следующие разделы показывают Вам, как установить коммуникацию порта с помощью нескольких различных типов портов.

Конфигурирование объекта NSMachPort

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

Реализация основного кода потока

Перечисление 3-12 показывает основной код потока для запуска вторичного рабочего потока. Поскольку платформа Какао выполняет многие прошедшие шаги для конфигурирования порта и выполненного цикла, launchThread метод заметно короче, чем его Базовая Основа, эквивалентная (Перечисление 3-17); однако, поведение этих двух почти идентично. Одно различие - то, что вместо того, чтобы отправить имя локального порта к рабочему потоку, этот метод отправляет NSPort возразите непосредственно.

Перечисление 3-12  Основной метод запуска потока

- (void)launchThread
{
    NSPort* myPort = [NSMachPort port];
    if (myPort)
    {
        // This class handles incoming port messages.
        [myPort setDelegate:self];
 
        // Install the port as an input source on the current run loop.
        [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
 
        // Detach the thread. Let the worker release the port.
        [NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:)
               toTarget:[MyWorkerClass class] withObject:myPort];
    }
}

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

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

Перечисление 3-13  , Обрабатывающее сообщения порта Маха

#define kCheckinMessage 100
 
// Handle responses from the worker thread.
- (void)handlePortMessage:(NSPortMessage *)portMessage
{
    unsigned int message = [portMessage msgid];
    NSPort* distantPort = nil;
 
    if (message == kCheckinMessage)
    {
        // Get the worker thread’s communications port.
        distantPort = [portMessage sendPort];
 
        // Retain and save the worker port for later use.
        [self storeDistantPort:distantPort];
    }
    else
    {
        // Handle other messages.
    }
}
Реализация вторичного кода потока

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

Перечисление 3-14 показывает код для установки рабочего потока. После создания пула автовыпуска для потока метод создает объект работника управлять выполнением потока. Объект работника sendCheckinMessage: метод (показанный в Перечислении 3-15) создает локальный порт для рабочего потока и передает сообщение регистрации обратно основному потоку.

Перечисление 3-14  , Запускающееся рабочий поток с помощью портов Маха

+(void)LaunchThreadWithPort:(id)inData
{
    NSAutoreleasePool*  pool = [[NSAutoreleasePool alloc] init];
 
    // Set up the connection between this thread and the main thread.
    NSPort* distantPort = (NSPort*)inData;
 
    MyWorkerClass*  workerObj = [[self alloc] init];
    [workerObj sendCheckinMessage:distantPort];
    [distantPort release];
 
    // Let the run loop process things.
    do
    {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                            beforeDate:[NSDate distantFuture]];
    }
    while (![workerObj shouldExit]);
 
    [workerObj release];
    [pool release];
}

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

Перечисление 3-15 показывает подпрограмму регистрации вторичного потока. Этот метод устанавливает свой собственный локальный порт для будущей коммуникации и затем передает сообщение регистрации обратно основному потоку. Метод использует объект порта, полученный в LaunchThreadWithPort: метод как цель сообщения.

Перечисление 3-15  , Отправляющее сообщение регистрации с помощью портов Маха

// Worker thread check-in method
- (void)sendCheckinMessage:(NSPort*)outPort
{
    // Retain and save the remote port for future use.
    [self setRemotePort:outPort];
 
    // Create and configure the worker thread port.
    NSPort* myPort = [NSMachPort port];
    [myPort setDelegate:self];
    [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
 
    // Create the check-in message.
    NSPortMessage* messageObj = [[NSPortMessage alloc] initWithSendPort:outPort
                                         receivePort:myPort components:nil];
 
    if (messageObj)
    {
        // Finish configuring the message and send it immediately.
        [messageObj setMsgId:setMsgid:kCheckinMessage];
        [messageObj sendBeforeDate:[NSDate date]];
    }
}

Конфигурирование объекта NSMessagePort

Установить локальное соединение с NSMessagePort объект, Вы не можете просто передать объекты порта между потоками. Удаленные порты сообщения должны быть получены по имени. Создание этого возможного в Какао требует регистрации Вашего локального порта с собственным именем и затем передачей того имени к удаленному потоку так, чтобы это могло получить надлежащий объект порта для коммуникации. Перечисление 3-16 показывает создание порта и процесс регистрации в случаях, где Вы хотите использовать порты сообщения.

Перечисление 3-16  , Регистрирующее порт сообщения

NSPort* localPort = [[NSMessagePort alloc] init];
 
// Configure the object and add it to the current run loop.
[localPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:localPort forMode:NSDefaultRunLoopMode];
 
// Register the port using a specific name. The name must be unique.
NSString* localPortName = [NSString stringWithFormat:@"MyPortName"];
[[NSMessagePortNameServer sharedInstance] registerPort:localPort
                     name:localPortName];

Конфигурирование основанного на порте входного источника в базовой основе

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

Перечисление 3-17 показывает код, вызванный основным потоком приложения для запуска рабочего потока. Первая вещь, которую делает код, устанавливается a CFMessagePortRef непрозрачный тип для прислушиваний к сообщениям от рабочих потоков. Для рабочего потока нужно имя порта для создания соединения, так, чтобы строковая ценность была сформирована к функции точки входа рабочего потока. Имена порта должны обычно быть уникальными в текущем пользовательском контексте; иначе, Вы могли бы столкнуться с конфликтами.

Перечисление 3-17  , Присоединяющее Базовую Основу, передает порт к новому потоку

#define kThreadStackSize        (8 *4096)
 
OSStatus MySpawnThread()
{
    // Create a local port for receiving responses.
    CFStringRef myPortName;
    CFMessagePortRef myPort;
    CFRunLoopSourceRef rlSource;
    CFMessagePortContext context = {0, NULL, NULL, NULL, NULL};
    Boolean shouldFreeInfo;
 
    // Create a string with the port name.
    myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.myapp.MainThread"));
 
    // Create the port.
    myPort = CFMessagePortCreateLocal(NULL,
                myPortName,
                &MainThreadResponseHandler,
                &context,
                &shouldFreeInfo);
 
    if (myPort != NULL)
    {
        // The port was successfully created.
        // Now create a run loop source for it.
        rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
 
        if (rlSource)
        {
            // Add the source to the current run loop.
            CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
 
            // Once installed, these can be freed.
            CFRelease(myPort);
            CFRelease(rlSource);
        }
    }
 
    // Create the thread and continue processing.
    MPTaskID        taskID;
    return(MPCreateTask(&ServerThreadEntryPoint,
                    (void*)myPortName,
                    kThreadStackSize,
                    NULL,
                    NULL,
                    NULL,
                    0,
                    &taskID));
}

С установленным портом и поток запустился, основной поток может продолжать свое регулярное выполнение, в то время как это ожидает потока для регистрации. Когда сообщение регистрации поступает, оно диспетчеризируется основному потоку MainThreadResponseHandler функция, показанная в Перечислении 3-18. Эта функция извлекает имя порта для рабочего потока и создает кабелепровод для будущей коммуникации.

Перечисление 3-18  , Получающее сообщение регистрации

#define kCheckinMessage 100
 
// Main thread port message handler
CFDataRef MainThreadResponseHandler(CFMessagePortRef local,
                    SInt32 msgid,
                    CFDataRef data,
                    void* info)
{
    if (msgid == kCheckinMessage)
    {
        CFMessagePortRef messagePort;
        CFStringRef threadPortName;
        CFIndex bufferLength = CFDataGetLength(data);
        UInt8* buffer = CFAllocatorAllocate(NULL, bufferLength, 0);
 
        CFDataGetBytes(data, CFRangeMake(0, bufferLength), buffer);
        threadPortName = CFStringCreateWithBytes (NULL, buffer, bufferLength, kCFStringEncodingASCII, FALSE);
 
        // You must obtain a remote message port by name.
        messagePort = CFMessagePortCreateRemote(NULL, (CFStringRef)threadPortName);
 
        if (messagePort)
        {
            // Retain and save the thread’s comm port for future reference.
            AddPortToListOfActiveThreads(messagePort);
 
            // Since the port is retained by the previous function, release
            // it here.
            CFRelease(messagePort);
        }
 
        // Clean up.
        CFRelease(threadPortName);
        CFAllocatorDeallocate(NULL, buffer);
    }
    else
    {
        // Process other messages.
    }
 
    return NULL;
}

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

Перечисление 3-19  , Настраивающее структуры потока

OSStatus ServerThreadEntryPoint(void* param)
{
    // Create the remote port to the main thread.
    CFMessagePortRef mainThreadPort;
    CFStringRef portName = (CFStringRef)param;
 
    mainThreadPort = CFMessagePortCreateRemote(NULL, portName);
 
    // Free the string that was passed in param.
    CFRelease(portName);
 
    // Create a port for the worker thread.
    CFStringRef myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.MyApp.Thread-%d"), MPCurrentTaskID());
 
    // Store the port in this thread’s context info for later reference.
    CFMessagePortContext context = {0, mainThreadPort, NULL, NULL, NULL};
    Boolean shouldFreeInfo;
    Boolean shouldAbort = TRUE;
 
    CFMessagePortRef myPort = CFMessagePortCreateLocal(NULL,
                myPortName,
                &ProcessClientRequest,
                &context,
                &shouldFreeInfo);
 
    if (shouldFreeInfo)
    {
        // Couldn't create a local port, so kill the thread.
        MPExit(0);
    }
 
    CFRunLoopSourceRef rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
    if (!rlSource)
    {
        // Couldn't create a local port, so kill the thread.
        MPExit(0);
    }
 
    // Add the source to the current run loop.
    CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
 
    // Once installed, these can be freed.
    CFRelease(myPort);
    CFRelease(rlSource);
 
    // Package up the port name and send the check-in message.
    CFDataRef returnData = nil;
    CFDataRef outData;
    CFIndex stringLength = CFStringGetLength(myPortName);
    UInt8* buffer = CFAllocatorAllocate(NULL, stringLength, 0);
 
    CFStringGetBytes(myPortName,
                CFRangeMake(0,stringLength),
                kCFStringEncodingASCII,
                0,
                FALSE,
                buffer,
                stringLength,
                NULL);
 
    outData = CFDataCreate(NULL, buffer, stringLength);
 
    CFMessagePortSendRequest(mainThreadPort, kCheckinMessage, outData, 0.1, 0.0, NULL, NULL);
 
    // Clean up thread data structures.
    CFRelease(outData);
    CFAllocatorDeallocate(NULL, buffer);
 
    // Enter the run loop.
    CFRunLoopRun();
}

Как только это вводит свой цикл выполнения, все будущие события, отправленные в порт потока, обрабатываются ProcessClientRequest функция. Реализация той функции зависит от типа работы, которую поток выполняет и не показан здесь.