Розуміння NSRunLoop


108

Хтось може пояснити, що таке NSRunLoop? так що, як я знаю NSRunLoop, це щось пов'язане з NSThreadправом? Тож припустимо, що я створюю тему як

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

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

так що після цього Thread закінчує своє робоче право? навіщо використовувати RunLoopsабо де використовувати? від документів Apple я щось прочитав, але для мене це не зрозуміло, тому, будь ласка, поясніть якомога простіше


Це питання занадто широке за обсягом. Будь ласка, уточнюйте своє запитання до чогось більш конкретного.
Джоді Хейґінс

3
спочатку я хочу знати, що робити в genereal NSRunLoop і як це пов'язано з Thread
taffarel

Відповіді:


211

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

Кожен NSThread має свій цикл запуску, до якого можна отримати доступ методом currentRunLoop.

Загалом, вам не потрібно безпосередньо отримувати доступ до циклу запуску, хоча є деякі (мережеві) компоненти, які можуть дозволяти вам вказати, який цикл запуску вони використовуватимуть для обробки вводу / виводу.

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

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

Це досить високий рівень опису (намагаються уникати занадто багато деталей).

EDIT

Спроба звернутися до коментаря. Я розбив його на шматки.

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

Справді. 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

"Встановіть обробник для будь-яких дій вводу-виводу (наприклад, натискання кнопок), які сплять." Ви маєте на увазі, якщо я продовжую тримати палець на кнопці, вона продовжуватиме блокувати нитку на час ?!
Мед

Ні. Я маю на увазі те, що runloop не обробляє нові події, поки обробник не завершиться. Якщо ви спите (або виконуєте деяку операцію, яка займає тривалий час) в обробці, цикл запуску буде блокуватися, поки обробник не завершить свою роботу.
Джоді Хейґінс

@taffarel чи можна додати якісь події до циклу запуску теми поза потоком? Якщо це означає "чи можу я зробити запуск коду на запуску іншого потоку за власним бажанням", то відповідь дійсно - так. Просто зателефонуйте performSelector:onThread:withObject:waitUntilDone:, передаючи NSThreadоб'єкт, і ваш селектор буде запланований на запуску цього потоку.
Мецькі

12

Запуск циклів - це те, що відокремлює інтерактивні програми від інструментів командного рядка .

  • Інструменти командного рядка запускаються з параметрами, виконують свою команду, а потім виходять.
  • Інтерактивні програми чекають введення користувача, реагують, а потім продовжують очікувати.

від сюди

Вони дозволяють зачекати, поки користувач натисне і відповісти відповідним чином, дочекатися, коли ви отримаєте завершенняHandler і застосуйте його результати, дочекайтеся отримання таймера та виконання функції. Якщо у вас немає запущеної версії, ви не можете слухати / чекати користувачів, ви не можете дочекатися, коли відбудеться мережевий дзвінок, ви не зможете прокинутися через х хвилин, якщо ви не використовуєте 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 в блоці асинхронізації відрізнявся від інших. Ви можете думати, як чому 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];
}

У цьому RunLoop він запускатиметься, поки ви не завершите якусь іншу роботу і не встановите значення YourBoolFlag на false .

Так само ви можете використовувати їх у нитках.

Сподіваюся, це вам допоможе.


0

Цикли запуску - частина основної інфраструктури, пов'язаної з потоками. Цикл запуску - це цикл обробки подій, який ви використовуєте для планування роботи та координації прийому вхідних подій. Мета циклу запуску - тримати зайняту нитку під час роботи, і покласти її спати, коли її немає.

Звідси


Найважливішою особливістю CFRunLoop є CFRunLoopModes. CFRunLoop працює з системою «Запустити джерела циклу». Джерела реєструються в циклі запуску для одного або декількох режимів, а сам цикл запуску робиться для запуску в заданому режимі. Коли подія надходить до джерела, вона обробляється циклом запуску, лише якщо режим джерела відповідає поточному режиму циклу запуску.

Звідси

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.