Понимание NSRunLoop

Может кто-нибудь объяснить, для чего NSRunLoop? так как я знаю NSRunLoop это что-то, связанное с NSThread право? Итак, предположим, что я создаю тему как

NSThread* th=[[NSThread alloc] initWithTarget:self selector:@selector(someMethod) object:nil];
[th start];

-(void) someMethod
{
    NSLog(@"operation");
}

так после того как этот поток закончит свою работу правильно? зачем использовать RunLoops или где использовать? из документов Apple я что-то читал, но мне это не понятно, поэтому, пожалуйста, объясните как можно проще

6 ответов

Решение

Цикл выполнения - это абстракция, которая (помимо прочего) предоставляет механизм для обработки системных входных источников (сокетов, портов, файлов, клавиатуры, мыши, таймеров и т. Д.).

Каждый NSThread имеет свой собственный цикл выполнения, доступ к которому можно получить с помощью метода currentRunLoop.

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

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

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

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

РЕДАКТИРОВАТЬ

Попытка ответить на комментарий. Я разбил его на куски.

  • это означает, что я могу получить доступ / запустить только для запуска цикла внутри потока, верно?

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

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

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

- (void)addPort:(NSPort *)aPort forMode:(NSString *)mode

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

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
  • что значит, что он потом вернется в свой цикл?

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

  • цикл запуска неактивен, когда я запускаю поток?

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

  • Можно ли добавить некоторые события в цикл выполнения потока вне потока?

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

  • Означает ли это, что иногда я могу использовать цикл выполнения, чтобы заблокировать поток на время

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

То же самое относится к любому циклу выполнения.

Я предлагаю вам прочитать следующую документацию по циклам выполнения:

https://developer.apple.com/documentation/foundation/nsrunloop

и как они используются в потоках:

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

Циклы выполнения - это то, что отделяет интерактивные приложения от инструментов командной строки.

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

Отсюда

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

Также из этого комментария:

Фоновые потоки не имеют своих собственных циклов выполнения, но вы можете просто добавить их. Например, AFNetworking 2.x сделал это. Это была опробованная и настоящая методика для NSURLConnection или NSTimer в фоновых потоках, но мы сами больше этого не делаем, поскольку новые API избавляют от необходимости делать это. Но, похоже, что URLSession выполняет, например, простой запрос, выполняя [см. Левую панель изображения] обработчики завершения в главной очереди, и вы можете видеть, что он имеет цикл выполнения в фоновом потоке

Runloops - что-то вроде коробки, где вещи просто случаются.

В основном в Runloop вы идете обрабатывать некоторые события, а затем возвращаетесь. ИЛИ вернитесь, если он не обрабатывает события до истечения времени ожидания. Вы можете сказать, что это похоже на асинхронные NSURLConnections, Обработка данных в фоновом режиме, не влияя на ваш текущий цикл, и в то же время вам требуются данные синхронно. Что может быть сделано с помощью Runloop, который контролирует асинхронную связь NSURLC и предоставляет данные во время вызова. Вы можете использовать Runloop следующим образом:

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
while (YourBoolFlag && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil])
loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];

в этом Runloop он будет работать до тех пор, пока вы не завершите некоторые другие ваши работы и не установите для YourBoolFlag значение false.

Точно так же вы можете использовать их в потоках.

Надеюсь, это поможет вам.

iOS RunLoop

RunLoop(EventLoop, Looper)является реализацией шаблона EventLoop (цикл обработки событий). Он основан наNSRunLoop(который является оберткойCFRunLoopRef)

Официальный документ

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

Один поток может иметь один RunLoop в одном режиме. Будут обрабатываться только события с этим режимом, все остальные будут ждать запуска RunLoop в этом режиме.

RunLoop — это механизм (основанный на цикле (for, while)), который перемещает запланированную задачу (например, очередь обратного вызова) в поток (стек потоков). RunLoop работает(), когда стек потоков пуст.

event processing loopкогда RunLoop между.entryи.exit. Во время этого RunLoop обрабатывает все запланированные задачи в определенном режиме. Все остальные режимы с собственными очередями будут управляться после

Приложение по умолчанию имеетmain threadсRunLoop(main loop). В остальных случаях вы должны создать его вручную

main run loopотвечает за сливmain queueв приложении.

      //Run loop for the current thread
RunLoop.current

//Run loop of the main thread.
RunLoop.main

Режим

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

режимы:

  • по умолчанию - используется по умолчанию
  • отслеживание — например, при прокрутке UITableViewscrollViewDidScroll
  • общий (это псевдорежим, например [по умолчанию, отслеживание])
  • <custom>- вы можете создать свой собственный режим
      //current input mode
RunLoop.current.currentMode

Например:

  • UIView.draw(_ rect:), действие кнопки... использует
  • NSObject.perform(_:with:afterDelay:) использует
  • использует
  • Timer.scheduledTimerиспользует . Вот почему, когда происходит прокрутка пользовательского интерфейса (в режиме отслеживания), ваш таймер не срабатывает (в режиме по умолчанию). Чтобы исправить это, используйте общий режим -RunLoop.main.add(timer, forMode: .common)
  • ОбъединитьRunLoop.mainпротивDispatchQueue.main(.receive(on:, options:)). RunLoop.main используетRunLoop.perform(_:)который используетdefault mode, DispatchQueue.main используетDispatchQueue.main.asyncкоторый используетcommon mode

источники ввода и таймеры

Цикл выполнения получает события:

  • Input sources- сообщения об асинхронных событиях (при запуске)

    • На основе порта — из другого потока или процесса. сигнализируется ядром автоматически
    • Пользовательские источники ввода — события, инициированные пользователем — действия пользователя, сетевые события. необходимо сигнализировать вручную из другого потока
    • performSelector: onThread
  • Timer sources- таймеры синхронных событий (в определенное время)

Их можно добавить в несколько режимов

наблюдатели

отслеживать изменения состояния RunLoop

Создать цикл выполнения

создать новый поток, настроить RunLoop и запустить поток

  1. создать RunLoopRunLoop.current
  2. Цикл выполнения должен иметь по крайней мере один источник ввода или таймер для мониторинга RunLoop.add(_ timer: Timer, режим forMode: RunLoop.Mode) RunLoop.add(_ aPort: Port, режим forMode: RunLoop.Mode)
  3. запустить RunLoopRunLoop.run()
      let thread = Thread {
    //1. create RunLoop
    //create a new one or return existing run loop for current thread
    //use RunLoop.current instead of RunLoop()
    let customRunLoop = RunLoop.current
    
    //add observer for current RunLoop for cpecufic mode
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), customObserver, CFRunLoopMode.commonModes)

    //2. A run loop must have at least one input source or timer to monitor
    let timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in
        //.default mode
    }
    customRunLoop.add(timer, forMode: .default)

    //3. run RunLoop
    //If no input sources or timers are attached to the run loop, this method exits immediately
    //infinite loop that processes data from the run loop’s input sources and timers.
    //calls RunLoop.run(mode:.default before:)
    customRunLoop.run()
    
    //------
    
    //create custom mode
    let customRunLoopMode = RunLoop.Mode("customeMode")
    
    //2. A run loop must have at least one input source or timer to monitor
    //Will be called when previous RunLoop.run() is done(no input sources or timers) - exit from loop
    let timer2 = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in
        //"customeMode" mode
    }
    customRunLoop.add(timer2, forMode: customRunLoopMode)
    
    //3. run RunLoop
    let isInputSourcesOrTimers = customRunLoop.run(mode: customRunLoopMode, before: Date.distantFuture)
}

thread.start()


let customObserver = CFRunLoopObserverCreateWithHandler(nil, CFRunLoopActivity.allActivities.rawValue , true, 0) { observer, activity in
    switch (activity) {
    case .entry:
        break
    case .beforeTimers:
        break
    case .beforeSources:
        break
    case .beforeWaiting:
        break
    case .afterWaiting:
        break
    case .exit:
        break
    case .allActivities:
        break
    default:
        break
    }
}

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

Отсюда


Наиболее важной особенностью CFRunLoop является CFRunLoopModes. CFRunLoop работает с системой "Run Loop Sources". Источники регистрируются в цикле выполнения для одного или нескольких режимов, и сам цикл выполнения выполняется для выполнения в данном режиме. Когда событие поступает в источник, оно обрабатывается циклом выполнения, только если режим источника соответствует текущему режиму цикла выполнения.

Отсюда

      Swift
let runLoop = RunLoop.current

Obj-c
NSRunLoop * runloop = [NSRunLoop currentRunLoop];

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

Другие вопросы по тегам