iOS app launching speed

  • Was just wondering if there are any techniques to speed up the launching of iOS apps.

    Back in the Director day, I would create a shell executable that would launch rather quickly and present a splash screen, then an animation that indicated to the user that the test of the application was loading. After that, the shell read a config file, loaded the rest of the app and started up.

    This approach got feedback to the user as quick as possible and provided the impression of a quick launching responsive app, right from the get go.

    in the case that our apps start launching sluggishly (I've seen this during development sometimes) are there any approaches to take along this vein?

    Thanks in advance,
    - Alex Zavatone
  • "Lazy loading" is the usual answer. Don't load a resource until the user needs it.

    View controllers are built this way. "viewDidLoad"  (or loadView if you prefer) should summon the resources and build the view.

    Look around for other opportunities to delay initialization.

    David

    On May 15, 2012, at 8:05 AM, Alex Zavatone wrote:

    > Was just wondering if there are any techniques to speed up the launching of iOS apps.
    >
    > Back in the Director day, I would create a shell executable that would launch rather quickly and present a splash screen, then an animation that indicated to the user that the test of the application was loading. After that, the shell read a config file, loaded the rest of the app and started up.
    >
    > This approach got feedback to the user as quick as possible and provided the impression of a quick launching responsive app, right from the get go.
    >
    > in the case that our apps start launching sluggishly (I've seen this during development sometimes) are there any approaches to take along this vein?
    >
    > Thanks in advance,
    > - Alex Zavatone
  • In my experience, this is usually localized to whatever you are doing in applicationDidFinishLaunching and the complexity of the main nib that is being loaded (and it's associated data, like graphics). Usually, getting your main nib down to just what you need and loading the rest later goes a long way towards speeding up your app.

    As a benchmark, you can make a new iOS project in Xcode, run it on the device, and compare it's launch time to your app.

    Dave

    On May 15, 2012, at 8:05 AM, Alex Zavatone wrote:

    > Was just wondering if there are any techniques to speed up the launching of iOS apps.
    >
    > Back in the Director day, I would create a shell executable that would launch rather quickly and present a splash screen, then an animation that indicated to the user that the test of the application was loading. After that, the shell read a config file, loaded the rest of the app and started up.
    >
    > This approach got feedback to the user as quick as possible and provided the impression of a quick launching responsive app, right from the get go.
    >
    > in the case that our apps start launching sluggishly (I've seen this during development sometimes) are there any approaches to take along this vein?
    >
    > Thanks in advance,
    > - Alex Zavatone
  • On 15 May 2012, at 10:05 AM, Alex Zavatone wrote:

    > Was just wondering if there are any techniques to speed up the launching of iOS apps.
    >
    > Back in the Director day, I would create a shell executable that would launch rather quickly and present a splash screen, then an animation that indicated to the user that the test of the application was loading. After that, the shell read a config file, loaded the rest of the app and started up.
    >
    > This approach got feedback to the user as quick as possible and provided the impression of a quick launching responsive app, right from the get go.
    >
    > in the case that our apps start launching sluggishly (I've seen this during development sometimes) are there any approaches to take along this vein?

    Only some obvious things. You know them already, so maybe all you'll learn from me is that iOS doesn't have a "run more fast" toggle…

    1. It's not a splash screen.

    Using the recommended default PNGs — screenshots without localizable content — gives the impression that the app's functionality is coming online. "Splash screens" give users the impression that you are delaying their access to the application so you can show them advertising. Small things can infuriate people.

    2. Make the application faster.

    The app doesn't have to complete its initialization quickly — it only has to show that it's running.

    a) Profile the startup

    Use the Time Profiler instrument to find performance hotspots. Do this _on the phone_; the simulator is too fast and has too much memory to provide an honest profile. (Much of the startup delay in the simulator is due to debugger gymnastics.) If you load data during your startup, use an absurdly large dataset (embedding it in the app for testing purposes, if that's convenient). That will catch scaling issues.

    b) Do all the initialization you can on a background thread.

    Something I learned from an app I did for teaching purposes. It used Core Data internally, but at startup it loaded the CD store from a large raw-data file. The particular example probably not useful to you, but it illustrates the larger point.

    My profiling showed me that some Core Data operations were irreducible. I put initialization in a background thread, on a thread-specific MOC. My main thread observed the update notifications from the background thread, and updated its own MOC accordingly. Objects became available to the user as soon as they arrived in the main database — with an NSFetchedResultsController, it looked pretty cool. Even if you have to block access until the whole dataset is loaded, the constantly-updating tables demonstrate that the app is doing something.

    Obviously, storing the data in a Core Data file in the first place would have saved this trouble, but the general point is that if you have something time-consuming to do, put it into the background and call back into the main thread to update the UI as it progresses.

    As a bonus, keeping the main thread free prevents the watchdog timer from killing your app after 20 seconds. (The watchdog won't kill you while debugging or profiling, because the development tools allow you unlimited time to debug the whole process.)

    c) Proceed regardless

    If you have a startup subprocess that is inherently time-consuming (Core Location, and ANY networking), use the asynchronous API, and make your UI show the resulting degradation in functionality gracefully (dimmed controls, overlaid progress/activity indicators). NEVER use the synchronous networking API, nor the Foundation -…WithContentsOfURL: methods (for schemes other than file:). Timeouts will be on the order of 30 seconds, and to repeat, the watchdog allows you only 20.

    d) Consider not using Core Data at all.

    Do you need complex indexes and searches, extensive object-relational links, or faulting large numbers of records to storage? If not, maybe a tree of NSArrays, NSDictionaries, NSSortDescriptors, and NSPredicates will work better for you.

    For smaller, less complex data sets, they're faster. How much faster, and for how many records, depends on exactly what you're doing, but I'd guess that the line could be at least N=2000.

    e) Do what you can just-in-time.

    If your architecture is master/detail, consider whether building detail data sets one at a time on demand would be less annoying than taking time out of startup to build N sets all at once.

    — F

    --
    Fritz Anderson -- Xcode 4 Unleashed: Coming 21 May 2012 -- <http://x4u.manoverboard.org/>
  • Very enlightening Fritz.  Thanks.

    One approach I took on purpose was to use a synchronous load of a JSON data set on startup.

    I didn't want to display the main screen (TVC), until the main data had loaded (a 4 KB data set) and serialized.

    I'm wondering if with a larger data set, the approach should be to have the first scene use the same graphic as the launch screen, fire off an async http load (through GDC or which preferred method?), display a little spinner if the delay takes longer then 1.25 seconds, then segue to the TVC with data when it loads serializes and plops itself into my data singleton.

    Does that sound like a sane approach, overengineering or completely off base?

    Thanks again.
    - Alex Zavatone.

    On May 15, 2012, at 09:20 AM, Fritz Anderson <fritza...> wrote:

    On 15 May 2012, at 10:05 AM, Alex Zavatone wrote:

    > Was just wondering if there are any techniques to speed up the launching of iOS apps.
    >
    > Back in the Director day, I would create a shell executable that would launch rather quickly and present a splash screen, then an animation that indicated to the user that the test of the application was loading. After that, the shell read a config file, loaded the rest of the app and started up.
    >
    > This approach got feedback to the user as quick as possible and provided the impression of a quick launching responsive app, right from the get go.
    >
    > in the case that our apps start launching sluggishly (I've seen this during development sometimes) are there any approaches to take along this vein?

    Only some obvious things. You know them already, so maybe all you'll learn from me is that iOS doesn't have a "run more fast" toggle…

    1. It's not a splash screen.

    Using the recommended default PNGs — screenshots without localizable content — gives the impression that the app's functionality is coming online. "Splash screens" give users the impression that you are delaying their access to the application so you can show them advertising. Small things can infuriate people.

    2. Make the application faster.

    The app doesn't have to complete its initialization quickly — it only has to show that it's running.

    a) Profile the startup

    Use the Time Profiler instrument to find performance hotspots. Do this _on the phone_; the simulator is too fast and has too much memory to provide an honest profile. (Much of the startup delay in the simulator is due to debugger gymnastics.) If you load data during your startup, use an absurdly large dataset (embedding it in the app for testing purposes, if that's convenient). That will catch scaling issues.

    b) Do all the initialization you can on a background thread.

    Something I learned from an app I did for teaching purposes. It used Core Data internally, but at startup it loaded the CD store from a large raw-data file. The particular example probably not useful to you, but it illustrates the larger point.

    My profiling showed me that some Core Data operations were irreducible. I put initialization in a background thread, on a thread-specific MOC. My main thread observed the update notifications from the background thread, and updated its own MOC accordingly. Objects became available to the user as soon as they arrived in the main database — with an NSFetchedResultsController, it looked pretty cool. Even if you have to block access until the whole dataset is loaded, the constantly-updating tables demonstrate that the app is doing something.

    Obviously, storing the data in a Core Data file in the first place would have saved this trouble, but the general point is that if you have something time-consuming to do, put it into the background and call back into the main thread to update the UI as it progresses.

    As a bonus, keeping the main thread free prevents the watchdog timer from killing your app after 20 seconds. (The watchdog won't kill you while debugging or profiling, because the development tools allow you unlimited time to debug the whole process.)

    c) Proceed regardless

    If you have a startup subprocess that is inherently time-consuming (Core Location, and ANY networking), use the asynchronous API, and make your UI show the resulting degradation in functionality gracefully (dimmed controls, overlaid progress/activity indicators). NEVER use the synchronous networking API, nor the Foundation -…WithContentsOfURL: methods (for schemes other than file:). Timeouts will be on the order of 30 seconds, and to repeat, the watchdog allows you only 20.

    d) Consider not using Core Data at all.

    Do you need complex indexes and searches, extensive object-relational links, or faulting large numbers of records to storage? If not, maybe a tree of NSArrays, NSDictionaries, NSSortDescriptors, and NSPredicates will work better for you.

    For smaller, less complex data sets, they're faster. How much faster, and for how many records, depends on exactly what you're doing, but I'd guess that the line could be at least N=2000.

    e) Do what you can just-in-time.

    If your architecture is master/detail, consider whether building detail data sets one at a time on demand would be less annoying than taking time out of startup to build N sets all at once.

    — F

    --
    Fritz Anderson -- Xcode 4 Unleashed: Coming 21 May 2012 -- <http://x4u.manoverboard.org/>
  • Responses below.

    (Sent from my iPad.)

    --
    Conrad Shultz

    On May 15, 2012, at 9:48, Alex Zavatone <zav...> wrote:

    > Very enlightening Fritz.  Thanks.
    >
    > One approach I took on purpose was to use a synchronous load of a JSON data set on startup.
    >
    Given your statement below I interpret this to mean that you are doing a synchronous download, not just synchronous deserialization. Never, ever do synchronous network I/O on the main thread - there is no guarantee that it will EVER complete. This applies throughout, not just at launch.

    > I didn't want to display the main screen (TVC), until the main data had loaded (a 4 KB data set) and serialized.
    >
    Why? This directly contravenes the Human Interface Guidelines for the reasons Fritz indicated. You should display a non-localized (i.e. text-free) image of a blank TVC if that's the first screen.

    > I'm wondering if with a larger data set, the approach should be to have the first scene use the same graphic as the launch screen, fire off an async http load (through GDC or which preferred method?), display a little spinner if the delay takes longer then 1.25 seconds, then segue to the TVC with data when it loads serializes and plops itself into my data singleton.
    >
    Well, first, as discussed, don't use a launch/splash screen.

    What occurs to me is that you really only need wait until you have a screen of data. I'm not sure whether your data model can be easily broken into chunks, but the best approach would probably be to initially fetch only the data for the first, say, 10 rows. Once processed you would display them and start loading the remaining data in the background. That way, if the user doesn't scroll down too quickly they will have the illusion of the whole data set having loaded immediately.

    If they do scroll fast enough to hit unavailable rows you just display a placeholder until the real data can be swapped into place.

    > Does that sound like a sane approach, overengineering or completely off base?

    Responsiveness is one of the most important usability traits, particularly on a mobile device. I'd say you're nowhere near the point of overengineering so long as your goal is responsiveness.
    >
    > Thanks again.
    > - Alex Zavatone.
    >
    > On May 15, 2012, at 09:20 AM, Fritz Anderson <fritza...> wrote:
    >
    > On 15 May 2012, at 10:05 AM, Alex Zavatone wrote:
    >
    >> Was just wondering if there are any techniques to speed up the launching of iOS apps.
    >> Back in the Director day, I would create a shell executable that would launch rather quickly and present a splash screen, then an animation that indicated to the user that the test of the application was loading. After that, the shell read a config file, loaded the rest of the app and started up.
    >> This approach got feedback to the user as quick as possible and provided the impression of a quick launching responsive app, right from the get go.
    >> in the case that our apps start launching sluggishly (I've seen this during development sometimes) are there any approaches to take along this vein?
    >
    > Only some obvious things. You know them already, so maybe all you'll learn from me is that iOS doesn't have a "run more fast" toggle…
    >
    > 1. It's not a splash screen.
    >
    > Using the recommended default PNGs — screenshots without localizable content — gives the impression that the app's functionality is coming online. "Splash screens" give users the impression that you are delaying their access to the application so you can show them advertising. Small things can infuriate people.
    >
    > 2. Make the application faster.
    >
    > The app doesn't have to complete its initialization quickly — it only has to show that it's running.
    >
    > a) Profile the startup
    >
    > Use the Time Profiler instrument to find performance hotspots. Do this _on the phone_; the simulator is too fast and has too much memory to provide an honest profile. (Much of the startup delay in the simulator is due to debugger gymnastics.) If you load data during your startup, use an absurdly large dataset (embedding it in the app for testing purposes, if that's convenient). That will catch scaling issues.
    >
    > b) Do all the initialization you can on a background thread.
    >
    > Something I learned from an app I did for teaching purposes. It used Core Data internally, but at startup it loaded the CD store from a large raw-data file. The particular example probably not useful to you, but it illustrates the larger point.
    >
    > My profiling showed me that some Core Data operations were irreducible. I put initialization in a background thread, on a thread-specific MOC. My main thread observed the update notifications from the background thread, and updated its own MOC accordingly. Objects became available to the user as soon as they arrived in the main database — with an NSFetchedResultsController, it looked pretty cool. Even if you have to block access until the whole dataset is loaded, the constantly-updating tables demonstrate that the app is doing something.
    > Obviously, storing the data in a Core Data file in the first place would have saved this trouble, but the general point is that if you have something time-consuming to do, put it into the background and call back into the main thread to update the UI as it progresses.
    > As a bonus, keeping the main thread free prevents the watchdog timer from killing your app after 20 seconds. (The watchdog won't kill you while debugging or profiling, because the development tools allow you unlimited time to debug the whole process.)
    >
    > c) Proceed regardless
    >
    > If you have a startup subprocess that is inherently time-consuming (Core Location, and ANY networking), use the asynchronous API, and make your UI show the resulting degradation in functionality gracefully (dimmed controls, overlaid progress/activity indicators). NEVER use the synchronous networking API, nor the Foundation -…WithContentsOfURL: methods (for schemes other than file:). Timeouts will be on the order of 30 seconds, and to repeat, the watchdog allows you only 20.
    >
    > d) Consider not using Core Data at all.
    >
    > Do you need complex indexes and searches, extensive object-relational links, or faulting large numbers of records to storage? If not, maybe a tree of NSArrays, NSDictionaries, NSSortDescriptors, and NSPredicates will work better for you.
    > For smaller, less complex data sets, they're faster. How much faster, and for how many records, depends on exactly what you're doing, but I'd guess that the line could be at least N=2000.
    >
    > e) Do what you can just-in-time.
    >
    > If your architecture is master/detail, consider whether building detail data sets one at a time on demand would be less annoying than taking time out of startup to build N sets all at once.
    >
    >
    > — F
    >
    > --
    > Fritz Anderson -- Xcode 4 Unleashed: Coming 21 May 2012 -- <http://x4u.manoverboard.org/
  • On May 15, 2012, at 9:48 AM, Alex Zavatone wrote:

    > I didn't want to display the main screen (TVC), until the main data had loaded (a 4 KB data set) and serialized.
    > I'm wondering if with a larger data set, the approach should be to have the first scene use the same graphic as the launch screen, fire off an async http load (through GDC or which preferred method?),

    NSURLConnection. Use the async API (with delegate).

    > display a little spinner if the delay takes longer then 1.25 seconds,

    Why not cache the data from the previous run and load that from local storage at launch? It’ll be super fast. Then at the same time you also fire off an async HTTP request, and when that response comes back you can update the UI if it has different data. (As an optimization, make it a conditional GET by adding an If-None-Match: or If-Modified-Since: header so the server only has to send you the data if it’s changed from what you’ve already got.)

    This way you also get offline support — the app will work properly if there’s no network, instead of just spinning forever.

    Native apps are different from web apps, and one big benefit is that they don’t need to be hamstrung by network performance. So it’s important to make them work well with slow or no network; otherwise you might as well just write a web app.

    —Jens
  • On 15 May 2012, at 11:48 AM, Alex Zavatone wrote:

    > async http load (through GDC or which preferred method?)

    Until proven otherwise, Grand Central Dispatch is a bad choice for network operations. Look at the documentation for NSURLConnection (and the URL Loading System Programming Guide) for an asynchronous loading system that never requires you to think about threading. Once you've used it a couple of times you'll be able to do it in your sleep.

    — F
  • On May 15, 2012, at 1:16 PM, Conrad Shultz wrote:

    > Responses below.
    >
    > (Sent from my iPad.)
    >
    > --
    > Conrad Shultz
    >
    > On May 15, 2012, at 9:48, Alex Zavatone <zav...> wrote:
    >
    >> Very enlightening Fritz.  Thanks.
    >>
    >> One approach I took on purpose was to use a synchronous load of a JSON data set on startup.
    >>
    > Given your statement below I interpret this to mean that you are doing a synchronous download, not just synchronous deserialization. Never, ever do synchronous network I/O on the main thread - there is no guarantee that it will EVER complete. This applies throughout, not just at launch.

    Yes, and I've tested it and we haven't had a problem with this.  I trust your advice, yet all I can see is that if the data transmission fails mid 4KB data transfer, it would hang.  In the case where the URL is invalid or unreachable, we have a dict to read from on the device.

    In any case, your advice seems perfect to add to the task list when we update the app.

    >> I didn't want to display the main screen (TVC), until the main data had loaded (a 4 KB data set) and serialized.
    >>
    > Why? This directly contravenes the Human Interface Guidelines for the reasons Fritz indicated. You should display a non-localized (i.e. text-free) image of a blank TVC if that's the first screen.

    Well, the Apple HUI guidelines have been taking some strange directions recently on the Mac end of things.  I'm sticking with what I know will provide a tried and true expected (good) user experience.

    I'm doing it because it's simply lame to display an empty TVC and then have it fill in as content appears like an HTML page.

    If I have the option to display the complete data in the TVC view, I'm going to opt over that.

    >> I'm wondering if with a larger data set, the approach should be to have the first scene use the same graphic as the launch screen, fire off an async http load (through GDC or which preferred method?), display a little spinner if the delay takes longer then 1.25 seconds, then segue to the TVC with data when it loads serializes and plops itself into my data singleton.
    >>
    > Well, first, as discussed, don't use a launch/splash screen.

    I'm missing your point.  Are you saying don't use a graphic (Default.png) when the app launches or the approach I used back last century with Director?

    > What occurs to me is that you really only need wait until you have a screen of data. I'm not sure whether your data model can be easily broken into chunks, but the best approach would probably be to initially fetch only the data for the first, say, 10 rows. Once processed you would display them and start loading the remaining data in the background. That way, if the user doesn't scroll down too quickly they will have the illusion of the whole data set having loaded immediately.
    >
    > If they do scroll fast enough to hit unavailable rows you just display a placeholder until the real data can be swapped into place.
    >
    >> Does that sound like a sane approach, overengineering or completely off base?
    >
    > Responsiveness is one of the most important usability traits, particularly on a mobile device. I'd say you're nowhere near the point of overengineering so long as your goal is responsiveness.

    Thanks much.  I'm looking forwards to our next projects that have insane amounts of data for a mobile experience.

    Cheers,
    - Alex Zavatone

    defaults write -g NSDisableAutomaticTermination -bool yes
    defaults write -g NSScrollViewRubberbanding -bool no
  • On May 15, 2012, at 11:18 AM, Alex Zavatone wrote:

    > Yes, and I've tested it and we haven't had a problem with this.  I trust your advice, yet all I can see is that if the data transmission fails mid 4KB data transfer, it would hang.  In the case where the URL is invalid or unreachable, we have a dict to read from on the device.

    It's a network. You are going to fail during transfer. Often.

    >
    > Well, the Apple HUI guidelines have been taking some strange directions recently on the Mac end of things.  I'm sticking with what I know will provide a tried and true expected (good) user experience.

    Prepare for app rejection.

    >
    > I'm doing it because it's simply lame to display an empty TVC and then have it fill in as content appears like an HTML page.

    You say "lame". People with more experience than you, and everyone else on the platform, say "expected."

    >
    > If I have the option to display the complete data in the TVC view, I'm going to opt over that.

    Prepare for app rejection.

    >
    > I'm missing your point.  Are you saying don't use a graphic (Default.png) when the app launches or the approach I used back last century with Director?

    You clearly understood what he meant. Make your Default.png depict an empty, text-less tableview.

    --Kyle Sluder
  • On 5/15/12 11:18 AM, Alex Zavatone wrote:
    > Yes, and I've tested it and we haven't had a problem with this.  I
    > trust your advice, yet all I can see is that if the data transmission
    > fails mid 4KB data transfer, it would hang.  In the case where the
    > URL is invalid or unreachable, we have a dict to read from on the
    > device.

    In the "real world" things never behave normally.  Here are just a few
    issues that can (and by Murphy's Law, will) come up but won't trigger a
    validity/reachability failure:

    - The network is reachable but DNS is slow or unavailable.
    - A caching DNS server has a stale record, directing traffic to a wrong
    machine whose firewall silently drops packets on the floor (i.e. no
    reject message sent).
    - The client is running inside a captive network that is not detected by
    iOS.  (This happens surprisingly often, as far as I can tell in cases
    where the captive network passes ICMP traffic but not TCP/UDP.)
    - As you indicated, data transmission fails mid-transfer.  This could
    happen for a reason as simple as walking into a basement or tunnel.  (I
    recall once standing at a construction site, watching in horror as a
    backhoe drove right through a fiber bundle, instantly killing all my
    connections.)  Of course, until a TCP timeout is reached, this is
    indistinguishable from a slow connection.  (Cf. the Two Generals' Problem.)

    You really can't assume that a network connection will ever complete in
    any finite period.  In fact, you have to assume that any given
    connection will take an indeterminate (worst-case: infinite) amount of time.

    > Well, the Apple HUI guidelines have been taking some strange
    > directions recently on the Mac end of things.  I'm sticking with what
    > I know will provide a tried and true expected (good) user experience.

    I disagree that splash screens are a good user experience.

    > I'm doing it because it's simply lame to display an empty TVC and
    > then have it fill in as content appears like an HTML page.

    I don't know what you mean by "lame."  That guidance is because it
    *appears* to the user to be faster when you display the empty TVC.

    There is an even more important reason not to just throw in a splash
    screen as Default.png, though.  As the devices get faster (and your code
    more efficient), Default.png will be on the screen for a shorter period
    of time.  Eventually you will get the jarring experience of a splash
    screen flashing for a moment before the UI draws.  This is a terrible,
    strobe-like user experience.

    The options then are two-fold:

    1) Present a UIImageView of Default.png in
    -applicationDidFinishLaunchingWithOptions:.  This allows you to
    subsequently do a controlled presentation of the UI without a strobe
    effect, but irritates users because you are now actively impeding their
    ability to actually use the app.

    2) Drop the splash screen and replace it with an "empty" (non-localized)
    UI image.

    I think of this as analogous to watching movies.  One reason that
    watching movies on iTunes is more pleasant than DVD or Blu-ray is that I
    don't have to suffer through Hollywood's version of a splash screen:
    Copyright notices.

    > I'm missing your point.  Are you saying don't use a graphic
    > (Default.png) when the app launches or the approach I used back last
    > century with Director?

    I'm not familiar with Director.

    I'm saying: grab a screenshot of the initial screen and edit out any
    elements that will vary by locale (e.g. text, dynamically sized
    controls, etc.) and use that.  Alternatively, create a xib that matches
    your UI but without any such localized elements, and use a screenshot of
    that xib.

    The latter requires maintaining two xibs, though, so I usually just go
    the Acorn (or Photoshop, or Pixelmator, pick your poison) route.

    --
    Conrad Shultz

    Synthetiq Solutions
    www.synthetiqsolutions.com
  • - Zav

    On May 15, 2012, at 10:44 AM, Jens Alfke <jens...> wrote:

    On May 15, 2012, at 9:48 AM, Alex Zavatone wrote:

    I didn't want to display the main screen (TVC), until the main data had loaded (a 4 KB data set) and serialized.
    I'm wondering if with a larger data set, the approach should be to have the first scene use the same graphic as the launch screen, fire off an async http load (through GDC or which preferred method?),

    NSURLConnection. Use the async API (with delegate).

    display a little spinner if the delay takes longer then 1.25 seconds,

    Why not cache the data from the previous run and load that from local storage at launch? It’ll be super fast. Then at the same time you also fire off an async HTTP request, and when that response comes back you can update the UI if it has different data. (As an optimization, make it a conditional GET by adding an If-None-Match: or If-Modified-Since: header so the server only has to send you the data if it’s changed from what you’ve already got.)
     
    We are caching (saving in a pList) the data from the previous launch.  It falls through and uses the previously saved data if we can't get to the internet.  Now, what I'm unclear on at this point (I've never truly truly trusted some system to cache stuff for me) is how the system knows if the data online is newer than the data we've got.  I trust that the caching method you're mentioning is different than saving to a pList?

    Maybe an overview is in order.

    Start up
    Fetch list of contents JSON & serialize & save to device as pList
    If we fail, load from pList if present

    Display lovely data in TVC

    User selects one of those items in the TVC cells
      Load 3 sets of data based on item selected ala 2 JSON files and PNGs & save as pLists and PNGs
      If we fail, load from pList and stored PNGs if present

      User can then hop around the app and enjoy the lovely data.

    If user goes back to the Start TVC and selects another cell, the above process proceeds relative to the new item selected.


    Start data is 4KB.  Optimized (sans spaces and tabs), 3 KB.  Zippedy fast to load and display.
    Secondary data is 33 KB, 17 KB optimized (tabs, spaces and returns removed).
    Images are between 80 and 460 KB.

    This way you also get offline support — the app will work properly if there’s no network, instead of just spinning forever.
     
    Yeah, in my current approach, as long as the data is loaded once, it's there in the app's documents folder and can be loaded if the user has no internet connection or the file can't be found.

    Native apps are different from web apps, and one big benefit is that they don’t need to be hamstrung by network performance. So it’s important to make them work well with slow or no network; otherwise you might as well just write a web app.
     
    Oh, god no.  That's just a horrific thought.  One key concept in my approach was to make sure the data is loaded before the user needs it.  Remember, I come from ye olden world of Director and Shockwave (served a 4 year sentence at Macromedia), when we could preload data, write our own async foundation classes and make sure that data was there before it's time to draw the screen, then draw the whole screen at once.  None of this HTMLy let stuff come in piece by piece, which is visually ugly.

    Thanks much.

    —Jens
  • OK.  I've got one item to discuss with you, the splash/launch screen.

    This is why I like it.

    Right after clicking on an app's icon, the splash screen/launch screen shows that the device has indeed paid attention to you and responded in the manner you expected, namely, the application has launched and is proceeding to load.

    I can not see how this is a bad idea.

    Now, after this appears, there is a reasonable time delay when the user expects some more feedback.  If the user has no reaction, no "when will this thing finish launching" thought, you're home free.  Users more likely pay attention to when something is wrong, than when it is good or great.  By eliminating all the points where a user would express a thought of "this sucks" you're left with something that meets their expectations, that is at least good enough.  This gets us to good.  That's what I'm trying to lay the groundwork for.

    There are barely conscious thoughts that happen if you break with the user's expectations for the worse, that put a moment of displeasure in the user's experience of using the app.  Identify and eliminate all of these that you can.  This makes a pleasant app.

    It's by identifying and removing flaws that you enhance an app's usability.  Ideally, the user would have no moments of "Meh, that sucks", or "why is this taking so loooong", so the user can get their task done without undue grief or inconvenience.
    Or at lease those are my thoughts on the process.
    - Alex Zavatone.

    On May 15, 2012, at 12:22 PM, Conrad Shultz <conrad...> wrote:

    On 5/15/12 11:18 AM, Alex Zavatone wrote:
    > Yes, and I've tested it and we haven't had a problem with this. I
    > trust your advice, yet all I can see is that if the data transmission
    > fails mid 4KB data transfer, it would hang. In the case where the
    > URL is invalid or unreachable, we have a dict to read from on the
    > device.

    In the "real world" things never behave normally. Here are just a few
    issues that can (and by Murphy's Law, will) come up but won't trigger a
    validity/reachability failure:

    - The network is reachable but DNS is slow or unavailable.
    - A caching DNS server has a stale record, directing traffic to a wrong
    machine whose firewall silently drops packets on the floor (i.e. no
    reject message sent).
    - The client is running inside a captive network that is not detected by
    iOS. (This happens surprisingly often, as far as I can tell in cases
    where the captive network passes ICMP traffic but not TCP/UDP.)
    - As you indicated, data transmission fails mid-transfer. This could
    happen for a reason as simple as walking into a basement or tunnel. (I
    recall once standing at a construction site, watching in horror as a
    backhoe drove right through a fiber bundle, instantly killing all my
    connections.) Of course, until a TCP timeout is reached, this is
    indistinguishable from a slow connection. (Cf. the Two Generals' Problem.)

    You really can't assume that a network connection will ever complete in
    any finite period. In fact, you have to assume that any given
    connection will take an indeterminate (worst-case: infinite) amount of time.

    > Well, the Apple HUI guidelines have been taking some strange
    > directions recently on the Mac end of things. I'm sticking with what
    > I know will provide a tried and true expected (good) user experience.

    I disagree that splash screens are a good user experience.

    > I'm doing it because it's simply lame to display an empty TVC and
    > then have it fill in as content appears like an HTML page.

    I don't know what you mean by "lame." That guidance is because it
    *appears* to the user to be faster when you display the empty TVC.

    There is an even more important reason not to just throw in a splash
    screen as Default.png, though. As the devices get faster (and your code
    more efficient), Default.png will be on the screen for a shorter period
    of time. Eventually you will get the jarring experience of a splash
    screen flashing for a moment before the UI draws. This is a terrible,
    strobe-like user experience.

    The options then are two-fold:

    1) Present a UIImageView of Default.png in
    -applicationDidFinishLaunchingWithOptions:. This allows you to
    subsequently do a controlled presentation of the UI without a strobe
    effect, but irritates users because you are now actively impeding their
    ability to actually use the app.

    2) Drop the splash screen and replace it with an "empty" (non-localized)
    UI image.

    I think of this as analogous to watching movies. One reason that
    watching movies on iTunes is more pleasant than DVD or Blu-ray is that I
    don't have to suffer through Hollywood's version of a splash screen:
    Copyright notices.

    > I'm missing your point. Are you saying don't use a graphic
    > (Default.png) when the app launches or the approach I used back last
    > century with Director?

    I'm not familiar with Director.

    I'm saying: grab a screenshot of the initial screen and edit out any
    elements that will vary by locale (e.g. text, dynamically sized
    controls, etc.) and use that. Alternatively, create a xib that matches
    your UI but without any such localized elements, and use a screenshot of
    that xib.

    The latter requires maintaining two xibs, though, so I usually just go
    the Acorn (or Photoshop, or Pixelmator, pick your poison) route.

    --
    Conrad Shultz

    Synthetiq Solutions
    www.synthetiqsolutions.com
  • On May 15, 2012, at 11:18 AM, Alex Zavatone wrote:

    > Yes, and I've tested it and we haven't had a problem with this.

    But I thought you said you were trying to speed up launch time? Isn’t that the impetus for this thread? And we’re pointing out that waiting for a network request adds a lot of (very unpredictable) latency to your launch time.

    If you’ve tested it, all you know is the latency on the networks (and at the times) you’ve tested it. It doesn’t tell you how it’s going to perform on, say, a train.

    > I trust your advice, yet all I can see is that if the data transmission fails mid 4KB data transfer, it would hang.  In the case where the URL is invalid or unreachable, we have a dict to read from on the device.

    You shouldn’t need to hang at all in that case. Just read the dict on the device at startup, and update from the network in the background.

    > Well, the Apple HUI guidelines have been taking some strange directions recently on the Mac end of things.  I'm sticking with what I know will provide a tried and true expected (good) user experience.

    It was probably a necessary UX for apps in slow emulated runtimes that have to download content to start up at all; but for a very long time the best practice in native apps has been to avoid splash screens. You can’t just apply all of your ideas about Director development to a very different platform; that’s why you’re here asking questions, right?

    —Jens
  • On May 15, 2012, at 3:15 PM, Alex Zavatone wrote:

    > OK.  I've got one item to discuss with you, the splash/launch screen.
    >
    > This is why I like it.
    >
    > Right after clicking on an app's icon, the splash screen/launch screen shows that the device has indeed paid attention to you and responded in the manner you expected, namely, the application has launched and is proceeding to load.
    >
    > I can not see how this is a bad idea.

    From <http://developer.apple.com/library/ios/DOCUMENTATION/UserExperience/Concept
    ual/MobileHIG/UEBestPractices/UEBestPractices.html#//apple_ref/doc/uid/TP40
    006556-CH20-SW25
    >:

    Display a launch image that closely resembles the first screen of the application. This practice decreases the perceived launch time of your application.

    Avoid displaying an About window or a splash screen. In general, try to avoid providing any type of startup experience that prevents people from using your application immediately.

    Also, the user doesn't click on the app's icon, they tap on it (see the section entitled "Apps Respond to Gestures, Not Clicks")

    Glenn Andreas                      <gandreas...>
    The most merciful thing in the world ... is the inability of the human mind to correlate all its contents - HPL
  • On May 15, 2012, at 1:15 PM, Alex Zavatone wrote:

    > Right after clicking on an app's icon, the splash screen/launch screen shows that the device has indeed paid attention to you and responded in the manner you expected, namely, the application has launched and is proceeding to load.
    > I can not see how this is a bad idea.

    It’s psychological. Splash screens make the launch time *seem* slower. The screen is in the user’s face saying “Hi! I’m starting up now but I can’t listen to you for a while because I’m really slow at launching, so just admire our spiffy logo while you’re waiting. Check out the lens flare! Your usage is super important to us, so just hang on until we actually finish setting everything up. It’s almost ready now…”

    It’s the equivalent of calling and immediately being put on hold.

    Now, if your app is going to take five or ten seconds to become responsive, then I’d agree you can’t just put up a picture of an empty UI — the user is going to notice that it’s not interactive and get really frustrated when her taps do nothing. But an app should not take that long to launch. When I was at Apple the goal was always “one bounce” — on a cold launch the app’s Dock icon should bounce only once, and by that time the app should be ready to go. That means one second or less. And in that second (or maybe two) the user is not going to immediately tap on something; her brain is going to be working on parsing the image of the UI, deciding where to tap first, moving her finger there...

    There’s no reason you can’t accomplish this. Admittedly a lot of iOS apps do take a long time to start up (especially games) but I suspect that’s mostly because they are dragging in giant 3rd party cross-platform runtime libraries and/or interpreters. People have been conditioned to expect games to take a long time to load, anyway.

    —Jens
  • On May 15, 2012, at 12:22 PM, Conrad Shultz <conrad...> wrote:

    > I'm doing it because it's simply lame to display an empty TVC and
    > then have it fill in as content appears like an HTML page.

    I don't know what you mean by "lame." That guidance is because it
    *appears* to the user to be faster when you display the empty TVC.
     
    But that's the point, I'm never going to display an empty TVC.

    Lame is how an HTML page loads with pieces coming in in varying order, with parts of the page appearing and then resizing and then graphics appearing and then being replaced.

    I don't want to see it build piece by piece.  And I certainly don't want the user to see that mess.  I want the data to load in an organized manner and then if the screen MUST display in a piece by piece fashion, do not resize components or adjust scroll positions after the data appears, and display the data in an orderly fashion as if it were meant to come in in that order.

    For loading a TVC with a 4KB data load, I opt to make the user wait 1/20th of a second so that the TVC can display all at once with all its data.  And when a user selects a top level item from that TVC, I'll spawn off the next requests to get the data expected while the user is occupied by looking at the interface.  There is a split second where the user thinks about what's in front of their eyes and I want to use that to the user's advantage by prefetching data.

    Cheers, 
    - Alex Zavatone
  • I can add to this. For a long time we followed Apple's advice and had a black screen with tabs at the bottom much like the built-in apps do for our launch screen. Then one release we changed it to be more like a book cover with our logo on it. Nothing else changed. Yet we got an enormous amount of feedback that startup had become slow. The reason? People saw the image of the book cover and not the image of a UI. There was zero difference, but the perception was there.

    We're moving back to the old screen.

    On May 15, 2012, at 3:35 PM, Jens Alfke wrote:

    >
    > On May 15, 2012, at 1:15 PM, Alex Zavatone wrote:
    >
    >> Right after clicking on an app's icon, the splash screen/launch screen shows that the device has indeed paid attention to you and responded in the manner you expected, namely, the application has launched and is proceeding to load.
    >> I can not see how this is a bad idea.
    >
    > It’s psychological. Splash screens make the launch time *seem* slower. The screen is in the user’s face saying “Hi! I’m starting up now but I can’t listen to you for a while because I’m really slow at launching, so just admire our spiffy logo while you’re waiting. Check out the lens flare! Your usage is super important to us, so just hang on until we actually finish setting everything up. It’s almost ready now…”
    >

    Alex Kac - President and Founder
    Web Information Solutions, Inc.

    "Forgiveness is not an occasional act: it is a permanent attitude."
    -- Dr. Martin Luther King
  • On May 15, 2012, at 1:57 PM, Alex Zavatone wrote:

    >
    > But that's the point, I'm never going to display an empty TVC.

    Yes you are, because best practice says you're going to let your app finish launching while you load your data in the background. Or else prepare for people to uninstall your app because it crashes so often during launch, since the network stalled while the data was loading.

    --Kyle Sluder
  • On May 15, 2012, at 01:22 PM, Jens Alfke <jens...> wrote:

    On May 15, 2012, at 11:18 AM, Alex Zavatone wrote:

    Yes, and I've tested it and we haven't had a problem with this.  

    But I thought you said you were trying to speed up launch time? Isn’t that the impetus for this thread? And we’re pointing out that waiting for a network request adds a lot of (very unpredictable) latency to your launch time.
     
    Ok, I guess I didn't do a great job of specifying what constitutes launch time and which display activities would trump launch speed.  In addition to thinking about my current app, I'm looking forwards to my next project where this will be an issue.  

    In the current app, the time to synchronously fetch, serialize and display 4 KB of JSON does not justify introducing unpleasant display behaviour by displaying the empty TVC first, then filling it in when the data gets there.  From what I've seen during development, the launch time did vary - but you explained a lot of that as a result of the simulator setting up debugging.  Right now, testing on two devices over two networks, my app has a 1 second launch time 'till the TVC with data is displayed.  That's great.

    In the next project's case, I can't get away with a sync request at the start since we'll be dealing with much more data and would like to have a design in mind to ponder over while the spec is still being formalized.

    If you’ve tested it, all you know is the latency on the networks (and at the times) you’ve tested it. It doesn’t tell you how it’s going to perform on, say, a train.
     
    Or outside the US.  Thankfully, I have experience testing connection speeds on 3G in the middle of the Kalahari while driving, but your point is noted.

    I trust your advice, yet all I can see is that if the data transmission fails mid 4KB data transfer, it would hang.  In the case where the URL is invalid or unreachable, we have a dict to read from on the device.

    You shouldn’t need to hang at all in that case. Just read the dict on the device at startup, and update from the network in the background.
     
    Wouldn't that display one data set and then another?  OHHHH, load and use the local saved data set while loading the other data and storing that in the pList, which would then get used next time.  

    I was thinking of keeping a version string in both data sets, or in a component file and only loading the new data file from the server if the version string is different.  Surely though, this issue must already be solved and probably in a more efficient manner without having a separate file to hold a version string.

    Well, the Apple HUI guidelines have been taking some strange directions recently on the Mac end of things.  I'm sticking with what I know will provide a tried and true expected (good) user experience. 

    It was probably a necessary UX for apps in slow emulated runtimes that have to download content to start up at all; but for a very long time the best practice in native apps has been to avoid splash screens. You can’t just apply all of your ideas about Director development to a very different platform; that’s why you’re here asking questions, right?
     
    I'm certainly not applying all my ideas from Director hybrid CD ROMs and Shockwave, just the ones that I think make sense, and asking you guys (who have been doing iOS longer than I) if these approaches make sense or not.  And I'm quite happy to get this much in-depth feedback.

    Thanks man.

    —Jens
  • On May 15, 2012, at 12:56 PM, Alex Zavatone wrote:

    > We are caching (saving in a pList) the data from the previous launch.  It falls through and uses the previously saved data if we can't get to the internet.  Now, what I'm unclear on at this point (I've never truly truly trusted some system to cache stuff for me) is how the system knows if the data online is newer than the data we've got.  I trust that the caching method you're mentioning is different than saving to a pList?

    It looks like this:

    * Read data from plist if present
    * If you have data, display it
    * Send an async HTTP GET to the data source URL. If you already have cached data, get the cached Etag value (q.v.) and add it as the value of an If-None-Match: header.
    * Start the event loop.

    * When a response arrives:
    * If the status is 304, it means there is no newer data on the server.
    * If the status is not 200, there was an error.
    * Otherwise update the UI with the new data.
    * Save the new data to the plist on disk.
    * Also save the value of the “Etag” header from the response, so you can send it with the next request.

    This is called a “Conditional GET”. It’s a key part of the way the Web does caching. Browsers use it, news readers use it, REST APIs use it. There are a lot of online references; here’s one from the Google search results that looks decent:
    https://ruturajv.wordpress.com/2005/12/27/conditional-get-request/

    ...OK, I just located the document I’ve been trying to find since this thread started. In terms of this, you’re hitting myths #1 and #2:

    > The Eight Myths Of Distributed Computing, by Peter Deutsch
    > Essentially everyone, when they first build a distributed application, makes the following eight assumptions. All prove to be false in the long run and all cause big trouble and painful learning experiences.
    >
    > 1.    The network is reliable
    > 2.    Latency is zero
    > 3.    Bandwidth is infinite
    > 4.    The network is secure
    > 5.    Topology doesn't change
    > 6.    There is one administrator
    > 7.    Transport cost is zero
    > 8.    The network is homogeneous

    http://nighthacks.com/roller/jag/resource/Fallacies.html

    —Jens
  • - Zav

    On May 15, 2012, at 01:32 PM, glenn andreas <gandreas...> wrote:

    Since the data and scroll position of the first screen is variable, I don't see how I can display a screen that is not "splash screen like".  I've got no idea why someone would display an about window when an app launches.  Since I'm delivering several enterprise apps, it seems best to convey a similar launch experience to all to bring across a feeling of "this is one of our apps", not "this is just another iOS app from some developer".  Corporate branding is important here, as well as which branch of the company it comes from.  With a 1 second launch time, I feel that this is not detrimental to the user experience at all, we have a specific target audience, and I want to set expectations for all the apps we will be producing internally.

    Also, the user doesn't click on the app's icon, they tap on it (see the section entitled "Apps Respond to Gestures, Not Clicks")
     
    Ah, yes.  A tap's up action still equates to a mouseUpInside in my mind.  Thanks for the clarification.  
  • - Zav

    On May 15, 2012, at 01:35 PM, Jens Alfke <jens...> wrote:

    On May 15, 2012, at 1:15 PM, Alex Zavatone wrote:

    Right after clicking on an app's icon, the splash screen/launch screen shows that the device has indeed paid attention to you and responded in the manner you expected, namely, the application has launched and is proceeding to load.
    I can not see how this is a bad idea.

    It’s psychological. Splash screens make the launch time *seem* slower.
     

    Ahhh, I disagree with this.  If it stays up for a while, then there is a bigger problem.  It is indeed psychological, and displaying something does tell the user that "yep, I, the device did respond to your action".

    I think it's worse to display a screen that looks like the first screen but one that they can't interact with.

    Now, if your app is going to take five or ten seconds to become responsive, then I’d agree you can’t just put up a picture of an empty UI — the user is going to notice that it’s not interactive and get really frustrated when her taps do nothing. But an app should not take that long to launch.
     
    My thoughts exactly.

    When I was at Apple the goal was always “one bounce” — on a cold launch the app’s Dock icon should bounce only once, and by that time the app should be ready to go.
     
    Tell that to the guys who make Safari.  Even on last summer's MBP with 16 GB of RAM, I don't know many apps that take one bounce or less to launch - and I'm not talking about Photoshop.   
  • On May 15, 2012, at 02:13 PM, Kyle Sluder <kyle...> wrote:

    On May 15, 2012, at 1:57 PM, Alex Zavatone wrote:

    >
    > But that's the point, I'm never going to display an empty TVC.

    Yes you are, because best practice says you're going to let your app finish launching while you load your data in the background. Or else prepare for people to uninstall your app because it crashes so often during launch, since the network stalled while the data was loading.
     
    Corporate intranet, Enterprise app.  Not a factor.
  • On May 15, 2012, at 2:35 PM, Alex Zavatone wrote:

    >
    > On May 15, 2012, at 02:13 PM, Kyle Sluder <kyle...> wrote:
    >
    >>
    >> On May 15, 2012, at 1:57 PM, Alex Zavatone wrote:
    >>
    >>>
    >>> But that's the point, I'm never going to display an empty TVC.
    >>
    >> Yes you are, because best practice says you're going to let your app finish launching while you load your data in the background. Or else prepare for people to uninstall your app because it crashes so often during launch, since the network stalled while the data was loading.
    >
    > Corporate intranet, Enterprise app.  Not a factor.
    >

    ALWAYS a factor. The network cannot be both reliable and fast.

    --Kyle Sluder
  • On May 15, 2012, at 02:22 PM, Jens Alfke <jens...> wrote:

    On May 15, 2012, at 12:56 PM, Alex Zavatone wrote:

    We are caching (saving in a pList) the data from the previous launch.  It falls through and uses the previously saved data if we can't get to the internet.  Now, what I'm unclear on at this point (I've never truly truly trusted some system to cache stuff for me) is how the system knows if the data online is newer than the data we've got.  I trust that the caching method you're mentioning is different than saving to a pList?

    It looks like this:

    * Read data from plist if present
    * If you have data, display it
    * Send an async HTTP GET to the data source URL. If you already have cached data, get the cached Etag value (q.v.) and add it as the value of an If-None-Match: header.
    * Start the event loop.

    * When a response arrives:
    * If the status is 304, it means there is no newer data on the server.
    * If the status is not 200, there was an error.
    * Otherwise update the UI with the new data.
    * Save the new data to the plist on disk.
    * Also save the value of the “Etag” header from the response, so you can send it with the next request.

    This is called a “Conditional GET”. It’s a key part of the way the Web does caching. Browsers use it, news readers use it, REST APIs use it. There are a lot of online references; here’s one from the Google search results that looks decent:
    https://ruturajv.wordpress.com/2005/12/27/conditional-get-request/
     
    Rockin'.  Exactly the information I was looking for.  My only other question is what's the "best" approach for async requests?  I've used GCD recently, but it appears that the frameworks have been advancing so quickly, it's not always obvious what the current best method is for async HTTP communication.

    It's been about 14 years since I've written an async network operation manager, one does get rusty.

    ...OK, I just located the document I’ve been trying to find since this thread started. In terms of this, you’re hitting myths #1 and #2:
     
    These are DIRECTLY relevant to my next project, where I can be assured that the connection health and speed will be variable.  You're on either my Christmas list or in my will.  Please pick only one.
      

    The Eight Myths Of Distributed Computing, by Peter Deutsch
    Essentially everyone, when they first build a distributed application, makes the following eight assumptions. All prove to be false in the long run and all cause big trouble and painful learning experiences.

    1.The network is reliable
    2.Latency is zero
    3.Bandwidth is infinite
    4.The network is secure
    5.Topology doesn't change
    6.There is one administrator
    7.Transport cost is zero
    8.The network is homogeneous

    http://nighthacks.com/roller/jag/resource/Fallacies.html

    —Jens
  • On May 15, 2012, at 2:45 PM, Alex Zavatone wrote:

    > Rockin'.  Exactly the information I was looking for.  My only other question is what's the "best" approach for async requests?  I've used GCD recently, but it appears that the frameworks have been advancing so quickly, it's not always obvious what the current best method is for async HTTP communication.

    Async NSURLRequest, as already mentioned. It’s a little bit clunky to use by modern standards — I wish they’d add some nice block-based callbacks to it instead of making me implement yet another delegate class — but it works well, and it’s quite well documented by Apple and others.

    —Jens
  • On 5/15/12 2:35 PM, Alex Zavatone wrote:
    >
    > On May 15, 2012, at 02:13 PM, Kyle Sluder <kyle...> wrote:
    >
    >>
    >> On May 15, 2012, at 1:57 PM, Alex Zavatone wrote:
    >>
    >>>
    >>> But that's the point, I'm never going to display an empty TVC.
    >>
    >> Yes you are, because best practice says you're going to let your app
    >> finish launching while you load your data in the background. Or else
    >> prepare for people to uninstall your app because it crashes so often
    >> during launch, since the network stalled while the data was loading.
    >
    > Corporate intranet, Enterprise app.  Not a factor.
    >

    As someone who, in addition to writing apps, has actually engineered and
    administered corporate networks I can assure you that this is still a
    factor.

    No matter the company, or how good the engineers are, there WILL be a
    point where someone accidentally creates a routing loop, or a switch
    goes down and there is no hot replacement, or there is an unrecognized
    dependency on some upstream service.  Heck, I've seen a malfunctioning
    elevator motor cause catastrophic wireless interference.

    I don't know how much clearer we can be on this: so far, it looks like
    Kyle, Jens, Fritz, and I have all told you, in various terms, that you
    should never, ever, for any reason at all put a synchronous network
    connection on the main thread.

    Even if all you are going for is a single ping, it absolutely must be
    done using the asynchronous APIs on the main thread, or the synchronous
    APIs on a background thread (I suppose the asynchronous APIs could also
    be used on a background thread, but that doesn't really buy you anything).

    If I were to model an app, and maybe Kyle can jump in here for obvious
    reasons, I would pick OmniFocus.  It sounds like it has similar broad
    behaviors to what you're going for.  When I open OmniFocus, here's the
    sequence of events:

    1) I tap the icon and it briefly shows a non-localized splash screen as
    I've been advocating.  (No Omni Group logo!)

    2) It loads my local data store and quickly shows my
    task/project/whatever list.

    3) At some point, presumably having detected that there is a network
    connection, it initiates a background connection to my WebDAV server and
    determines whether there have been any changes on either the client or
    server.

    4) Only if changes are present, a sync progress indicator is presented
    and the sync proceeds asynchronously.

    5) While the sync is in progress, I can't edit my existing tasks, but I
    can of course quick-add new ones.  This is important since OmniFocus
    can't know how long the sync might take, and it doesn't want to block my
    ability to use the app to the maximum extent possible.

    6) If the sync succeeds, I am immediately returned to a table view which
    has been reloaded with any synced data.  If the sync fails (presumably
    by exceeding some timeout threshold), an alert view notifies me of as much.

    7) I can continue using the full feature set.

    --
    Conrad Shultz

    Synthetiq Solutions
    www.synthetiqsolutions.com
  • On May 15, 2012, at 3:10 PM, Conrad Shultz wrote:

    > If I were to model an app, and maybe Kyle can jump in here for obvious
    > reasons, I would pick OmniFocus.  It sounds like it has similar broad
    > behaviors to what you're going for.  When I open OmniFocus, here's the
    > sequence of events:

    Yes, this is both what OmniFocus does and why it does it. But—importantly!—OmniFocus isn't special; it's following general best practice.

    Want to see other examples of this in action? Twitter, Mail, and Messages all do it. You might have to force-quit them to see the behavior in action, but they all start up with text-free screenshots as their Default.png, then quickly replace it with live UI, then populate that UI with whatever local content they can fetch quickly, then check for new content and update the content.

    You might consider disabling user interaction from the time your app starts loading to the time you've finished populating the UI with local data, in order to prevent the user from getting into a state that will just be disrupted when the local data has loaded. But you should _not_ load any significant amount data synchronously on the main thread, even if it's only local data.

    --Kyle Sluder
  • On 5/15/12 1:57 PM, Alex Zavatone wrote:
    > But that's the point, I'm never going to display an empty TVC.
    >
    > Lame is how an HTML page loads with pieces coming in in varying order,
    > with parts of the page appearing and then resizing and then graphics
    > appearing and then being replaced.

    > I don't want to see it build piece by piece.  And I certainly don't want
    > the user to see that mess.  I want the data to load in an organized
    > manner and then if the screen /MUST/ display in a piece by piece
    > fashion, do not resize components or adjust scroll positions after the
    > data appears, and display the data in an orderly fashion as if it were
    > meant to come in in that order.

    I feel like we should back up here and walk through the loading process
    because I get the impression that we are all talking about different events.

    When you launch an application, the sequence of events is, to a first
    approximation, the following:

    1) Springboard, or launchd, or the window server, or whatever Apple
    component is responsible for managing application launches looks inside
    your bundle and picks out a Default.png depending on the device and
    orientation.  This gets drawn immediately to the screen.

    2) After going through the usual application launch details that you
    have no control over (and whose duration will depend on hardware, system
    resources and activity, etc.), the OS sends your application delegate
    -applicationDidFinishLaunchingWithOptions:.  This is effectively the
    first place you can start doing anything interesting.

    3) The Default.png continues to be displayed until
    -applicationDidFinishLaunchingWithOptions: returns and, on the next turn
    of the run loop, the UI can start being updated.  This means that it is
    very important to get and and get out of
    -applicationDidFinishLaunchingWithOptions: FAST.  No synchronous network
    connections, no lengthy disk I/O, no blocking CPU operations.  If you
    drag your feet in here, not only will Default.png remain on screen too
    long, but the watchdog might even kill your app.

    4) On the next turn of the run loop, the UI (which was set up in
    -applicationDidFinishLaunchingWithOptions:) is displayed, instantly
    replacing Default.png.

    5) The run loop is now running normally, and you can do whatever you
    wish within your code.

    The takeaways:

    * Don't block in -applicationDidFinishLaunchingWithOptions:.  (Well,
    don't ever block, but especially not here.)
    * Don't display a splash screen in Default.png because it will, if
    things are working well, flash away very quickly.

    What I am suggesting you do is have a Default.png that will ease the
    transition to the initial UI, which might just be an empty table view.
    As data comes in, you update the table view.

    If you don't want to update the table view in real time (I agree with
    you that if the connection is really slow it can be awkward to watch
    rows load one at a time), you can collect updates as they come in and
    load them in batches.

    > For loading a TVC with a 4KB data load, I opt to make the user wait
    > 1/20th of a second so that the TVC can display all at once with all its
    > data.  And when a user selects a top level item from that TVC, I'll
    > spawn off the next requests to get the data expected while the user is
    > occupied by looking at the interface.  There is a split second where the
    > user thinks about what's in front of their eyes and I want to use that
    > to the user's advantage by prefetching data.

    Except that, as discussed, you have no way to ensure that it will only
    be 1/20th of a second.  Plus, things that start out as 4 KB have a way
    of growing; it's easier to implement best practices up front than to
    have to rearchitect your entire launch process when your manager or
    client comes to you and says, "OK, now we need to deal with 500 KB."

    And if you do get fast performance, you run smack into the other problem
    I described with splash screens (the flash/strobe effect), which you
    haven't addressed.

    --
    Conrad Shultz

    Synthetiq Solutions
    www.synthetiqsolutions.com
  • On May 15, 2012, at 2:50 PM, Jens Alfke wrote:

    > Async NSURLRequest, as already mentioned. It’s a little bit clunky to use by modern standards — I wish they’d add some nice block-based callbacks to it instead of making me implement yet another delegate class — but it works well, and it’s quite well documented by Apple and others.

    I'd disagree regarding the "quite well documented by Apple" part.
    Don't know about documentation by others.

    Apple's doc has this bit of example code.

    ----------------
    NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
    if (theConnection) {
        // Create the NSMutableData to hold the received data.
        // receivedData is an instance variable declared elsewhere.
        receivedData = [[NSMutableData data] retain];
    } else {
        // Inform the user that the connection failed.
    }
    ----------------

    Is it possible for one of the delegate methods to be called before
    receiveData is initialized?  The doc for initWithRequest:delegate: states

      This is equivalent to calling initWithRequest:delegate:startImmediately:
      and passing YES for startImmediately.

    Sure looks like a possible race to me.  That disqualifies it as good
    documentation.

    Marc
  • On May 15, 2012, at 3:33 PM, Marco S Hyman wrote:

    > I'd disagree regarding the "quite well documented by Apple" part.

    Have you read the URL Loading System guide, not just the class API docs?

    > Is it possible for one of the delegate methods to be called before
    > receiveData is initialized?

    No. The delegate will only be called on the thread you created the NSURLConnection object on (as the class doc clearly says), and only via runloop callbacks, so there’s no way it can be called before you finish handling the current event.

    I suppose it could conceivably call the delegate from within the -init method itself, but that would be rather strange behavior (I can’t think of any API that does this, since it means your delegate gets called before you even know the identity of the object calling it!) and moreover, the fact that Apple’s example shows things being done in this order implies that it’s safe.

    > Sure looks like a possible race to me.  That disqualifies it as good
    > documentation.

    So it’s bad documentation because it shows a race condition you thought up, even though the race condition doesn’t actually exist? O_o

    —Jens
  • On May 16, 2012, at 5:50 AM, Jens Alfke wrote:
    >
    > Async NSURLRequest, as already mentioned. It’s a little bit clunky to use by modern standards — I wish they’d add some nice block-based callbacks to it instead of making me implement yet another delegate class — but it works well, and it’s quite well documented by Apple and others.
    >
    > —Jens

    +1

    I eventually wrote a generic block-based delegate which fulfills 95% of my uses of NSURLConnection, I'm sure others have too. Just give it the NSURLRequest and a block to call on success or failure and off it goes. I'm hoping to see some more block-based callbacks creep into the next version of iOS (and OSX I guess), they are awesomely usable.
  • On May 15, 2012, at 5:13 PM, Jens Alfke wrote:

    > On May 15, 2012, at 3:33 PM, Marco S Hyman wrote:
    >
    >> I'd disagree regarding the "quite well documented by Apple" part.
    >
    > Have you read the URL Loading System guide, not just the class API docs?

    The URL Loading System Guide contained the code I quoted.  Nowhere in that
    doc does the term "thread" appear.  Yes, you are correct that I missed
    the comment in the related class doc that stated delegate methods will be
    called on the thread that started the async load operation.

    In hindsight I should have expected that.

    > So it’s bad documentation because it shows a race condition you thought up, even though the race condition doesn’t actually exist? O_o

    No, the URL Loading System Guide is bad documentation because it leaves important
    details to the imagination of this reader. :)

    Marc
previous month may 2012 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