Понимание NSRunLoop

109

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

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

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

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

тафарель
источник
Это слишком широкий вопрос. Уточните свой вопрос, задав более конкретный вопрос.
Джоди Хагинс
3
сначала я хочу знать, что делают в общем NSRunLoop и как это связано с Thread
taffarel

Ответы:

211

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

Каждый 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#//apple_ref/doc/uid/10000057i-CH16-SW1

Джоди Хейгинс
источник
2
это означает, что я могу получить доступ / запустить цикл только внутри потока, верно? есть ли простой пример, как добавить событие в цикл выполнения? что означает, что он вернется в свой цикл? цикл выполнения неактивен, когда я запускаю поток? можно ли добавить некоторые события в цикл выполнения потока вне потока? Означает ли это, что иногда я могу использовать цикл выполнения для блокировки потока на время?
taffarel
«Установите обработчик для любого действия ввода-вывода (например, нажатия кнопки), которое засыпает». Вы имеете в виду, что если я продолжу удерживать палец на кнопке, он продолжит блокировать поток какое-то время ?!
Мед
Нет. Я имею в виду, что цикл выполнения не обрабатывает новые события, пока обработчик не завершится. Если вы спите (или выполняете какую-то операцию, которая занимает много времени) в обработчике, цикл выполнения будет заблокирован, пока обработчик не завершит свою работу.
Джоди Хагинс
@taffarel можно ли добавить некоторые события в цикл выполнения потока вне потока? Если это означает «могу ли я заставить код работать в цикле выполнения другого потока по своему желанию», то ответ действительно положительный. Просто вызовите performSelector:onThread:withObject:waitUntilDone:, передав NSThreadобъект, и ваш селектор будет запланирован на цикл выполнения этого потока.
Mecki
12

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

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

От сюда

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

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

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


В частности, о: «Фоновые потоки не имеют собственных циклов выполнения». Следующий таймер не срабатывает для асинхронной отправки:

class T {
    var timer: Timer?

    func fireWithoutAnyQueue() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { _ in
            print("without any queue") // success. It's being ran on main thread, since playgrounds begin running from main thread
        })
    }

    func fireFromQueueAsnyc() {
        let queue = DispatchQueue(label: "whatever")
        queue.async {
            self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from a queue — async") // failed to print
            })
        }
    }

    func fireFromQueueSnyc() {
        let queue = DispatchQueue(label: "whatever")
        queue.sync {
            timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from a queue — sync") // success. Weird. Read my possible explanation below
            })
        }
    }

    func fireFromMain() {
        DispatchQueue.main.async {
            self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from main queue — sync") //success
            })
        }
    }
}

Я думаю, что syncблок также запускается потому, что:

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

Чтобы проверить это, я заходил RunLoop.currentв каждую рассылку.

У отправки синхронизации был такой же цикл выполнения, что и у основной очереди. В то время как RunLoop в блоке async отличался от других экземпляров. Вы можете подумать, почему RunLoop.currentвозвращается другое значение. Разве это не общая ценность !? Отличный вопрос! Читайте дальше:

ВАЖНАЯ ЗАМЕТКА:

Свойство класса current НЕ является глобальной переменной.

Возвращает цикл выполнения для текущего потока.

Это контекстно. Это видно только в рамках потока, то есть в локальном хранилище потока . Подробнее об этом см. Здесь .

Это известная проблема с таймерами. У вас не будет такой же проблемы, если вы используетеDispatchSourceTimer

Мед
источник
8

RunLoops чем-то напоминает ящик, в котором что-то происходит.

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

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];

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

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

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

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

Акшай Сандервани
источник
0

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

Отсюда


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

Отсюда

dengApro
источник