Chapter 12. Performance

In This Chapter

Hardware Acceleration

Rules of Thumb

Tiled Layers

Multithreaded Animation

Core Animation was designed with performance in mind. It first appeared as Layer Kit and was designed to run on a small device (the iPhone and iPod touch), which had a limited amount of RAM, a small CPU, and unlike most desktop computers, a tiny GPU. Core Animation was built for speed and efficiency, but that doesn’t mean you can’t tweak your code even more.

As with any other complex system, performance is a consideration when dealing with Core Animation. Fortunately, Core Animation does a lot of the heavy performance lifting for you. However, when working with a complex animation, you can refine the code even further to give your Core Animation-based apps an added performance boost.

This chapter shows you how to get the most from Core Animation. The chapter starts off with some guidelines; things to keep in mind as you’re programming with Core Animation and trying to eek the most out of your code. Then you learn how to leverage the GPU, how to multithread your animations, and utilize CATiledLayer to present large images to the user without bogging down your application.

Hardware Acceleration

One of the major benefits of using Core Animation is that it takes advantage of the Mac’s built-in hardware to its advantage. Previously, when developers would need to animate some part of the user interface, it generally took place on the central processing unit (CPU), consumed CPU cycles, and was relatively slow. Making a user interface animate without stuttering took a great deal of time and usually involved working with the graphics processing unit (GPU) through OpenGL or some other technology. With Core Animation, you get the hardware acceleration for free! All you need to do is define the animation, kick it off, and let it run. You don’t have to worry about loading it onto the GPU because Core Animation handles that automatically.

This is something to keep in mind while designing your user interfaces. What might be considered a complicated animation is more than likely trivial for the GPU to perform. Sliding rectangles around on the screen, transforming them in a 3D space, and so on is what the GPU was designed to do. Only when we start to push the limits of the GPU do we start to see performance degradation.

iPhone Hardware Rendering

Hardware acceleration occurs on both the desktop and in Cocoa Touch for the iPhone and iPod touch. Although the GPU is smaller on a Cocoa Touch device, we still get its benefit and get to hand off that animation to the GPU. However, unlike the desktop, there is a limited amount of resources on Cocoa Touch devices. As such, it is generally not a good idea to do processor-intensive work while the animation runs.

Your application should wait until any animations have finished running before performing any other tasks that might consume vital resources. Having said that, it is possible to perform other activities on background threads, or even on the main thread, while an animation runs. However, there is a balancing act between having the animation run smoothly and getting several tasks completed at the same time.

If you need to perform other tasks during an animation, be sure to flag them as potential performance bottlenecks, so they can be reviewed later, just in case it becomes obvious that the animations perform poorly.

Rules of Thumb

The following sections present those guidelines mentioned in the introduction as something that you need to keep in mind as you create your animations.

Avoid Offscreen Rendering

Whether you work with a desktop application or a Cocoa Touch device, you want to limit your drawing to only those areas visible to the user. Fortunately the -drawRect: method passes in only the rectangle that is dirty, so you can control exactly how much is drawn in each cycle of the run loop.

Limit the Use of Filters and Shadows

Filters and shadows take quite a bit of time to calculate and render within Core Animation. Therefore, it is recommended that you keep these to a minimum and that you avoid animating either one if possible. Here are some helpful tips:

• Especially in the case of filters, it is possible to render a static, temporary image, with the filter applied. This static image can then be used in place of the layer (with filters applied) during an animation to speed up rendering. When the animation is complete, the filtered-layer can then be swapped back in. Although this might not apply in all situations, it can yield significant performance advantages. This can be accomplished by asking the topmost layer to -drawInContext: that creates the static image that can be swapped in.

• Shadows are expensive. Because they are a partially transparent layer, they require large calculations to determine each pixel (because each pixel needs to be calculated on each layer until an opaque layer is hit). If shadows end up overlapping this increases the cost dramatically. Consider limiting shadows to only the outer layers and allow the inner layers to generate without any effects. See “Minimize Alpha Blending” later in this chapter.

Use Transition Effects Wisely

Transition effects offer great ways to give visual clues to the users that their view of the data or application is changing and how it is changing. From views sliding in and out of the window to layers curling away, the intention is obvious to the users. However, transitions such as filters and shadows (mentioned in the previous section), can be expensive with regard to performance.

Therefore, when designing your applications you should consider how many transitions you are applying to the visible application at one time or how quickly they are being applied.

Avoid Nested Transforms

As part of the power of Core Animation, it is possible to transform multiple layers that reside within each other. For example, you can have layers within layers all of which are transformed on their z-axis. However, it should be noted that these transforms are applied in real time and are not cached. Therefore when you move or animate a layer that has multiple levels of transforms, each transform needs to be recalculated for each frame of the animation.

To increase performance, avoid using multiple levels of transforms while your animations run.

Minimize Alpha Blending

Similar to the nested transforms discussed earlier, alpha blending is also calculated in real time. When a layer has partial transparency, that transparency is recalculated for every frame of an animation. Beware of this when animating layers that have transparency, and try to minimize the calculations as much as possible. Sliding layers behind other layers that are transparent can incur a large performance penalty.

Fortunately, there is an easy way to determine which layers have alpha blending and remove it. This is especially useful on the iPhone with its limited resources. To check for alpha blending, launch your application on the iPhone with Instruments running on the desktop. In Instruments, add the Core Animation instrument in addition to any others you want to use.

When your application runs on the iPhone and you have Instruments attached to it, to turn on the Color Blended Layers, switch in the Core Animation Instrument, as shown in Figure 12-1.

FIGURE 12-1 The Color Blended Layers Switch in Instruments

image

As soon as you enable this switch, you see an immediate effect on the iPhone. The entire iPhone screen turns either red or green. In fact, this switch can be turned on without any applications running, and you can see its effect on the home screen, as shown in Figure 12-2.

FIGURE 12-2 Color Blended Layers Mode on the iPhone Springboard

image

When enabled, the Color Blended Layers option colors the layers, so you can quickly identify the trouble spots in your app. Areas marked in green do not have any alpha blending, whereas areas marked in red do. The goal with this is to eliminate as much of the red as possible. For example, in the TransparentCocoaTouch application, you can see that all the UILabel objects have a transparent background, as shown in Figure 12-3.

FIGURE 12-3 iPhone Application with Color Blended Layers Mode Turned On

image

When you examine the code for these areas, you can see where the issue lies. Specifically, look at the -initWithFrame:reuseIdentifier: of the CustomTableViewCell object; this is where the trouble lies (see Listing 12-1).

LISTING 12-1 [CustomTableViewCell -initWithFrame:reuseIdentifier:]

image

As you can see, the UILabel objects, titleLabel and descriptionLabel, both have their -backgroundColor set to [UIColor clearColor], causing an alpha blending to occur. To correct this, change that UIColor to be the same color as the background for the entire UITableViewCell. This eliminates the alpha blending and improves performance, as shown in Listing 12-2.

LISTING 12-2 Corrected [CustomTableViewCell -initWithFrame:reuseIdentifier:]

image

Tiled Layers

Core Animation has a great feature that enables you to display and quickly view highly detailed images within your application. CATiledLayer is designed to display large images without having to load the entire image into memory and cause performance issues.

To demonstrate the usefulness of CATiledLayer, open the TiledLayers sample application that accompanies this chapter. In this application, a very large image (6064 × 4128) is loaded and the ability to zoom in and out of it is enabled. Normally, large images are loaded fully into memory before they are displayed, and that can cause a huge performance issue. However, by loading the image into a CATiledLayer, Core Animation handles all of the memory issues for us. Core Animation loads in only parts of the image as needed rather than pulling in the entire file. All you need to do to configure the CATiledLayer is to give it the levels of detail used to determine zoom levels, along with the tile size.

NOTE

CATiledLayer solves a number of problems that occur in both desktop development and iPhone development. Specifically it enables you to handle large images, multiresolution data, and handles on-demand loading automatically.

In the TiledLayers application, a single CATiledLayer is initialized in the main window and is assigned an NSSegmentedControl (via Interface Builder) to manage the levels of zoom. The resulting window is shown in Figure 12-4.

FIGURE 12-4 Tiled Layer Application

image

When the interface has been designed, construct the CATiledLayer in the -awakeFromNib method of the Application Delegate, as shown in Listing 12-3.

LISTING 12-3 -awakeFromNib

image

When the app has the path to the image, the path is loaded into a CIImage call and stored for later use. CATiledLayer is initialized and applied to the bounds of the image that will be displayed. Next, the image is positioned within the root layer by setting its position and configuring the levels of available zoom. The last step is to assign the AppDelegate as delegate for the layer so that we can receive events when drawing needs to occur.

NSSegmentedControl is bound to the -zoom: method, which sends an event every time the segmented control changes state. The index of the selected segment is used to determine the image’s current scale before displaying the image within the CATiledLayer. This is accomplished by setting its sublayerTransform with a CATransform3DMakeScale call, passing in the x and y values based on the segmented control (see Listing 12-4).

LISTING 12-4 -zoom:

image

The final method you need to implement is a delegate of the CATiledLayer. This method enables us to override the layer’s -drawInContext: method without having to subclass CALayer. In the default implementation of -drawInContext:, it looks for a delegate and checks to see if the delegate implements -drawLayer:inContext:. If both of those conditions are true, the -drawLayer:inContext: is called, as shown in Listing 12-5.

LISTING 12-5 -drawLayer:inContext:

image

This method uses the image initialized in -awakeFromNib and draws it into the context using the image size as the rectangle for drawing. It should be noted that you don’t need to handle any form of zoom calculations, scaling, or transforms in this method. The CATiledLayer handles that automatically.

How Does This Work?

CATiledLayer loads images into tiles, hence the name. Each tile corresponds to a level of detail based on a tile size that you set. When you request a level of detail, CATiledLayer takes the original image, scales it to the desired size, and breaks it into individual tiles before drawing the tiles on-screen. Because these tiles are cached by CATiledLayer, you can scroll around the image and change levels of detail very quickly.

CATiledLayer caches only so many tiles at once, and when it hits that limit it starts dropping tiles out of the cache as needed. If we request that tile again in the future, it is generated again for our use. This means that tiles appear in an asynchronous manner as they are generated for us, and users see a delay as the tiles are generated and drawn in the layer. A great demonstration of this effect is the Google Maps application on iPhone, as shown in Figure 12-5. If you zoom out or zoom into a different level of tiles, the images that are not yet available display as a simple gray grid. As the tiles are loaded, they replace this grid with the actual information.

FIGURE 12-5 Google Maps Application on the iPhone

image

Multithreaded Animation

Core Animation is internally thread safe because it was designed with multicore systems in mind.

What this means in practical terms is that you can manipulate Core Animation layers on multiple threads without risk of corrupting its data internally and, equally important, that it does the right thing. However, the one area of Core Animation that is not necessarily thread safe is when it comes to accessing properties of a CALayer.

NOTE

What Is a Lock?

A lock is when one object grabs the “access flag” to another object. Only a single object can have that access flag at a time when locks are engaged. Any subsequent object must wait until the flag is available before it can proceed to access the affected object.

NOTE

What Does Atomic Mean?

Atomic is defined as consisting of a set of operations that can be combined so that they appear to the rest of the system to be a single operation with only two possible outcomes: success or failure.

If your application is accessing a property on its main thread and that same property gets accessed on a background thread at the same time, the results are undefined. It is possible that the change will not go through or that it could cause a crash deep within the Core Animation API or any other result. When you try to fetch a property and change it and then restore the property to its previous state, those actions should be wrapped in a lock to make them atomic, as shown in Listing 12-6.

LISTING 12-6 Locking Layer Changes

image

In this example, a [CATransaction lock] is requested to prevent any other thread from obtaining the same lock, effectively blocking the other threads. This serializes access to the layer and its properties and guarantees that the accessed property won’t change before you are done with it.

Care should be taken when working with layers on multiple threads for a couple reasons:

• Holding a lock too long can cause the user interface to stop drawing. Because access to that layer is blocked, you cannot access it on another thread, and other parts of the system also cannot access it until the lock is released. Remember, any time you lock a layer, you need to unlock it as quickly as possible.

• The second issue, which is inherit to all threading locks and not just Core Animation, is the contempt of circular locks. If you lock a layer with a thread and that layer needs access to another property locked by another thread waiting for access to the lock, that can cause the entire application to become unresponsive to user interaction, resulting in the eventual crash of that application.

NOTE

What Is a Circular Lock?

A circular lock (or deadlock) is a situation wherein two or more competing threads are waiting for the other to finish, and thus neither ever does. This is illustrated in Figure 12-6.

FIGURE 12-6 A Theoretical Circular Lock

image

Mutlithreading with Filters

Unlike Core Animation, Core Image filters are not thread safe. This means that you should only manipulate Core Image filters on a single thread. It is possible, however, to adjust a filter on more than one thread by using key-value coding (KVC) and key paths to manipulate the properties. For example, if you have a layer with a filter named acme and it has a property named job, you can adjust it with code similar to the following:

[layer setValue:myValue forKeyPath:@”layer.filters.acme.job”];

The layer would guarantee that the change is atomic and, therefore, thread safe.

Threads and Run Loops

When working with Core Animation on multiple threads, you need to be aware that any thread that calls -setNeedsDisplay on a layer is going to get the -display message as well. If you flag a layer as needing the display from a thread other than the main thread, you need to set up a run loop to ensure that the thread still runs when the -display call occurs. If you don’t, the -display call never occurs because the thread it is looking for will have terminated, causing unexpected results.

Although it is possible to call -setNeedsDisplay from a thread other than the main thread, it is a lot of work for very little effect or benefit. It is highly recommended that these calls be localized to the main thread by utilizing -performSelectorOnMainThread:.

Summary

In this chapter, we walked through a lot of tips and tricks with regard to performance. As is always recommended when dealing with performance issues, finish the code first. Do not spend time optimizing the code for a “possible” bottleneck until that bottleneck is confirmed.

It is also highly recommended to test potential bottlenecks and have recordable results. Sometimes, a simple fps (frames per second) recording is sufficient to test the bottleneck. With recordable results, we can confirm that any performance changes were positive instead of being a no-op or, worse, having a negative impact.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.120.161