v1.10 Upgrade Tips: New SC.SplitView

written by dcporter

One of the underpublicized changes we made in v1.10 was the inclusion of a great new SC.SplitView. The existing view was broken in some key ways, and the new view, which had been marinating in an experimental framework for some time, was snazzy and stable. We decided that few enough people had gotten SplitView working in their production apps that we could afford to swap in the new view, but it’s API-noncompliant and we should have made a bigger deal about it. Sorry about that!

Luckily, updating to the new SplitView is easy. The most important thing to do is mix SC.SplitChild into your topLeftView and bottomRightView. If you want your views to have an initial height or width, depending on layoutDirection, just set the size property (whole-number pixel values only). The best news is that a SplitView can now handle an arbitrary number of sections – you specify them in its childViews array, just like a normal view.

Full documentation for the new SC.SplitView available here; more information about the new SplitChild mixin available here.

SproutCore 1.10.1 Release

written by admin

The first patch release of SproutCore 1.10 is now available. This update includes the following fixes:

  • Allows inline text fields to work with automatic child view layout.

  • Fixed SC.Module loading for IE11 and future versions.

  • Fixed SC.Drag cancellation with the Escape key to call dragEnded for cleanup.

  • Added a retina image for the default theme of SC.MenuPane.

  • Fixed the touch selection of SC.CollectionView to prevent an item from appearing deselected when touched twice in a row.

  • Fixed the insertion point code for SC.GridView in order to show an insertion view when dragging and dropping onto grid views.

  • Fixed an issue with SC.ScrollView which would cause the HW accelerated content offset to be incorrect when the content view re-renders.

  • Improved the deceleration animation of SC.ScrollView for better performance.

  • Fixed an error in the deceleration animation of SC.ScrollView that could throw an exception when stopped very quickly.

  • Changed the SC.EmptyTheme class name to sc-empty-theme to avoid conflicts with the use of sc-empty. This fixes a bug where SC.ProgressView would always appear empty when used with the empty theme.

  • Fixed incorrect behavior of SC.Statechart sendEvent that allowed events to be sent during a state transition.

  • Fixed a bug that prevented queued events due to a state transition from being sent when the state transition was complete.

  • Fixed a caching problem with SC.routes that would allow the location property to be incorrect depending on how it was changed.

  • Fixed a regression in determining the OS of Linux and Android in SC.browser.

Welcome New Committers: Nicolas Badia & Greg Fairbanks

written by admin

We’re pleased to announce that Greg Fairbanks and Nicolas Badia have accepted becoming SproutCore Committers. These two have been especially active in submitting fixes and improvements to the framework and are great examples of persevering through the process of vetting pull requests with the core team in order to ensure each change meets the high standards of SproutCore. It can be a tedious process to get something approved, but the end result is better code that stands the test of time. Now that they are official Committers this will become even easier for them.

For those that aren’t sure of the difference between Committers and Reviewers, Committers are actually required to work in their own branches in the repo and submit every change as a pull request to be accepted by a Reviewer. The Reviewers are allowed to make direct fixes, but will most likely submit anything at all substantial as a pull request too in order to get a few other Reviewers’ feedback and acceptance. Both roles also include the responsibility to try to move the outstanding issues and pull requests towards a resolution.

As you can tell from the above description, both roles are a lot of work and it takes people with a real dedication to the project to accept what can be a thankless and demanding task, so please take a moment to catch all of these people on #sproutcore and sproutcore@googlegroups.com and show them your support.

SproutCore 1.10.0 Release

written by admin

The SproutCore team is very pleased to announce the final release of SproutCore version 1.10.0. This version is the fastest, most feature-rich and most stable version of SproutCore to-date and includes several new additions and improvements to make development with SproutCore even better and to make SproutCore apps that much more impressive. For a quick introduction to the many changes in version 1.10, please check out the official press release.

For those wishing to install SproutCore for the first time, please visit http://sproutcore.com/install/ for instructions. If you are upgrading from a previous version of SproutCore, simply run the following:

gem update sproutcore

If you have any trouble installing or upgrading to 1.10, be sure to visit the mailing list sproutcore@googlegroups.com or the IRC channel #sproutcore for support. As always, the team and community are here to help!

Highlights of Version 1.10.0

In this release we dug deep into the core of SproutCore to improve the entire development and runtime experience from start-to-finish. This includes clean-ups of the developer API to improve consistency and memorability; brand new features for automatic layout, transition animations and live updates; extensive internal architectural refactors to boost speed and reduce memory consumption; and more.

We’re sure you will find that this is the most stable, usable and best performing version of SproutCore ever. As such, this is a massive update and there are so many improvements and fixes that we can’t possibly cover them all, but here is a list of the key features:

New Features

Automatic Child View Layout

In 1.10 we introduce the childViewLayout plugin property of SC.View.  This is an incredibly useful utility that makes defining and updating the user interface much easier.  For example, to create a long form previously, you would need to specify the layout of each child view, which was a tedious task.  As well, making an adjustment to the layout of any child view, meant a lot of effort updating all the following child views and it was really difficult to make a live adjustment, say to show an error message under a field for example.

In 1.10, you can now set a child view layout plugin that contains the logic to automatically lay out the child views for you. The plugin architecture is quite simple, so you can create custom child view layouts of your own, but you likely will never need to since the two plugins bundled with SproutCore take care of most needs. These are SC.View.VERTICAL_STACK and SC.View.HORIZONTAL_STACK, which will automatically lay out the child views in a stack either vertically or horizontally respectively. If the layout of a child view is adjusted, the plugin will re-lay out all the remaining views as efficiently as possible.

Of course, you can use the SC.FlowedLayout mixin to perform this and there are actually a lot of similarities between childViewLayout plugins and SC.FlowedLayout, but childViewLayout is significantly easier to use and can be swapped in or out and configured on the fly much more easily. As was already noted, the plugin architecture makes it even easier to create custom layout engines that can be shared among projects and the community. And childViewLayout works with the new transition plugins for creating amazing new user interfaces.  See childViewLayout in action here.

Automatic SC.View Transitions

One of the coolest new features in 1.10 is the addition of SC.View transition plugins. Similar to the childViewLayout plugin, these plugins can be set on views to automatically modify the behavior of the view. For example, instead of simply appending a dialog pane, which is jarring to the user, we can set transitionInSC.View.POP to have it pop in. This allows us to quickly add subtle animations as our views are added, removed, shown, hidden or moved. The overall effect is that the user interface feels smoother and more alive. Best of all, it’s so simple to add transition plugins, the challenge will be to not overdo it (* seriously, don’t overdo the animations. For users, a little animation is better than nothing, but nothing is better than too much).

There are now five SC.View state changes that you can have transition automatically:

  • transitionIn: Animate each time the view/pane is appended to the document. Ex. SC.View.BOUNCE_IN

  • transitionOut: Animate when the view/pane is removed from the document. Ex. SC.View.SLIDE_OUT

  • transitionShow: Animate when the view is made visible from being hidden. Ex. SC.View.SPRING_IN

  • transitionHide: Animate when the view is hidden from being visible. Ex. SC.View.FADE_OUT

  • transitionAdjust: Animate when the view’s layout is adjusted. Ex. SC.View.SMOOTH_ADJUST

As you can see in the examples above, SproutCore includes several built-in transition plugins. For transitionIn and transitionShow you can use SC.View.BOUNCE_INSC.View.FADE_INSC.View.POP_INSC.View.SCALE_INSC.View.SLIDE_IN and SC.View.SPRING_IN. Likewise, each of these has a matching outgoing transition for use with transitionOut and transitionHide: SC.View.BOUNCE_OUTSC.View.FADE_OUTSC.View.POP_OUTSC.View.SCALE_OUTSC.View.SLIDE_OUT and SC.View.SPRING_OUT.

You can also transition most layout adjustments and SproutCore includes plugins for transitionAdjustSC.View.BOUNCE_ADJUSTSC.View.SMOOTH_ADJUST and SC.View.SPRING_ADJUST. You will want to use transitionAdjust when some other code is adjusting the child views automatically (e.g. a childViewLayout plugin) and you want everything to smoothly change.  For example, notice how removing a child view has a smooth transition in this demo?

See these transition plugins in action here.

Automatic SC.ContainerView Transitions

Last, but not least, there is one more view that received the transition treatment. SC.ContainerView is a workhorse view that is used extensively in SproutCore and now in 1.10, animating the view swapping is dead simple. SproutCore now includes built-in transitionSwap plugins for use by SC.ContainerViewSC.ContainerView.DISSOLVESC.ContainerView.FADE_COLORSC.ContainerView.MOVE_INSC.ContainerView.PUSH and SC.ContainerView.REVEAL.

Just like the other transitions, it is simply a matter of setting the property on the view like so,

  transitionSwap: SC.ContainerView.PUSH

And just like all the plugins, you can easily define custom transitions for even more impressive view swapping. See the container view transitions in action here.

Application Cache

One of SproutCore’s main goals has always been to provide all the tools a developer needs to create professionally quality web software as quickly as possible and 1.10 makes this easier in many ways, one of which is to make it easier to provide offline support and live software updates for your users. This is all possible using the browser’s application cache, but the complex behavior and states of the application cache make it difficult and time-consuming to work with directly.

Instead, SproutCore 1.10 introduces SC.appCache which is a simple object that  abstracts out the most important properties of  the application cache for developers to use.  This is done with the following observable properties on SC.appCachehasNewVersionisNewVersionValidprogress and isReadyForOffline.

Using these properties you can update your UI to show the progress of the app cache as it loads or when the application is ready for offline state (i.e. cached), you can check for a new version of your app while running using SC.appCache.get('hasNewVersion') and you can even prompt your users to upgrade (i.e. reload) their running apps live.  So as you can see, with SC.appCache you will find lots of easy ways to make your SproutCore app’s user experience just a little bit more amazing. Note, this can be especially useful in a test environment, where you want to ensure that the testers aren’t running the old version (which happens frequently when application cache is enabled and not managed).

SC.CollectionView Performance Increase

Perhaps the change that will have the most noticeable impact on your app is the automatic collection view DOM and item pooling. This is functionality that you would have had to have a deep understanding of SC.CollectionView and the now deprecated, SC.CollectionFastPath to use previously. That is no longer the case, because it is on by default in 1.10. This means that not only does SC.CollectionView optimize list rendering by only rendering the visible items, it now also does this by pooling and re-using the item views and DOM elements so that scrolling through gigantic lists is even smoother.

As part of this work, you will notice a marked improvement of SC.ScrollView + SC.CollectionView (SC.ListView or SC.GridView) on mobile.

The caveat to this performance boost is that all item view subclasses should implement render and update. The update function is of critical importance since it allows an existing item view in memory to be re-used with new content and update the layer in place rather than destroying and creating a new item view and layer for each item.

You can read more about this change here.

SC.DateTime Additions

Formatting dates and times is always a tricky subject, in particular when localization is concerned. SproutCore has always had excellent date time support in SC.DateTime and in 1.10 this gets even better. You can now use a special SproutCore-only formatter, %E, with SC.DateTime to return the elapsed time from or since now. This makes it easy to show more human readable time spans like “Right now” or “Last week”.

There is an entire set of default values from the number of years ago to right now to a number of years in the future and of course the strings are completely configurable and localizable. For example,

  var date = SC.DateTime.create();
  date.toFormattedString("%E"); // "Right now"

  date.advance({ minute: 4 });
  date.toFormattedString("%E"); // "In 4 minutes"

  date.advance({ day: -7 });
  date.toFormattedString("%E"); // "About a week ago"

There is also a new SC.DateTime instance method, difference, which allows you to very easily compute the number of weeks, days, hours, minutes, or seconds between two dates.

Animation

Animation saw a lot of love in 1.10. Part of this was to pave the way for automatic transitions and part of it was to make using animation even simpler. The biggest change for those already using SproutCore is that calling SC.View.prototype.animate now always runs in the next run of the run loop via invokeNext. This solves the chronic problem having to wrap a call to animate within an invokeLast function so that we could be sure that the DOM had been updated so that the CSS transition would run. Wondering why animations failed to run properly should no longer be an issue in 1.10.

We’ve also added a new option delay to delay the start of the animation and added SC.View.prototype.cancelAnimation so that you can cancel animations and revert to where it started, where it was going or even in place (* which is extremely hard to do considering that some animations use CSS transforms, but yeah SproutCore can handle it).

You can also now animate centerX and centerY and if you animate the height or width of a centered view, SproutCore will automatically animate margin-left and margin-top to make the animations smooth.

Cascading Enabled State

Just like the animation changes should make basic SproutCore development that much easier, we’ve also reworked isEnabled to help you lock down your UI in one simple step. For example, it’s often nicer to edit some content modally in place rather than throwing up a modal pane to block the entire UI. However, while editing the content in place we may still want to prevent other visible controls from being active.

While you could search out and bind up every control’s isEnabled property so that you could turn them all off, this is a lot of work and prone to error. Instead, isEnabled will now cascade actively to all child views so you can disable whole sections of your app simply by setting isEnabled on the top-most view for the section.

Since this cascading can be blocked by any child view by setting shouldInheritEnabled to false, you could even set isEnabled to false on the main pane and still keep a section of child views separately enabled.

Overall Performance Increase

The best feature of 1.10 is the one without a specific tagline, but 1.10 results in a large performance increase over previous versions. For instance, SC.View has been completely refactored to allow for faster view creation, rendering and updates and improvements to the rendering architecture means that many of SC.View’s subclasses, in particular the big three: SC.ButtonViewSC.LabelView and SC.CollectionView were able to be further optimized to use much less memory. For instance, we removed probably a dozen display observers just from these three classes alone, which makes a noticeable difference in how fast these views can be instantiated and the effect they have on run time performance.

This release also includes several memory leak fixes that would have gradually slowed down a previous app. For example, migrating a large 1.9 app to 1.10 resulted in that app being runnable on iPad one, where previously it would crash after several minutes.

Miscellaneous

  • Improves the regular expression used by SC.RenderContext to escape strings so that HTML entities like ' or à are preserved.

  • Removes the blockers that prevented all browsers that support touch events from using mouse events. This prevented certain configurations of later versions of IE from receiving mouse events.

  • Adds the concept of Infinity to SC.IndexSet. Although, the number of indexes will always be constrained to Number.MAX_SIZE, attempting to create a range even in the several hundreds of thousands would freeze the client, because it will attempt to generate hints every 256 items. Instead we can use the concept of Infinity and avoid hinting the infinite range. For one, this allows for infinite arrays and infinite lists to be possible without using really large numbers that are very slow to hint.

  • Adds isEditableisDeletable and isReorderable support for item views. This change uses canEditContentcanDeleteContent and canReorderContent to indicate whether to add the respective properties to the item views. For example, this allows you to toggle canReorderContent to hide or show a drag handle on item views that have isReorderable as a display property. Note: Setting isEditable to false on the collection view overrides the three other properties.

  • Adds ‘sc-item’ class to non-group collection items. Otherwise, you can apply styles to groups, to items and groups, but not items individually without adding custom class names to the example view.

  • Adds createdByParent property that is set to true when the view was instantiated by its parent through createChildView.

  • Prevents memory leaks and simplifies the code of several views by using createdByParent to identify when the child view being removed was automatically created and should now be destroyed.

  • Adds updated SproutCore image assets.

  • Renames SC.FILL_PROPORTIONALLY to SC.BEST_FILLSC.FILL_PROPORTIONALLY is still supported but no longer documented.

  • Allows SC.State to represent the empty (“”) route. Previously, there was no way for a state to represent the empty route using representRoute:. Now a state can set representRoute: "" to be triggered when the empty route is fired.

  • Added platform detection for the Apache Cordova (formerly phonegap) javascript-to-mobile bridge.

  • Added retina stylesheet support to module loading, style sheets were being generated but not loading.

  • Removes backgroundColor observer from all SC.Views. If anyone actually uses this property AND needs it to update, they’ll have to add the displayProperty themselves.

  • Makes SC.ToolbarView more intelligent about it’s styling: not just styled appropriately for top alignment, layout adjusted to include a top or bottom border.

  • Refactors the SC.PickerPane positioning code for SC.PICKER_POINTER and SC.MENU_POINTER. With this refactor, the picker pane properly positions itself on the most appropriate side and will slide itself up/down or left/right if it can in order to fit.

    • adds ability for pointer to automatically adjust its position as the pane shifts left/right or up/down regardless of its size

    • fixes pane positioning to take into account the borderFrame

    • adds windowPadding property that ensures pickers are positioned a certain amount of distance from all edges of the screen

    • deprecates the extraRightOffset property since the pointer positions itself now

  • Removes the restriction that render delegate data sources can only retrieve displayProperties properties. This restriction is not especially helpful, but worse than that, it forces us to have excess display properties, which means excess observers being set up and running although not every property that effects the display necessarily needs to be observed.

  • Adds an ‘[]’ observer to the array when an ‘@each.key’ observer is used. Previously, changes to the array’s membership would noisily call propertyDidChange while setting up key observers on each additional item. This change gets rid of this shotgun approach that resulted in multiple fires of the observer when adding multiple new items to the array. It also fixes the problem that removing items from the array also failed to call a change to the array.

  • Adds debug mode only warnings if invokeOnce or invokeLast are called outside of a run loop. This should prevent developers from writing custom event handling code outside of the run loop, which can cause strange race condition bugs.

  • Improved performance of scroll view on iOS significantly by using request animation frame.

  • Optimizes destroy of SC.View. This is a slow part of the view lifecycle, due to recursively running through every descendant and notifying willRemoveFromParentdidRemoveFromParentwillRemoveChilddidRemoveChild as well as searching through and clearing all of the childViews arrays. This cuts the destroy time almost in half for the average view tree.

  • All errors are actual Error objects, which provide more debuggable stack traces.

  • Updates jQuery framework to 1.8.3 and removes buffered jQuery.

    • Patches jQuery to prevent security warnings in IE.
  • Improves animation support for current and future browsers. The previous platform checks prevented using accelerated layers on non-webkit platforms, plus any browser that dropped the prefixes would indicate that they did not have support for transitions and transforms.

  • Requesting an index beyond the length of a sparse array should not trigger an index or range request on the delegate and just returned undefined. If you need to prime a sparse array to start loading without setting a length, it’s best to use sparseArray.requestIndex(0).

  • The SC.Store.reset() method now also clears out all of the record arrays.

  • Added SC.browser.cssPrefixSC.browser.domPrefix and SC.browser.classPrefix.

  • Added SC.browser.experimentalNameFor()SC.browser.experimentalStyleNameFor() and SC.browser. experimentalCSSNameFor() methods for working with experimental browser properties in a future-proof manner.

  • Added SC.UNSUPPORTED constant.

  • The animate()function can now accept a target argument as well as a method.

    • if no target is given the view itself will be the value of this in the callback. Previously, this would be the layoutStyleCalculator, an internal object of SC.View.

    • the animate() function is thoroughly documented now, including notes on how to hardware accelerate position changes.

  • resetAnimation() has been renamed to cancelAnimation(), because reset indicates ‘revert’, but the default behavior doesn’t revert the layout. Reset animation didn’t work before either.

    • added SC.LayoutState enum for use by cancelAnimation().
  • fixes hasAcceleratedLayer to be dependent on the layout. Previously, adjusting a non-accelerated layout to an accelerate-able layout failed toupdate the property and hasAcceleratedLayer would be incorrect.

  • Makes invokeNext trigger the next run of the run loop if none is scheduled. This makes invokeNext the appropriate method to use when needing the DOM to be up-to-date before running code against it.

  • Refactors SC.RenderContextto simplify the API, introduce consistent naming and make it behave appropriately. This should remove the guess work about whether a function is or is not supported by the context and make it easier to remember the names and parameters of each method.

    • There are now 3 similar access methods: attrs()classes()styles()

    • There are now 3 similar add methods: addAttr()addClass()addStyle()

    • There are now 3 similar remove methods: removeAttr()removeClass()removeStyle()

    • There are now 2 similar reset methods: resetClasses()resetStyles()

    • Duplicate and inconsistent method names have been deprecated: css()html()classNames()resetClassNames()attr()

v1.10 Upgrade Tips: invokeFoo Developer Warnings

written by dcporter

Upgrading your project to 1.10 is a no-brainer – even if you never get around to adding a little easy flare with view transitions, you get a free performance boost from the plugged memory leaks and Tyler’s great work refactoring SC.View. 1.10 also makes some changes under the hood which take some previously not great practices and turn them into warnings or breaks. These are good changes, and enable a lot of the efficiency gains that we packed into this release, but there are some previously-common practices that trigger some new developer warnings, so I want to go over what’s causing them and how to fix it.

You should read this post if you’re receiving unexpected developer warnings to the effect of “Developer Warning: invokeOnce called outside of the run loop. This can cause problems in production code if not addressed.” I’m going to cover these possible causes of this issue:

  • Misusing .create() when defining your view layer

  • Using SC.MenuPane.create() when defining your view layer

  • Using SC.FileFieldView ever

  • Handling browser events directly, without setting up a run-loop

The Background

Some quick background. If you’re on a deadline and just need to get things fixed, you can skip this section (though I hope you’ll come back later).

The RunLoop is one of SproutCore’s key foundational technologies. It allows SproutCore to queue changes in an orderly fashion, powers the bindings/properties/observers KVO engine, and allows for the progressive UI rendering that’s crucial for SC’s speed and scalability. It also enables the crucial invokeLast, invokeOnce and invokeNext methods. This means that any SproutCore code, whether at the framework or application level, has to run in a RunLoop, or lots of important things don’t happen. It’s why you use SC.Request instead of XHR, and SC.View events instead of DOM events.

Previously, if invokeOnce and friends were called outside of a run loop, they would simply fire one up and continue on their way. This was a flexible response, but it had some unintended consequences, and was certainly not “correct”. It also enabled some bad practices, which is what this post is about.

In v1.10, the invokeFoo methods no longer fire up a run loop if needed, instead throwing a developer warning to the console. This puts more responsibility on you to ensure that run loops happen when needed, and that you’re not inadvertently triggering certain code from outside a run loop.

So if you’ve upgraded your app and suddenly see a string of developer warnings about the run loop, here’s what’s probably causing it, and how to fix it.

1. SC.View#create

The most likely cause of the run loop developer warnings is the use of the create method when defining your view layer. Unless you’ve gotten fancy and have a method which is manually creating and appending child views, you probably not be using SC.View#create. (Creating views is expensive, so SproutCore goes to great lengths to defer it.)

The best practice for defining your view layer is to create an SC.Page instance, and populate it with your application’s panes. You should use SC.FooView.extend or SC.FooView.design, and the same with pane classes. Then, when you first append your pane in your app’s main method, make sure to use MyApp.myPage.get(‘myPane’) rather than MyApp.myPage.myPane. You should do this all the time anyway, but SC.Page’s superpower is taking uninstantiated views and panes and lazily instantiating them the first time you get them.

This important best practice will speed up the launch of your application by deferring view creation until the views are needed, and is highly recommended. It also happens to be the solution to this set of developer warnings: by extending or defining your views, and letting an SC.Page handle instantiation, you ensure that things happen when they’re supposed to.

More details on this, if you’re curious. (Again if you’re not, you can skip this paragraph.) First, creating views is expensive, so SproutCore goes to great lengths to defer it. Second, it’s important to keep in mind that all of your application’s initial setup (the actual parsing and execution of your application’s files) happens prior to your app’s main method, and therefore before the run loop gets going. Finally, SC.View#create calls invokeOnce to schedule deferred layout updates, for great efficiency gains when your code makes multiple layout adjustments in one runloop.

2. SC.MenuPane#create

The next most likely reason for these warnings is SC.MenuPane or its subclasses, which tend to be used in ways that encourage premature creation. This is a specific case of the above create issue, but I want to call it out specifically. If you’ve got any PopupButtonViews, for example, which take a menu property, make sure that you extend or design that MenuPane.

3. SC.FileFieldView

Another problematic view with v1.10 is the third-party framework view SC.FileFieldView. Unfortunately, this old but popular view badly breaks v1.10 applications, and you will need to transition away from it. The currently best-maintained branch of this view’s repo is maintained by LearnDot here (shout-out to the inimitable Joe Gaudet), which contains a number of better options, for example the excellent single-file-only view SC.FileChooserView.

4. Handling Browser Events Directly

Finally, it’s also possible that you’re actually running your own methods outside of a run loop. The most likely cause of this is direct handling of browser events that should be proxied through SproutCore. For example, you should be using SC.Request instead of directly using JavaScript’s XMLHTTPRequest, and you should be using SC.View’s event handlers (‘click’, ‘doubleClick’, ‘mouseDown’, etc.) instead of trying to set up your own DOM event handlers. If you absolutely need to handle your own browser events – for example, if you’re running WebSockets, for which SproutCore does not (yet) have a wrapper class – then you need to make sure to call SC.RunLoop.begin() at the beginning if the handler, and SC.RunLoop.end() at the end. (You can also wrap some code up in a function and pass it to SC.run(…), which accomplishes the same thing.)

Wrapup

These tips and best practices should get you well on your way to a warning-free console, and a solid upgrade. If you still have any questions or are still seeing warnings, please hit up the mailing list, and if you’ve discovered a new cause of the run loop developer warnings, I’ll be sure to add it here!