runloops and NSURLConnection

  • A bit puzzled ...seems like I need some advise here.

    From a NSWindowController I create a modal dialog

      [NSApp runModalForWindow:[self window]];

    On a button click I am trying to use NSURLConnection to do retrieve
    information in a asynchronous fashion

      connection = [[NSURLConnection alloc] initWithRequest:request
    delegate:self];

    Unfortunately this only works when I afterwards enter this runloop

        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
        while(!terminated) {
            if (![[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
    beforeDate:[NSDate dateWithTimeIntervalSinceNow:100000]]) {
                break;
            }
            [pool release];
            pool = [[NSAutoreleasePool alloc] init];
        }
        [pool release];

    (the connectionDidFinishLoading:/connection:didFailWithError:
    terminates the above loop)

    Now I am a bit surprised. Shouldn't runModalForWindow: do pretty much
    the same thing? The documentation states

    "By default, for the connection to work correctly the calling thread’s
    run loop must be operating in the default run loop mode. See
    scheduleInRunLoop:forMode: to change the runloop and mode."

    But I am a bit lost what to do with that information. I would assume
    that

      scheduleInRunLoop:[NSRunLoop currentRunLoop]
    forMode:NSDefaultRunLoopMode

    is the default anyway. Nevertheless I am targeting 10.4 and this is
    only available since 10.5.

    I found this response from Fraser

    http://lists.apple.com/archives/aperture-dev/2008/Feb/msg00036.html

    ...but I would like to understand the "why". Do I really have to spawn
    another thread for this? Or is it OK to process the run loop like
    shown above? I have the feeling I am somehow fighting the framework
    again here.

    Comments?

    cheers
    --
    Torsten
  • I think you want to schedule the connection for the
    NSModalPanelRunLoopMode runloop mode.  This is the mode that is used
    for modal windows.

    On May 31, 2008, at 7:28 AM, Torsten Curdt wrote:

    > A bit puzzled ...seems like I need some advise here.
    >
    > From a NSWindowController I create a modal dialog
    >
    > [NSApp runModalForWindow:[self window]];
    >
    > On a button click I am trying to use NSURLConnection to do retrieve
    > information in a asynchronous fashion
    >
    > connection = [[NSURLConnection alloc] initWithRequest:request
    > delegate:self];
    >
    > Unfortunately this only works when I afterwards enter this runloop
    >
    > NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    > while(!terminated) {
    > if (![[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
    > beforeDate:[NSDate dateWithTimeIntervalSinceNow:100000]]) {
    > break;
    > }
    > [pool release];
    > pool = [[NSAutoreleasePool alloc] init];
    > }
    > [pool release];
    >
    > (the connectionDidFinishLoading:/connection:didFailWithError:
    > terminates the above loop)
    >
    > Now I am a bit surprised. Shouldn't runModalForWindow: do pretty
    > much the same thing? The documentation states
    >
    > "By default, for the connection to work correctly the calling
    > thread’s run loop must be operating in the default run loop mode.
    > See scheduleInRunLoop:forMode: to change the runloop and mode."
    >
    > But I am a bit lost what to do with that information. I would assume
    > that
    >
    > scheduleInRunLoop:[NSRunLoop currentRunLoop]
    > forMode:NSDefaultRunLoopMode
    >
    > is the default anyway. Nevertheless I am targeting 10.4 and this is
    > only available since 10.5.
    >
    > I found this response from Fraser
    >
    > http://lists.apple.com/archives/aperture-dev/2008/Feb/msg00036.html
    >
    > ...but I would like to understand the "why". Do I really have to
    > spawn another thread for this? Or is it OK to process the run loop
    > like shown above? I have the feeling I am somehow fighting the
    > framework again here.
    >
    > Comments?
    >
    > cheers
    > --
    > Torsten
  • On May 31, 2008, at 16:09, Michael Vannorsdel wrote:

    > I think you want to schedule the connection for the
    > NSModalPanelRunLoopMode runloop mode.  This is the mode that is used
    > for modal windows.

    You mean with scheduleInRunLoop:forMode: ? ...but that's only
    available since 10.5

    cheers
    --
    Torsten
  • On 31 May '08, at 8:07 AM, Torsten Curdt wrote:

    >> I think you want to schedule the connection for the
    >> NSModalPanelRunLoopMode runloop mode.  This is the mode that is
    >> used for modal windows.
    >
    > You mean with scheduleInRunLoop:forMode: ? ...but that's only
    > available since 10.5

    I don't know of a good alternative that's 10.4-compatible, short of
    running the NSURLConnection on a background thread with its own runloop.

    But have you considered _not_ using a modal panel? IMHO, modal panels
    are a "UI smell", to coin a phrase. Does it really need to be
    impossible for the user to interact in any other way with the app
    while that panel is open? Usually a sheet will suffice, and sheets
    don't use a special runloop mode, so that would solve your problem.

    —Jens
  • I saw you using that in your included code so I thought you were ok
    with it.  For Tiger I can only suggest using secondary thread
    processing as the cleanest approach.  Or you can reconsider using a
    modal window.

    On May 31, 2008, at 9:07 AM, Torsten Curdt wrote:

    > You mean with scheduleInRunLoop:forMode: ? ...but that's only
    > available since 10.5
  • On May 31, 2008, at 18:55, Jens Alfke wrote:
    > On 31 May '08, at 8:07 AM, Torsten Curdt wrote:
    >
    >>> I think you want to schedule the connection for the
    >>> NSModalPanelRunLoopMode runloop mode.  This is the mode that is
    >>> used for modal windows.
    >>
    >> You mean with scheduleInRunLoop:forMode: ? ...but that's only
    >> available since 10.5
    >
    > I don't know of a good alternative that's 10.4-compatible, short of
    > running the NSURLConnection on a background thread with its own
    > runloop.

    OK ..so at least it was a real shortcoming and not me fighting the
    framework again.

    What about my current solution with the run loop? Does it smell really
    bad ...or is the workaround OK as well?

    > But have you considered _not_ using a modal panel? IMHO, modal
    > panels are a "UI smell", to coin a phrase. Does it really need to be
    > impossible for the user to interact in any other way with the app
    > while that panel is open? Usually a sheet will suffice, and sheets
    > don't use a special runloop mode, so that would solve your problem.

    Indeed - modal is smelly  ...and I have considered non-modal. It would
    make my life easier. But since I have just integrated your exception
    handling into the FeedbackReporter framework ...wouldn't you expect
    the dialog that pops up in case of an uncaught exception to be modal?

    cheers
    --
    Torsten
  • On 31 May '08, at 10:14 AM, Torsten Curdt wrote:

    > Indeed - modal is smelly  ...and I have considered non-modal. It
    > would make my life easier. But since I have just integrated your
    > exception handling into the FeedbackReporter framework ...wouldn't
    > you expect the dialog that pops up in case of an uncaught exception
    > to be modal?

    I don't think it should be modal. When it comes up as a modal panel,
    it's forcing the user to decide between (a) filling out a bug report
    right now, or (b) continuing with his work. Most people would choose
    (b), and by dismissing the panel the information in it is lost.

    If you make it modeless, then the user can move the exception-alert
    window aside for the time being, finish the task at hand, and then
    report the bug a minute later.

    I only made it a modal alert in my sample code modal because it's
    easier to call NSAlert than to make a new nib. :)

    —Jens
  • Le 31 mai 08 à 19:14, Torsten Curdt a écrit :

    > On May 31, 2008, at 18:55, Jens Alfke wrote:
    >> On 31 May '08, at 8:07 AM, Torsten Curdt wrote:
    >>
    >>>> I think you want to schedule the connection for the
    >>>> NSModalPanelRunLoopMode runloop mode.  This is the mode that is
    >>>> used for modal windows.
    >>>
    >>> You mean with scheduleInRunLoop:forMode: ? ...but that's only
    >>> available since 10.5
    >>
    >> I don't know of a good alternative that's 10.4-compatible, short of
    >> running the NSURLConnection on a background thread with its own
    >> runloop.
    >
    > OK ..so at least it was a real shortcoming and not me fighting the
    > framework again.
    >
    > What about my current solution with the run loop? Does it smell
    > really bad ...or is the workaround OK as well?
    >
    >> But have you considered _not_ using a modal panel? IMHO, modal
    >> panels are a "UI smell", to coin a phrase. Does it really need to
    >> be impossible for the user to interact in any other way with the
    >> app while that panel is open? Usually a sheet will suffice, and
    >> sheets don't use a special runloop mode, so that would solve your
    >> problem.
    >
    > Indeed - modal is smelly  ...and I have considered non-modal. It
    > would make my life easier. But since I have just integrated your
    > exception handling into the FeedbackReporter framework ...wouldn't
    > you expect the dialog that pops up in case of an uncaught exception
    > to be modal?
    >
    > cheers
    > --
    > Torsten

    If you handle the modal session yourself, it's easy to let the
    connection do some processing while the modal window is open:
    Replace -[NSApp runModalForWindow:[self window]] by this and it should
    do the trick.

        /* Create a modal session, and in each loop,
        we process the default runloop event sources (url download,
    network connections, etc.) */
        NSModalSession session = [NSApp beginModalSessionForWindow:[self
    window]];
        for (;;) {
          if ((result = [NSApp runModalSession:session]) !=
    NSRunContinuesResponse)
            break;
          /* Note: Do not use a 0 timeout, else this loop will never
    block and will consume a lots of CPU.
            In fact, the Cocoa UI event port is scheduled in the default
    runloop, so each Cocoa event will wakeup the runloop. */
          [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
        }
        [NSApp endModalSession:session];
  • > If you handle the modal session yourself, it's easy to let the
    > connection do some processing while the modal window is open:
    > Replace -[NSApp runModalForWindow:[self window]] by this and it
    > should do the trick.
    >
    > /* Create a modal session, and in each loop,
    > we process the default runloop event sources (url download,
    > network connections, etc.) */
    > NSModalSession session = [NSApp beginModalSessionForWindow:[self
    > window]];
    > for (;;) {
    > if ((result = [NSApp runModalSession:session]) !=
    > NSRunContinuesResponse)
    > break;
    > /* Note: Do not use a 0 timeout, else this loop will never
    > block and will consume a lots of CPU.
    > In fact, the Cocoa UI event port is scheduled in the default
    > runloop, so each Cocoa event will wakeup the runloop. */
    > [[NSRunLoop currentRunLoop] runUntilDate:[NSDate
    > distantFuture]];
    > }
    > [NSApp endModalSession:session];

    So that basically means I create my own run loop and call out into the
    non-modal one from time to time, right?
    Just wondering about the "from time to time" part.

    cheers
    --
    Torsten
  • On May 31, 2008, at 19:54, Jens Alfke wrote:

    >
    > On 31 May '08, at 10:14 AM, Torsten Curdt wrote:
    >
    >> Indeed - modal is smelly  ...and I have considered non-modal. It
    >> would make my life easier. But since I have just integrated your
    >> exception handling into the FeedbackReporter framework ...wouldn't
    >> you expect the dialog that pops up in case of an uncaught exception
    >> to be modal?
    >
    > I don't think it should be modal. When it comes up as a modal panel,
    > it's forcing the user to decide between (a) filling out a bug report
    > right now, or (b) continuing with his work. Most people would choose
    > (b), and by dismissing the panel the information in it is lost.

    Indeed a good point. I think I am convinced :)
    Will change that in the next release.

    cheers
    --
    Torsten
  • Le 1 juin 08 à 14:27, Torsten Curdt a écrit :

    >> If you handle the modal session yourself, it's easy to let the
    >> connection do some processing while the modal window is open:
    >> Replace -[NSApp runModalForWindow:[self window]] by this and it
    >> should do the trick.
    >>
    >> /* Create a modal session, and in each loop,
    >> we process the default runloop event sources (url download,
    >> network connections, etc.) */
    >> NSModalSession session = [NSApp beginModalSessionForWindow:[self
    >> window]]; // 1
    >> for (;;) {
    >> if ((result = [NSApp runModalSession:session]) !=
    >> NSRunContinuesResponse) // 2
    >> break; // 3
    >> /* Note: Do not use a 0 timeout, else this loop will never
    >> block and will consume a lots of CPU.
    >> In fact, the Cocoa UI event port is scheduled in the default
    >> runloop, so each Cocoa event will wakeup the runloop. */

           [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
    beforeDate:[NSDate distantFuture]];

    >> //[[NSRunLoop currentRunLoop] runUntilDate:[NSDate
    >> distantFuture]]; // 4
    >> }
    >> [NSApp endModalSession:session];
    >
    > So that basically means I create my own run loop and call out into
    > the non-modal one from time to time, right?
    > Just wondering about the "from time to time" part.
    >
    > cheers
    > --
    > Torsten
    >

    Argh, This code does not works (there is no launch and test button in
    Mail :-)).
    You have to call [[NSRunLoop currentRunLoop]
    runMode:NSDefaultRunLoopMode  beforeDate:[NSDate distantFuture]];
    else it will never returns.

    You never create a runloop. Each thread has a "current loop" which
    support recursive call to "run".

    This snippet create a modal session.  (//1)
    Then it check the modal session state and process UI events if some
    events are ready ([NSApp runModalSession:session]) (//2)
    Note that runModalSession: does not block and always returns immediatly.

    If the user stop the modal session, it stop the loop (//3)

    After that it blocks until something append (network event, ui event,
    etc). (//4)
    Each time a network event occurs, the run loop pass it to the handler
    (NSURLConnection in your case).
    Each time a UI event occurs, the run loop push it on the UI event
    queue and the next call the runModalSession: process it.
    Each time an event occurs, runMode:beforeDate: returns.
previous month may 2008 next month
MTWTFSS
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  
Go to today