Understanding NSRunLoop
Asked Answered
J

6

123

Can anyone explain for what is NSRunLoop? so as I know NSRunLoop is a something connected with NSThread right? So assume I create a Thread like

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

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

so after this Thread finishes his working right? why use RunLoops or where to use ? from Apple docs I have read something but its not clear for me, so please explain as simple as it possible

Jaunita answered 23/8, 2012 at 12:7 Comment(3)
This question is too broad in scope. Please refine your question to something more specific.Mohr
at first i want to know what do in genereal NSRunLoop and how it connected with ThreadJaunita
a blog to deeply understand NSRunloop in iOS suelan.github.io/2021/02/13/20210213-dive-into-runloop-iosTumefaction
M
226

A run loop is an abstraction that (among other things) provides a mechanism to handle system input sources (sockets, ports, files, keyboard, mouse, timers, etc).

Each NSThread has its own run loop, which can be accessed via the currentRunLoop method.

In general, you do not need to access the run loop directly, though there are some (networking) components that may allow you to specify which run loop they will use for I/O processing.

A run loop for a given thread will wait until one or more of its input sources has some data or event, then fire the appropriate input handler(s) to process each input source that is "ready.".

After doing so, it will then return to its loop, processing input from various sources, and "sleeping" if there is no work to do.

That's a pretty high level description (trying to avoid too many details).

EDIT

An attempt to address the comment. I broke it into pieces.

  • it means that i can only access/run to run loop inside the thread right?

Indeed. NSRunLoop is not thread safe, and should only be accessed from the context of the thread that is running the loop.

  • is there any simple example how to add event to run loop?

If you want to monitor a port, you would just add that port to the run loop, and then the run loop would watch that port for activity.

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

You can also add a timer explicitly with

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
  • what means it will then return to its loop?

The run loop will process all ready events each iteration (according to its mode). You will need to look at the documentation to discover about run modes, as that's a bit beyond the scope of a general answer.

  • is run loop inactive when i start the thread?

In most applications, the main run loop will run automatically. However, you are responsible for starting the run loop and responding to incoming events for threads you spin.

  • is it possible to add some events to Thread run loop outside the thread?

I am not sure what you mean here. You don't add events to the run loop. You add input sources and timer sources (from the thread that owns the run loop). The run loop then watches them for activity. You can, of course, provide data input from other threads and processes, but input will be processed by the run loop that is monitoring those sources on the thread that is running the run loop.

  • does it mean that sometimes i can use run loop to block thread for a time

Indeed. In fact, a run loop will "stay" in an event handler until that event handler has returned. You can see this in any app simply enough. Install a handler for any IO action (e.g., button press) that sleeps. You will block the main run loop (and the whole UI) until that method completes.

The same applies to any run loop.

I suggest you read the following documentation on run loops:

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

and how they are used within threads:

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1

Mohr answered 23/8, 2012 at 13:0 Comment(4)
it means that i can only access/run to run loop inside the thread right? is there any simple example how to add event to run loop ? what means it will then return to its loop? is run loop inactive when i start the thread? is it possible to add some events to Thread run loop outside the thread? does it mean that sometimes i can use run loop to block thread for a time ?Jaunita
"Install a handler for any IO action (e.g., button press) that sleeps." Do you mean if I continue to hold my finger on the button, it will continue to block the thread for a time?!Onshore
No. What I mean is that the runloop does not process new events until the handler has completed. If you sleep (or do some operation that takes a long time) in the handler, then the run loop will block until the handler has completed its work.Mohr
@Jaunita is it possible to add some events to Thread run loop outside the thread? If that means "can I make code run on the runloop of another thread at will" then the answer is indeed yes. Just call performSelector:onThread:withObject:waitUntilDone:, passing a NSThread object and your selector will be scheduled on the runloop of that thread.Luettaluevano
O
16

Run loops are what separates interactive apps from command-line tools.

  • Command-line tools are launched with parameters, execute their command, then exit.
  • Interactive apps wait for user input, react, then resume waiting.

From here

They allow you to wait till user taps and respond accordingly, wait till you get a completionHandler and apply its results, wait till you get a timer and perform a function. If you don't have a runloop then you can't be listening/waiting for user taps, you can't wait till a network call is happening, you can't be awoken in x minutes unless you use DispatchSourceTimer or DispatchWorkItem

Also from this comment:

Background threads don't have their own runloops, but you can just add one. E.g. AFNetworking 2.x did it. It was tried and true technique for NSURLConnection or NSTimer on background threads, but we don't do this ourselves much anymore, as newer APIs eliminate the need to do so. But it appears that URLSession does, e.g., here is simple request, running [see the left panel of the image] completion handlers on the main queue, and you can see it has a run loop on background thread


Specifically about: "Background threads don't have their own runloops". The following timer fails to fire for an async dispatch:

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
            })
        }
    }
}

I think the reason the sync block also runs is because:

sync blocks usually just get executed from within their source queue. In this example, source queue is main queue, the whatever queue is the destination queue.

To test that I logged RunLoop.current inside every dispatch.

The sync dispatch had the same runloop as main queue. While the RunLoop within the async block was a different instance from the others. You might be thinking how why does RunLoop.current return a different value. Isn't it a shared value!? Great question! Read further:

IMPORTANT NOTE:

The class property current is NOT a global variable.

Returns the run loop for the current thread.

It's contextual. It's visible only within the scope of the thread ie Thread-local storage. For more on that see here.

This is a known issue with timers. You don't have the same issue if you use DispatchSourceTimer

Onshore answered 4/8, 2017 at 20:25 Comment(0)
M
8

RunLoops are a bit of like a box where stuff just happens.

Basically, in a RunLoop, you go to process some events and then return. Or return if it doesn't process any events before the timeout is hit. You can say it as similar to asynchronous NSURLConnections, Processing data in the background without interfering your current loop and but at the same time, you require data synchronously. Which can be done with the help of RunLoop which makes your asynchronous NSURLConnection and provides data at calling time. You can use a RunLoop like this:

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];

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

In this RunLoop, it will run until you complete some of your other work and set YourBoolFlag to false.

Similarly, you can use them in threads.

Hope this helps you.

Meredith answered 14/10, 2014 at 9:30 Comment(0)
H
3

iOS RunLoop

RunLoop(EventLoop, Looper) is an implementation of EventLoop (event processing loop) pattern. It is based on NSRunLoop (which a wrapper of CFRunLoopRef)

Official doc

Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.

Single thread can have single RunLoop in a single mode. Only events with this mode will be processed all others will be waiting when RunLoop will be started at that mode

RunLoop is a mechanism (based on loop(for, while)) which move a scheduled task(e.g Callback Queue) to a thread(thread stack). RunLoop works(event processing loop) when Thread Stack is empty.

event processing loop is when RunLoop between .entry and .exit. During it RunLoop handles all scheduled task in specific mode. All others modes with their own Queues will be managed after

Application by default has a main thread with RunLoop(main loop). In other cases you should create it manually

main run loop is responsible for draining the main queue in an app.

//Run loop for the current thread
RunLoop.current

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

Mode

A run loop mode is a collection of input sources and timers to be monitored and a collection of run loop observers to be notified.

modes:

  • default - used by default
  • tracking - for example when you scroll UITableView scrollViewDidScroll
  • common(is a pseudo mode like [default, tracking])
  • <custom> - you are able to create your own mode
//current input mode
RunLoop.current.currentMode

For example:

  • UIView.draw(_ rect:), button action... uses default mode
  • NSObject.perform(_:with:afterDelay:) uses default mode
  • DispatchQueue.main.async uses common mode
  • Timer.scheduledTimer uses default mode. That is why when UI scrolling occurring(scrollViewDidScroll where tracking mode is used) your timer is not fired(in default mode). To fix it use common mode - RunLoop.main.add(timer, forMode: .common)
  • Combine RunLoop.main vs DispatchQueue.main(.receive(on:, options:)). RunLoop.main uses RunLoop.perform(_:) which uses default mode, DispatchQueue.main uses DispatchQueue.main.async which uses common mode

input sources and timers

Run loop receives events:

  • Input sources - asynchronous events(as fired) messages

    • Port-based - from another thread or process. signaled automatically by the kernel
    • Custom Input Sources - user-initiated events - user actions, network events. must be signaled manually from another thread
    • performSelector: onThread
  • Timer sources - synchronous events(at specific time) timers

They can be added to several modes

observers

monitor RunLoop's state changes

Create RunLoop

create new thread, setup RunLoop and start the thread

  1. create RunLoop RunLoop.current
  2. A run loop must have at least one input source or timer to monitor RunLoop.add(_ timer: Timer, forMode mode: RunLoop.Mode) RunLoop.add(_ aPort: Port, forMode mode: RunLoop.Mode)
  3. run RunLoop RunLoop.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
    }
}
Hanoi answered 22/1, 2023 at 21:58 Comment(0)
F
1

Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.

From here


The most important feature of CFRunLoop is the CFRunLoopModes. CFRunLoop works with a system of “Run Loop Sources”. Sources are registered on a run loop for one or several modes, and the run loop itself is made to run in a given mode. When an event arrives on a source, it is only handled by the run loop if the source mode matches the run loop current mode.

From here

Fugere answered 25/4, 2018 at 1:50 Comment(0)
C
0
Swift
let runLoop = RunLoop.current

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

A run loop is an event processing loop that is used to continuously monitor and process input events and assign them to the corresponding targets for processing.

Citizenship answered 20/7, 2021 at 1:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.