Video hosted by Apple at devstreaming-cdn.apple.com

Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2012-601
$eventId
ID of event: wwdc2012
$eventContentId
ID of session without event part: 601
$eventShortId
Shortened ID of event: wwdc12
$year
Year of session: 2012
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2012] [Session 601] Optimizing ...

WWDC12 • Session 601

Optimizing Web Content in UIWebViews and Websites on iOS

Safari and Web • iOS • 52:26

Learn the best practices for enhancing the functionality and performance of your web content on iOS, whether it is in a website or native app. Gain proficiency with the remote Web Inspector, see how to reduce the overall memory footprint of your web content, and dive deep into the details of WebKit drawing and compositing to ensure that scrolling and animations are always smooth. Discover new features in iOS 6 that allow for tighter integration between apps and websites, enabling deep linking to content within an already-installed app or letting users purchase an app with just a few taps.

Speakers: Paul Knight, Joseph Pecoraro

Unlisted on Apple Developer site

Downloads from Apple

HD Video (540.4 MB)

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Hi everyone, thanks for coming out today. My name is Paul Knight, I'm an engineer on Safari on iOS. This is Optimizing Web Content in UIWebViews and Websites on iOS. And I want to start out by talking about a kind of a bold assertion. The web is crucial to modern technology. I know this is like a really hard sell to a room filled with web developers, but hear me out here.

You know, these devices that we're carrying around with us all the time, these iPhones that we keep in our pockets, that give us instant access to all sorts of information wherever we are, they really change the way we work with content and work with information. And whether we're developing a website or a standalone web app or delivering this content through a UI WebView and a UIKit app, your users really expect a great experience. And this is especially important on iOS. It's one of the core tenets of iOS.

It's one of the core tenets of the platform, that users want a great experience. And one of the most important things about delivering a great experience is performance. So we're going to talk a little bit about web content performance and optimizing your web content. We're going to start by talking about some hidden costs and surprising slowdowns in web content.

Things that look fairly obvious at first, but when you kind of get under the hood and look at what's going on, turn out to be really heavy in terms of implementation. We're going to look at how WebKit takes your code. Your HTML, your CSS, your JavaScript, and turns it into websites on the screen. We're going to actually look into what WebKit does in a way that we've never really done before in WWDC. And we're going to talk about some techniques for improving painting performance.

And lastly, we're saving this for the end, we've got some cool new features in iOS 6 to talk about. Things that will improve performance in your app and that you can use when you're working on your website and your app. So let's get started with hidden costs and surprising slowdowns.

And when I say hidden costs, I don't mean that at Apple we're trying to hide away surprising little performance gotchas. We're not in the hallways, "Hey Joe, I just figured out a way to make this guy's website 50 milliseconds faster, high five!" You know, that's not what we're talking about here. What we mean is that sometimes there are things that when you're just staring at your HTML or your CSS, it seems really straightforward. But in terms of what's going on behind the scenes, there's a lot more.

So there's three different areas we're going to talk about. We're going to start with image decoding. So I have a photograph. It's 960 by 1280 pixels. It's not that large. And you know when you're saving this photograph out from your image editing program, there's a bunch of different options you can tweak in terms of quality and type.

So we could say this is a low-quality JPEG that might be 100 kilobytes on disk, a higher-quality JPEG, the file's a bit larger, or we could be crazy and save it as a lossless PNG. I hope you're not serving this on your website. but this is an option.

This is the encoded on disk form of the image. In order to actually paint this image onto the screen, we first have to decode it into memory. If we look at just a really small region of this photo and blow it up a lot, all of these pixels, every one of these pixels that make up the photograph have with them four pieces of information.

There's a red, green, blue, and alpha component, and every one of those components takes a byte of memory. When we take this image and blow it up into memory, this image takes 4.7 megabytes of memory. of continuous allocated RAM. And what's really interesting is this is true whether or not we're talking about the low quality, 100 kilobyte image on disk, or the lossless, much larger image in the PNG format. In order to paint it, we're going to have to decompress it into 4.7 megabytes. Width times height times 4 bytes. Now, this isn't bad, necessarily. It's simply a fact of how images work.

And it's worth keeping in the back of our minds. So, for example, let's take a look at-- this is the iPad user guide manual. It's a web app, and we have all these images on the left side of the screen, the sort of chapter icons. And we could download all of these images individually, all 20-something images.

But we're trying to minimize HTTP requests and the overhead associated with loading this page. And one really popular technique for doing that is taking all of these individual images or sprites and stitching them into one sprite sheet, one image that we can download. And in order to show just a part of this image, we use a little bit of CSS trickery.

We have our element set its width and height to the size of the sprite we're trying to show, and then use background position to slide the image around behind the actual element. And this is a great technique for reducing the number of individual downloads that you need in order to load your page. But if your sprite sheet looks like this-- and so we're not trying to point any fingers, Ted.

We're not trying to point any fingers. So what I've done is I've taken this real sprite sheet from a popular website, probably several hundred thousand uniques per day, and I've colored the areas that are actually being used, the areas of this image with image data. They're colored red.

And then the rest of the image has that sort of tiny gray and white transparent checkerboard showing through. And this image is not that big on disk. The data that gets transferred over the network is only 28 kilobytes. But when we decode this image into RAM for painting, it requires 5.7 megabytes of RAM.

That is a lot of memory. And if we were to take this image and sort of repack it by hand, we could do it into a much smaller image. So small I'm going to actually scale it up a little bit. Whereas this old image was 84% unused space, this new image is only 5% unused space.

And the size on disk doesn't really change. This doesn't affect the download speed at all. But what it changes is the amount of RAM we need to actually draw this on the screen. And this is not just a high score thing. We're not trying to get down our RAM, the amount of used RAM down just because it's a number we can brag about. When the system is under memory pressure, the system behaves worse overall.

The JavaScript collector is going to have to run more often, introducing pauses in your application. Other applications are going to be exited more often. You're going to get memory warnings in your UI application more often. The overall user experience is worse. In the worst case, your app might even get killed for using too much memory.

Now you might be thinking to yourself, "Hey, Paul, you know, this is just..." No, most of you have already forgotten my name. And that's okay. So you're probably thinking to yourself, "Hey, guy on stage, this is just like one example, right? People aren't doing this all over the place." These are all real sprite sheets that we've collected from real popular websites. The one on the far left is my particular favorite, 99.7% empty space. These are real savings in RAM that we can get just by repacking these images.

So, image decoding. They have a cost associated with them. There's the size of an image on disk, but there's also the decoded size of the image. And you can get a rough estimate of that by taking the width times the height, multiplying that by four bytes. Avoid wasted space in your sprite sheet that's just memory that you're not going to be using.

And as a more general rule, avoid using images that are larger than you need. Don't download a 1,000 by 1,000 image if you're just going to display that at 250 by 250 pixels on screen. And likewise, don't download high DPI content for non-retina devices. In fact, if you want some good ways to do that, there's going to be a session later this week on just high DPI content, and there's some really cool new stuff that you can use for that.

So that is our first area of hidden costs image decoding. Next, let's talk about JavaScript loading. So images are just one kind of resource on your site. You also have a bunch of JavaScript files. And in this particular example, I've got four scripts up here. And when I load this page, when I pull out my phone and I'm on a cellular network, I tap on a link to load this page, it sure feels like it takes a long time to load. In fact, it feels like nothing is happening for like 14 seconds. The progress bar is filling up, but nothing else is happening on screen.

So we can use the new Web Inspector for iOS. And if you haven't heard about this yet, the Web Inspector works on iOS. You can take on a Mac computer, open up a Web Inspector and attach it to an iOS device. This is really cool. There was a session about this just a few minutes ago in this room.

But if you missed that, you can come by the labs and we'll get you up to speed. So in the instrument navigator, it's called on the top left, it's the third navigator, there's this network requests timeline. And in here, we get a really nice sort of waterfall timeline view of all of the resources that we're trying to load, when they start loading, when we get the first byte of information back from the server, and when that resource finishes loading.

And we don't get a really complete picture of why it feels like nothing is happening when we're loading our app just from this view. There's a couple things worth mentioning. There's this red line right at the end. This is when the onload event fires, when all the sub-resources on the page have been loaded. And there's this one sort of suspicious-looking JavaScript file at the bottom. It's got a 12-second latency, 12 seconds between when WebKit first asks for this resource, and we finally get one byte back from the server. Now, this seems like a lot.

This almost seems impossible. But on a cellular connection, latency measured in the seconds is pretty much the norm. And if this is maybe another server hosted by a third party, and it's really slow today, they're under a lot of load, you might see really high latencies. And maybe we're getting the feeling that this one script is actually preventing the page from rendering at all. In the layout and rendering timeline in the Web Inspector, we get a list of events. And the interesting thing here is that we're seeing a painting event. But they're only happening right at the end of the load after 14 seconds or so. All right.

So what's going on? It turns out that the way HTML works, the way the HTML and JavaScript execution model works is that JavaScript will pause parsing. So you have your HTML parser, you have an HTML document, and the parser starts at the top. It reads the doc type, HTML tag, head tag, title tag, and then it gets to a script tag.

And the parser has to stop and wait until all of the JavaScript in that script tag has been fully downloaded. Then the JavaScript executes immediately, and then the parser keeps going. Meta tag, link tag, script tag, and it stops again until all of that JavaScript has finished downloading. And this is just sort of a fact. It's the way things are specified to work. It's how JavaScript expects to run inside of web content.

So we want to avoid this. We want to avoid this synchronous JavaScript loading behavior. And there's two attributes we can put on our script tags to use either an asynchronous or deferred execution model. The idea behind both of these is the download for that JavaScript begins immediately, and it doesn't block. So the HTML parser keeps going down and builds the DOM tree and sets everything up on the page.

Paul Knight, Joseph Pecoraro If you add the async attribute to a script, they're going to be executed as soon as they're loaded. So the download kicks off immediately, and whenever that JavaScript file is fully downloaded, that's when it's run. It's run at the next good opportunity. Deferred scripts, on the other hand, make a guarantee for the order of execution of the scripts.

So deferred scripts will always be executed in the order that they're listed in the HTML, and they're always executed just before DOM content loaded. So that means DOM content loaded is delayed until all of your deferred scripts run, but sometimes you need to make guarantees about the order.

So going back to my few scripts here in my page that's taking forever to load, these first three scripts, I need them to run in a certain order. They're dependencies of each other. They expect certain objects to have been constructed. But this last one, I don't care when it runs. As long as it runs sometime, that's good enough for me. So I'll add the defer attribute to the first three and the async attribute to the fourth one. And if we reload and look at the timeline, the timeline looks a little bit different.

There's still the onload event at the end, and now there's this new blue dotted line. This is when DOM content ready fires. But more importantly, if we look at the layout and rendering timeline, the first paint happens just four and a half seconds into the load. That's because the parser was able to get all the way through the document. We were able to load enough resources to actually draw something on the screen.

And even better, there are several more painting events as more content is loaded, as those images start to come in. And we sort of incrementally render the page. This is a much better user experience. Even though the page doesn't load any faster necessarily total, the first time we paint the page happens much earlier and the user can start interacting with the page.

And there's one more thing I want to talk about with JavaScript loading. And that's, we've seen a lot of JavaScript libraries, a sort of pattern where you include one script at the top, and then that script will load all of its dependencies and plugins, either by dynamically inserting script tags or using XML HTTP requests.

And if you do that, you end up with a picture that looks something like this. You end up with this staggered stair-step waterfall. Because we don't know that you need any of these dependencies, any of these other resources, until we finish loading the first top-level resource. And remember, we're talking about cellular data connections where latencies are measured in seconds.

So every single time a resource is chained onto another resource, that adds multiple seconds to the total page loading time. Instead, it's much better to just list all of the resources you need, all of the stylesheets, images, CSS files in your HTML. And then we'll be able to start downloading them immediately.

[Transcript missing]

If you need to know when the DOM is ready, there's been a lot of confusion over which browsers support which events. These days, DOMContentReady is a standard DOM-specified method that's part of the spec. So you can use DOMContentReady to know when it's safe to start attaching all of your event listeners and manipulating the DOM.

And if you're looking for the position of an element, instead of like taking an element's offset top and adding it to its parent's offset top, and again, to its parent's offset top, and walking the entire chain, there's these methods, get bounding client rect, and that returns a rect object with all of the geometry information. It's really fast.

So there's a lot of built-in power to the DOM. This is just a sample of what the DOM can do these days. So if we're looking to these libraries maybe to round out what we think are missing features, maybe they're not missing anymore. It's worth taking a look at. And in general, the challenge I'm sort of making is, if you don't need the JavaScript framework, try getting rid of it. They're very large resources. They cause a tremendous amount of memory allocation.

Paul Knight, Joseph Pecoraro Again, if you're using it for good reason, cross-browser compatibility or something like that, by all means use it. They're great tools. But if you're just loading some resources out of your application bundle, maybe it's time to reevaluate your usage. So that's the second area of hidden costs and surprising slowdowns, JavaScript loading. And the last one I want to talk about is layout calculation.

So I have this web page. It has about 500 elements in it. And when I tap on this button on the web page, I want to increase the height of all of these elements by 10 pixels. So I use querySelectorAll to get all of the things. And then for each one of the things, I grabbed the offset height and then just set an inline style that's 10 pixels taller. It's a little silly, but it works. Except it works really slowly. In fact, I tried this on my iPad the other day, and it took almost 700 milliseconds to go through these 500 elements.

[Transcript missing]

Well, it turns out in this line where we ask for the offset height of the element, we're asking for the current calculated up-to-date layout information about the element. We're asking, what is its height right now? And then in the very next line, when we change the height, we invalidate all of the current up-to-date calculated style information.

And so the next time through the loop for the second element, WebKit is going to have to re-layout the page. It's going to have to make sure that nothing else is moved around and that when it returns the offset height for the second element, that is still up-to-date and accurate. And we keep doing this. We keep invalidating the current layout information, then forcing the current layout information, invalidating it, and forcing the layout over and over and over again. So it's going to look a little bit silly.

But instead, if we create an array, went through all of the elements and pulled out the heights at once, and then in another loop, updated all of the elements at once, the code runs faster. In fact, it runs almost 100 times faster. And the best part is, in the events list, there's just one recalculate styles, one layout, and then all the painting. That's exactly what we're looking for.

So it's usually fairly obvious when we're invalidating current layout information, adding or removing a class, inserting a style tag, manipulating the DOM, or inserting an inline style element. But what's often not so obvious is what methods require up-to-date and current layout information. So if a method has either client or offset in its name, that's a really good sign that it could force a layout if layout is out of date.

GetComputedStyle, GetPropertyCSSValue, of course, they're explicitly asking for the up-to-date style information. But there are a couple methods that aren't so obvious. InnerText is one of them, actually, because InnerText is not going to include any text that is inside of an element with display none set. So it needs current up-to-date style information, and it could force a layout.

So avoiding unnecessary layout. Use the Web Inspector timeline. It'll show you when layouts are occurring, when we're recalculating styles, and you can figure out if maybe there's a section of your JavaScript code that is forcing layout unnecessarily. Operations that need current style information will force a layout. And if you're finding that you're forcing layout too often, try batching all of the changes that are updating style information towards the end of your JavaScript. that will make a huge difference in the speed of some of your methods.

And that is the third area of hidden costs and surprising slowdowns. But we are really just sort of scratching the surface here. These are just like the first couple of things to look at. If we want a really good understanding of how we can improve painting performance and understanding what WebKit is doing behind the scenes, we're going to need to peel back a few more layers and look a little bit deeper. And to do that, I'm going to turn things over to my colleague, Joseph Pecoraro, who's going to talk a little bit about layers and WebKit rendering.

[Transcript missing]

So we can easily create this page and with the body controls when we need them. But that's not the whole story to painting. We don't want to paint the entire page all the time. Take this page, for instance, that video is going to be updating on the screen constantly if it's playing.

So the best thing we can do, and this is what WebKit does, is it cuts out a portion of the screen and hands it to the video renderer and says paint the video here. This is great. It can update as frequently as it wants without the page having to worry about it.

WebKit does have to remember to take the video controls, group them in their own box, and paint them on top of the video when they need to display. And I think you can see where I'm going with this. These are the layers that WebKit is managing under the hood for the page. So to give you a better visualization of that, here's the page as we as users see it.

But under the hood, WebKit has taken that video. It's going to push it into content that's floating on top of the page. And the button controls, when they need to be displayed, are again in another layer that's sitting on top. And this really is a layer. It's got a width, a height, dimensions, and painted content.

So, to finish this painting story, when we have layers, what we're going to do is we're going to flatten them into a single image. This is called compositing. It's something that the GPU does really well, and it's something that we'll see more, and it's one of the reasons why using layers is efficient for painting and animation, because we can composite so quickly.

So, WebKit has a number of different types of layers. What normally distinguishes them is whether or not they have a backing store and the size of the backing store. So, in this example, those video controls that were floating on top are a small layer with a small backing store. That backing store is a memory allocation that contains image pixel data. It's got a width and a height.

It's exactly like the decoded data of images that Paul talked about earlier. It is the painted contents. So, when we have that painted content, we can do some really cool stuff with it. We can send that image to the GPU as a texture and manipulate the GPU efficiently, things like transforms, rotation, scaling, opacity to take advantage of blending with background layers.

This is great. This is called hardware acceleration. This is how we can get animations to work so smoothly. But of course, back-end stores have a cost, and that cost is memory. So if you have a 100x100 CSS pixel div, about 40,000 bytes, the back-end store might actually be a lot larger depending on your device or how the WebView is displayed. So on a retina device, the high DPI device, there are two device pixels for each single CSS pixel. So we've doubled the dimensions of the back-end store to 200x200, or about four times larger.

And if you zoom in on the web page to say 2x, we've again doubled the dimensions necessary for the back-end store. And as back-end stores get really large, WebKit's going to potentially use up too much memory. So it's going to mitigate that risk of using too much memory and create a different kind of layer, which we call tile layers, for large layers. It's going to break it up and paint it in pieces. And there's always one case where there's a problem.

It's a tile layer, and that's the main page's content. I think you can see why. I'm sure you've all gone to a page where if you scroll to the bottom, new content gets loaded in. And you scroll again, new content gets loaded. You could have an infinitely large page. WebKit's not going to create one large layer to store all of the painted contents of that. Instead, it's going to optimize. It's going to paint only the visible portions of the layer, and it's going to paint it in smaller back-end stores. That's what these tiles are.

So we know a couple of the different types of layers that WebKit creates. How do they get created? What's the cause in HTML and styles that causes these layers to be made? So there's always that one tiled layer for the main page. That's where content draws if it draws nowhere else. But the rest is all elements and styles. So elements that paint frequently, things like video or canvas, those will get a layer.

3D transformations, things that the GPU has been optimized for for years, rotation, translation. It's important to note here that these transforms, anything with the WebKit transform style, are only affecting how the element is painted on the page. It doesn't affect how it's laid out. It doesn't move the position of the element and cause a recalculate style and relay out. This is why it's so efficient to do transforms for animations. Any other enhancements to content, things like opacity, reflections, animations, transitions, these may cause layers, and in most cases, they do.

Special cases where content moves separately from the content of the main page. So on iOS it would be position fixed content or touch scrolling. And finally, and this is sometimes the gotcha, is any content that overlaps other content in a layer. So if we think back to the original example, the video controls were displaying on top of the video, so they needed to be pushed into a layer in order for the page to be drawn properly.

So there are a number of tools that you can use for quickly editing your page and measuring performance. I'm going to walk through some now and then do a demo. So the Web Inspector, really powerful. There's so much it can do. Here we're going to narrow in on just editing styles. So you can tweak the styles of some particular element.

My favorite thing to do is open up the style sidebar and check on or off an individual style to see exactly how that affects the page. And then you can see the performance on the page. You don't have to go back to Xcode, tweak your app and run it again. You can just do it all on the fly.

WebKit sits on top of the platform graphic libraries Core Animation and Core Graphics. So the instruments, the native debugging tools that work with those will work just fine with web content. Core Animation has an instrument where you can turn on certain debug options like show blended layers or flash screen updates, and that will work just fine with web content to measure performance.

If you want to measure the memory performance and cost of the layers, there is the VM Tracker instrument. If you use this and take a snapshot of the page, you can see the different malloc zones and their sizes. Make a change to the page, take another snapshot, and you can see how it changes over time. The WebKit backing store allocations show up under the I/O Kit malloc zone. And finally, what we'll spend the time in the demo talking about is WebKit's own debug setting.

So there's a user default, WebKit show debug border. If you set this on your application and relaunch it, whenever it paints web content, it will, if there's a layer, paint a border around that layer. And there's different colors for the different types of layers. But rather than explain them here, I'm going to actually go into the demo and show you.

Excellent. So I've got the sample application, which you can download from developer.apple.com, and it's got a number of pages that I'm just going to load up and go through in order. So this first example is a page that has a widget that's common on many pages and apps, and it's got an issue with flashing. We're going to go through, understand why it's flashing, and then fix this problem.

So at the top, we've got a, well, let me zoom out. At the top, we've got a scroller that's using touch overflow scrolling. It performs great at this zoom level, but if I zoom in or out, you can see the content inside it flashes. Okay, you can see that. On the bottom, we've got a similar slider, but it uses touch events and it tracks my finger, but when I release it and it animates to a page boundary, it flickers for just a moment.

[Transcript missing]

And we'll see what's going on here. So here we can already see the tiles for main page content are drawn with their borders. But if I load up this example and zoom in, there's a lot going on here. So around the main page, there's this yellow border. It's difficult to see here, but that just means a container layer. Any page that has layers will normally have this yellow border around the body, meaning that there are layers inside it. This is kind of the root containing layer.

But the scroll on top has a couple The Orange Border Contents of the Scroller are so large that WebKit is optimizing and only painting what is visible. The Light Blue Cyan Border Contents are masked or clipped content, which is certainly the case when clipping around the edges of the scroller. The Scroller at the bottom that follows touch events doesn't have any layers visible right now, but when I release my finger and it animates, it gets the same borders and the same properties as the scroller on top. There's this orange tiled layer and the blue for clipping.

So what's going on here is the content in these layers are so large that they're being painted in tiles. And in order for WebKit to determine what's on screen and what it needs to paint, that's an asynchronous operation. And you get a slight flicker as it's repainting or recreating the layer at a new zoom level or pushing content in a layer for the first time and having to draw it for the first time.

So an approach to fix this is to make a smaller layer, to break up that large layer into smaller pieces or eliminate it altogether. An approach that we can take here is push each of the individual items of these sliders into its own layer. That's of course going to take a cost, some extra memory. But we're making a smaller layer that WebKit can manage without tiling. So I've got a button in the corner that does just that.

It just set a WebKit transform translate Z on each of the individual items inside the scrollers. And you can see now they have a green border around them. So now as I zoom in and out of the page, WebKit doesn't need to paint that content in pieces. It's now in one chunk that we've kind of hinted to WebKit is important and small enough that it can be painted. And small enough that it can keep around. The scroller on the bottom no longer jumps into and out of tiles. It's always there, always visible and performs much, much smoother.

So, second example, while this loads. While I was doing research for this talk, I went on the internet and I did a web search, what information was out there on performance on iOS. And there were some great articles, but there were also some that were recommending what may have been useful in the past, but is definitely not useful anymore. A bad practice of adding 3D transforms to everything on the page. One even went so far as to say this will solve all your problems on iOS.

Paul Knight, Joseph Pecoraro So, I decided to give it a shot, you know, take this blog's advice and do it on a medium to large size page. So, I went to the New York Times and I did just that. I injected some style that would put layers on everything. And the results were pretty dramatic. The scrolling performance dropped from 60 frames per second to 20. The double tap to zoom takes about two to three times longer to recover. The memory consumption skyrocketed from 30 megabytes to 100 megabytes. 140.

So I know this is a pretty contrived example, but there's actually a lot that we can learn from it. If you turn WebKit's debug borders on in your own application and you see any issues like this, it would be a great quick performance fix to improve the performance or get a little boost to the painting performance in your app. So on the left, we've got this blue clipping masking border.

It's difficult to see here, but it was the same as in the previous example. And that means something like overflow hidden or overflow scroll being applied as a style to that content. If that's not necessary, it's just extra work that WebKit's doing every time it has to paint that portion of content. So removing that style if it's unnecessary will get you a small performance boost.

And static content like the text that's in a yellow or a green layer is just going to be wasted memory. And removing the style that's causing that is going to save your app and the system memory and get you better performance overall. It may also be indicative of a problem with CSS where a selector is more general than it needs to be as applying styles to something you didn't expect. I'm going to wrap up with one nice example. This is a pretty famous example out there. I'm sure it's a good example. It's come up in a few WWDCs in the past.

And it's about animations. And this page is pretty complex. It's got a lot of leaves falling down the page. It's got this clipping border around the edges. It's got a label with opacity that's on top. And what I want to stress here, and I'm going to turn off the debug border so we can see what it looks like normally.

What I want to stress here is that in no way in this content was there any 3D transforms applied. This was just written like a normal page, keeping in mind that we want to keep layers small. I want to say don't prematurely optimize your page by adding something like a 3D transformer, trying to trick WebKit into doing something it doesn't necessarily need to do.

The WebKit does a really good job, and it's improved over the past few years, on making sites perform as best they can just out of the box without you having to make any extra changes. In review, WebKit layers are great when you want smooth animations, when you want fluid interactivity.

You want your content to be in layers when you want it to animate, something like that. But there are cons. You definitely don't want to have unnecessary layers. The memory cost does balloon pretty quickly, and it can put pressure on your app, maybe even get it killed. Large layers will cause tiling and flashing.

You want to avoid them when possible, break them up into smaller layers, and hint to WebKit in those particular cases. And something that app developers should be aware of, anytime you're using the GPU, you're consuming power. So a lot of GPU usage, like a lot of layers, will cause a little bit extra power consumption.

And remember, use the debug tools. They're there to help you find and fix issues, and it makes it really easy to find and understand what WebKit's doing now, and then fix issues that you come across. So with that, I'm going to hand things over back to Paul to talk about some new iOS 6 enhancements. Thank you.

Thanks, Joe. So that's a look at what WebKit is doing underneath the hood when it's trying to take your HTML, CSS, JavaScript, and paint it onto the page. But I want to step back a little bit. and talk about this in the context of your application, where all of this fits in in your app. So in previous versions of iOS, UI WebView used what's called a synchronous rendering model. And it's this idea where some work is going on in the main thread.

Your app is responding to some touch events, maybe, that's causing some content to update, and WebKit needs to redraw. So it's going to take over the main thread and do its rendering there for a little while. And then when that's done, the main thread can go back and do whatever it needs to do, respond to UIKit events. And this can happen a few times. It happens regularly throughout the lifecycle of your app.

But the problem is this area where rendering is happening on the main thread. This means that the main thread can't respond to user events. You can't handle delegate callbacks from CF Network or any of those sorts of things that we do on the main thread. The main thread is blocked, and the application appears to freeze.

So this is not great. How do we fix it? Well, we fix it like we do any sort of long-running task on the main thread. We pull the rendering off into a new thread, a background thread, the rendering thread, and this leaves the main thread free to do whatever sorts of stuff it needed to do.

The idea being that WebKit rendering happens on a background thread, it doesn't block the application, and this is going to be automatic for all UIWebViews that are built with the iOS 6 SDK. So the SDK that you downloaded yesterday, if you rebuild your app with it, all UIWebViews will start using this asynchronous rendering model. Scrolling is going to be so much smoother. It's really fantastic. It performs great.

I agree, it's totally awesome. But one word of warning, this doesn't change the API. So still, use UIWebView only from the main thread, just like all the UIKit classes. Okay? And one other thing I want to talk about rendering, and we sort of touched on this a little bit earlier, is this idea that when you're loading a web page, it doesn't all just pop in at once. It sort of loads in chunks.

So as you're loading a web page, some text will pop in, and then as an image will load, it draws, and then as more images load, they draw. And this incremental rendering is fantastic. It's great, especially for websites and web content, where, especially on a cellular connection, it might take a while for very large images to load. And so you want to let the user start interacting with the page as quickly as possible.

But if you have this sort of incremental rendering with an app interface that you've implemented with a UIWebView, it doesn't feel so great. In fact, it might even feel a little bit broken as parts of your interface are kind of drawing and flashing in as they're being loaded. So what we really want is we want a way to suppress this incremental rendering. We want to avoid rendering partially loaded content.

And in iOS 6, there's a new property on UIWebView called SuppressesIncrementalRendering. Set this to Yes, and WebKit is going to try to avoid rendering things until they're fully loaded. This is awesome. Use it for any case where you're building a significant amount of your application UI with web content. But we don't want to turn this on all the time. Again, if you're loading sort of regular website content, incremental rendering is a good thing.

So we've spent all of this time building a great website, and then we've spent all of this time building a great app, and we want to let all of the users who visit our website know about this app, right? And so what happens? Well, the user visits your website, so I have this San Francisco pocketbook guide app, and they're reading an article about a seal rocks over by Lincoln Park, the park named after the president, not the band.

The user's kind of, they're interested in this content, they're reading it, and they're maybe about to start panning or tap something else. When this happens, the user gets punched in the nose. Or at least it feels like that sometimes, right? Big pop-up ads take over the full screen. We've seen this on all sorts of sites. And good luck hitting the close button in the corner. I can never do that myself.

So there are a bunch of problems with this approach. They interrupt the user's browsing. The user was interested in this content. They were trying to read this. And suddenly, there's a big ad in their face. The user may already have the app installed. And there's really no way for web content to determine this or detect this.

And that's a privacy issue. You know, you don't want any website knowing that-- well, I don't want any website knowing that I've got Sallie's Spa installed on my phone. Maybe unless I can tell that website that I've got expert scores in all 40 levels. The point is it's a privacy issue.

And lastly, say the user does have the app installed. So they go to the home screen. They find your app. They launch it. And it opens to something completely different. It opens to whatever article they were reading the last time they had your app open. Or it opens to the app's home screen. The user's context is lost if they move from your website to your app.

So in iOS 6, we think we have a better solution. We're calling it the Smart App Banner, and this is what happens. When the user visits your website, a new banner slides in. It has your app icon, a link to the App Store, information about the current rating and the cost, and all this sort of great stuff.

And if the app is already installed on the user's device, that view button changes to an open button. So tapping on the app banner links the user directly to your application and can also pass in information to maintain the user's current state. This is really cool. It works not just on iPhones, it works on iPads, and it totally works in landscape, too. It's really neat. So how do you add this to your site? It's a new meta tag. It's called Apple iTunes App. And you add in your app ID. This is just a unique ID that's associated with your app. You can get it from itunes.apple.com slash linkmaker.

And there's also this optional app argument parameter. It's a URL. It's a real URL, so it needs a scheme and it needs a colon and that sort of stuff. But the actual format of the URL is totally up to you. So I'm using a private URL scheme that includes the name of the article that the user is reading. But in your case, this could be as simple as maybe the HTTP URL of the page that the user is currently on. Then when your application opens, your app will know how to translate your website URLs into view controller stacks.

Paul Knight, Joseph Pecoraro For example, your app doesn't need to register for this URL scheme. Because the app is associated with the app ID, the correct app is opened no matter what URL schemes you've registered for. It's really cool. Inside of UI application, the method application open URL, that URL parameter is where your app is going to be passed this information. So that's where you would parse this URL, set up all your view controllers to restore the user state.

Paul Knight, Joseph Pecoraro So the smart app banner, it's new in iOS 6. You can invite the user to install your app from your website. And the really cool thing is you can maintain the user's context. So if they're reading about something, they can continue reading about it in your app instead of losing everything that they're doing.

So we've talked about a ton of stuff today, and let's try and wrap it all together. Web technologies are a great way to deliver application content. I don't need to tell you guys this. You guys are the ones who are building the really cool application content that's being delivered to all of our users.

But the way we write web content has a direct impact on the overall user experience. It's not simply that WebKit is a black box and every year Apple makes WebKit a little bit faster and that makes all web content a little bit faster. As web content authors, we can actually make changes to the way we code that have direct impact on the overall performance of our apps today.

Look at resource loading times. Make sure that you don't have any JavaScript blocking loads or you're not chaining resource imports unnecessarily slowing things down. Avoid unnecessary memories and layers. This is huge. Memory pressure is one of the biggest things that can cause your app to feel slow on iOS. So make sure you don't have sprite sheets with a ton of wasted space. Avoid images that are larger than they need to be and don't set Transform Z on everything. And really sort of the theme is understanding a little bit of the internals of WebKit.

Just keeping some of these ideas in the back of your mind means that it's a lot easier to build really great web content that performs really well. Lastly, take advantage of these really cool new features in iOS 6. You're automatically going to get asynchronous rendering in all UI web views. It's totally awesome. And you'll also be able to take advantage of the ability to suppress incremental rendering for app content and use the new app banner to link people to your app.

So, for more information, you can always contact Vicky. There's great documentation for the Web Inspector and some other general best practices for web content authoring on the Safari Developer Center. And there's always devforums at apple.com. Paul Knight, Joseph Pecoraro So, for more information, you can always contact Vicky. There's great documentation for the Web Inspector and some other general best practices for web content authoring on the Safari Developer Center. And there's always devforums at apple.com.