Update note: This tutorial was updated to iOS 8 and Swift by James Frost. Original post was by Tutorial Team member Matt Galloway.
Whether you’ve worked on many iOS apps or are still getting started with your first: you are no doubt coming up with new features and otherwise wondering what you can do to make your apps even better.
Besides improving your app by adding features though, there is one thing that all good app developers should do… instrument their code!
This tutorial will show you how to use the most important features of the tool called Instruments that ships with Xcode. It allows you to check your code for performance issues, memory issues, reference cycles, and other problems.
In this tutorial you’re going to learn:
- How to determine hot-spots in your code using the Time Profiler instrument in order to make your code more efficient, and
- How to detect and fix memory management issues such as strong reference cycles in your code using the Allocations instrument.
Note: This tutorial assumes that you are competent in Swift and iOS programming. If you are a complete beginner to iOS programming, you may wish to check out some of the other tutorials on this site. This tutorial makes use of a storyboard, so make sure you’re familiar with the concept; a good place to start is with the tutorial on this site.
All set? Get ready to dive into the fascinating world of Instruments! :]
Getting Started
For this tutorial you won’t go through the process of creating an application from scratch; instead, a sample project has been provided for you. Your task is to go through the application and improve it using Instruments as your guide — very similar to how you would go about optimizing your own apps!
Download the starter project then unzip it and open it up in Xcode.
This sample app uses the Flickr API to search for images. To use the API you’ll need an API key. For demo projects, you can generate a sample key on Flickr’s website. Just perform any search at: http://www.flickr.com/services/api/explore/?method=flickr.photos.search and copy the API key out of the URL at the bottom – it follows the “&api_key=” all the way to the next “&”.
For example, if the URL is:
http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=6593783 efea8e7f6dfc6b70bc03d2afb&format=rest&api_sig=f24f4e98063a9b8ecc8b522b238d5e2f
Then the API key is: 6593783efea8e7f6dfc6b70bc03d2afb.
Paste it into the top of the FlickrSearcher.swift file, replacing the existing API key.
Note that this sample API key is changed every day or so, so you’ll occasionally have to regenerate a new key. The app will alert you whenever the key is no longer valid.
Build and run the app, perform a search, click the result, and you’ll see something like the following:
Browse through the application and check out the basic functions. You might be tempted to think that once the UI looks great the app is ready for store submission. However, you’re about to see the value that using Instruments can add to your app.
The remainder of this tutorial will show you how to find and fix the issues that still exist in the app. You’ll see how Instruments can make debugging problems a whole lot easier! :]
Time for Profiling
The first instrument you’ll look at is the Time Profiler. At measured intervals, Instruments will halt the execution of the program and take a stack trace on each running thread. Think of it as pressing the pause button in Xcode’s debugger.
Here’s a sneak preview of the Time Profiler:

This screen displays the Call Tree. The Call Tree shows the amount of time spent executing in various methods within an app. Each row is a different method that the program’s execution path has followed. The time spent in each method can be determined from the number of times the profiler is stopped in each method.
For instance, if 100 samples are done at 1 millisecond intervals, and a particular method is found to be at the top of the stack in 10 samples, then you can deduce that approximately 10% of the total execution time — 10 milliseconds — was spent in that method. It’s a fairly crude approximation, but it works!
Note: In general, you should always profile your app on an actual device, instead of the simulator. The iOS simulator has all of the horsepower of your Mac behind it, whereas a device will have all of the limitations of mobile hardware. Your app may seem to run just fine in the simulator, but you might discover a performance issue once it’s running on a real device.
So without any further ado, time to get instrumenting!
From Xcode’s menu bar, select Product\Profile, or press ⌘I. This will build the app and launch Instruments. You will be greeted with a selection window that looks like this:
These are all different templates that come with Instruments.
Select the Time Profiler instrument and click Choose. This will open up a new Instruments document. Click the red record button in the top left to start recording and launch the app. You may be asked for your password to authorize Instruments to analyze other processes — fear not, it’s safe to provide here! :]
In the Instruments window, you can see the time counting up, and a little arrow moving from left to right above the graph in the center of the screen. This indicates that the app is running.
Now, start using the app. Search for some images, and drill down into one or more of the search results. You have probably noticed that going into a search result is tediously slow, and scrolling through a list of search results is also incredibly annoying – it’s a terribly clunky app!
Well, you’re in luck, for you’re about to embark on fixing it! However, you’re first going to get a quick run down on what you’re looking at in Instruments.
First, make sure the view selector on the right hand side of the toolbar has both options selected, like so:
That will ensure that all panels are open. Now study the screenshot below and the explanation of each section beneath it:
- These are the recording controls. The red ‘record’ button will stop & start the app currently being profiled when it is clicked (it toggles between a record and stop icon). The pause button does exactly what you’d expect and pauses the current execution of the app.
- This is the run timer. The timer counts how long the app being profiled has been running, and how many times it has been run. If you stop and then restart the app using the recording controls, that would start a new run and the display would then show Run 2 of 2.
- This is called a track. In the case of the Time Profiler template you selected, there’s just one instrument so there’s just one track. You’ll learn more about the specifics of the graph shown here later in the tutorial.
- This is the detail panel. It shows the main information about the particular instrument you’re using. In this case, it’s showing the methods which are “hottest” — that is, the ones that have used up the most CPU time.
If you click on the bar at the top which says Call Tree (the left hand one) and select Sample List, then you are presented with a different view of the data. This view is showing every single sample. Click on a few samples, and you’ll see the captured stack trace appear in the Extended Detail inspector.
- This is the inspectors panel. There are three inspectors: Record Settings, Display Settings, and Extended Detail. You’ll be learning more about some of these options shortly.
Now onto fixing the clunky UI! :]
Drilling Deep
Perform an image search, and drill into the results. I personally like searching for “dog”, but choose whatever you wish – you might be one of those cat people! :]
Now, scroll up and down the list a few times so that you’ve got a good amount of data in the Time Profiler. You should notice the numbers in the middle of the screen changing and the graph filling in; this tells you that CPU cycles are being used.
You really wouldn’t expect any UI to be as clunky as this; no table view is ready to ship until it scrolls like butter! To help pinpoint the problem, you need to set some options.
On the right hand side, select the Display Settings inspector (or press ⌘+2). In the inspector, under the Call Tree section, select Separate by Thread, Invert Call Tree, Hide Missing Symbols and Hide System Libraries. It will look like this:
Here’s what each option is doing to the data displayed in the table to the left:
- Separate by Thread: Each thread should be considered separately. This enables you to understand which threads are responsible for the greatest amount of CPU use.
- Invert Call Tree: With this option, the stack trace is considered from top to bottom. This is usually what you want, as you want to see the deepest methods where the CPU is spending its time.
- Hide Missing Symbols: If the dSYM file cannot be found for your app or a system framework, then instead of seeing method names (symbols) in the table, you’ll just see hex values corresponding to addresses inside the binary. If this option is selected, then only fully resolved symbols are displayed and the unresolved hex values are hidden. This helps to declutter the data presented.
- Hide System Libraries: When this option is selected, only symbols from your own app are displayed. It’s often useful to select this option, since usually you only care about where the CPU is spending time in your own code – you can’t do much about how much CPU the system libraries are using!
- Flatten Recursion: This option treats recursive functions (ones which call themselves) as one entry in each stack trace, rather than multiple.
- Top Functions: Enabling this makes Instruments consider the total time spent in a function as the sum of the time directly within that function, as well as the time spent in functions called by that function. So if function A calls B, then A’s time is reported as the time spent in A PLUS the time spent in B. This can be really useful, as it lets you pick the largest time figure each time you descend into the call stack, zeroing in on your most time-consuming methods.
- If you’re running an Objective-C app, there’s also an option of Show Obj-C Only: If this is selected, then only Objective-C methods are displayed, rather than any C or C++ functions. There are none in your program, but if you were looking at an OpenGL app, it might have some C++, for example.
Although some values may be slightly different, the order of the entries should be similar to the table below once you have enabled the options above:
Well, that certainly doesn’t look too good. The vast majority of time is spent in the method that applies the ‘tonal’ filter to the thumbnail photos. That shouldn’t come as too much of a shock to you, as the table loading and scrolling were the clunkiest parts of the UI, and that’s when the table cells are constantly being updated.
To find out more about what’s going on within that method, double click its row in the table. Doing so will bring up the following view:
Well that’s interesting, isn’t it! applyTonalFilter()
is a method added to UIImage
in an extension, and almost 100% of the time spent in it is spent creating the CGImage
output after applying the image filter.
There’s not really much that can be done to speed this up: creating the image is quite an intensive process, and takes as long as it takes. Let’s try stepping back and seeing where applyTonalFilter()
is called from. Click Call Tree in the breadcrumb trail at the top of the code view to get back to the previous screen:
Now click the small arrow to the left of the applyTonalFilter
row at the top of the table. This will unfold the Call Tree to show the caller of applyTonalFilter
. You may need to unfold the next row too; when profiling Swift, there will sometimes be duplicate rows in the Call Tree, prefixed with @objc
. You’re interested in the first row that’s prefixed with your app’s target name (InstrumentsTutorial):
In this case, this row refers to the results collection view’s cellForItemAtIndexPath
. Double click the row to see the associated code from the project.
Now you can see what the problem is. The method to apply the tonal filter takes a long time to execute, and it’s called directly from cellForItemAtIndexPath
, which will block the main thread (and therefore the entire UI) each time it’s ask for a filtered image.
Offloading the Work
To solve this, you’ll take two steps: first, offload the image filtering onto a background thread with dispatch_async
; then cache each image after it’s been generated. There is a small, simple image caching class (with the catchy name ImageCache
) included in the starter project, that simply stores images in memory and retrieves them based on a given key.
You could now switch to Xcode and manually find the source file you’re looking at in Instruments, but there’s a handy Open in Xcode button right in front of your eyes. Locate it in the panel just above the code and click it:
There you go! Xcode opens up at exactly the right place. Boom!
Now, within collectionView(_:cellForItemAtIndexPath:)
, replace the call to loadThumbnail()
with the following:
flickrPhoto.loadThumbnail { image, error in if cell.flickrPhoto == flickrPhoto { if flickrPhoto.isFavourite { cell.imageView.image = image } else { if let cachedImage = ImageCache.sharedCache.imageForKey("\(flickrPhoto.photoID)-filtered") { cell.imageView.image = cachedImage } else { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { if let filteredImage = image?.applyTonalFilter() { ImageCache.sharedCache.setImage(filteredImage, forKey: "\(flickrPhoto.photoID)-filtered") dispatch_async(dispatch_get_main_queue(), { cell.imageView.image = filteredImage }) } }) } } } } |
The first section of this code is the same as it was before, and is concerned with loading the Flickr photo’s thumbnail image from the web. If the photo is favorited, the cell displays the thumbnail as-is. However, if the photo isn’t favorited, the tonal filter is applied.
This is where things have changed: first, the code checks to see if a filtered image for this photo exists in the image cache. If yes, great; that image is displayed in the image view. If not, a call to apply the tonal filter to the image is dispatched onto a background queue. This will allow the UI to remain responsive whilst the images are filtered. When the filter has been applied, the image is saved in the cache, and the image view is updated on the main queue.
That’s the filtered images taken care of, but there’s still the original Flickr thumbnails to be taken care of. Open up FlickrSearcher.swift and find loadThumbnail(_:)
. Replace it with the following:
func loadThumbnail(completion: ImageLoadCompletion) { if let image = ImageCache.sharedCache.imageForKey(photoID) { completion(image: image, error: nil) } else { loadImageFromURL(URL: flickrImageURL(size: "m")) { image, error in if let image = image { ImageCache.sharedCache.setImage(image, forKey: self.photoID) } completion(image: image, error: error) } } } |
This is quite similar to the code that handled filtered images. If an image already exists in the cache then the completion closure is called straight away with the cached image. Otherwise, the image is loaded from Flickr and stored in the cache.
Re-run the app in Instruments by navigating to Product\Profile (or ⌘I – remember, those shortcuts will really save you some time).
Notice that this time you are not asked for which instrument to use. This is because you still have a window open for this app, and Instruments assumes you want to run again with the same options.
Perform a few more searches, and notice that this time the UI is not quite so clunky! The image filter is now applied asynchronously and the images are cached in the background, so once they only have to be filtered once. You’ll see a number of dispatch_worker_threads in the Call Tree – these are handling the heavy lifting of applying image filters.
Looks great! Is it time to ship it? Not yet! :]
Allocations, Allocations, Allocations
The next instrument covered in this tutorial is the Allocations instrument. This gives you detailed information about all the objects that are being created and the memory that backs them; it also shows you retain counts of each object.
To start afresh with a new instruments profile, quit the Instruments app. This time, build and run the app, and open the Debug Navigator in the Navigators area. Then click on Memory to display graphs of memory usage in the main window:
These graphs are useful for to get a quick idea about how your app is performing. But you’re going to need a bit more power. Click the Profile in Instruments button and then Transfer to bring this session into Instruments. The Allocations instrument will start up automatically.
This time you’ll notice two tracks. One is called Allocations, and one is called Leaks. The Allocations track will be discussed in detail later on; the Leaks track is generally more useful in Objective-C, and won’t be covered in this tutorial.
So what bug are you going to track down next? :]
There’s something hidden in the project that you probably don’t know is there. You’ve likely heard about memory leaks. But what you may not know is that there are actually two kinds of leaks:
- **True memory leaks** are where an object is no longer referenced by anything but still allocated – that means the memory can never be re-used. Even with Swift and ARC helping manage memory, the most common kind of memory leak is a retain cycle or strong reference cycle. This is when two objects hold strong references to one another, so that each object keeps the other one from being deallocated. This means that their memory is never released!
- **Unbounded memory growth** is where memory continues to be allocated and is never given a chance to be deallocated. If this continues forever, then at some point the system’s memory will be filled and you’ll have a big memory problem on your hands. In iOS this means that the app will be killed by the system.
With the Allocations instrument running on the app, make five different searches in the app but do not drill down into the results yet. Make sure the searches have some results! Now let the app settle a bit by waiting a few seconds.
You should have noticed that the graph in the Allocations track has been rising. This is telling you that memory is being allocated. It’s this feature that will guide you to finding unbounded memory growth.
What you’re going to perform is a “generation analysis”. To do this, press the button called Mark Generation. You’ll find the button at the top of the Display Settings inspector:

Press it and you will see a red flag appear in the track, like so:

The purpose of generation analysis is to perform an action multiple times, and see if memory is growing in an unbounded fashion. Drill into a search, wait a few seconds for the images to load, and then go back to the main page. Then mark the generation again. Do this repeatedly for different searches.
After a drilling into a few searches, Instruments will look like this:
At this point, you should be getting suspicious. Notice how the blue graph is going up with each search that you drill into. Well, that certainly isn’t good. But wait, what about memory warnings? You know about those, right? Memory warnings are iOS’s way of telling an app that things are getting tight in the memory department, and you need to clear out some memory.
It’s possible that this growth is not just due to your app; it could be something in the depths of UIKit that’s holding onto memory. Give the system frameworks and your app a chance to clear their memory first before pointing a finger at either one.
Simulate a memory warning by selecting Instrument\Simulate Memory Warning in Instruments’ menu bar, or Hardware\Simulate Memory Warning from the simulator’s menu bar. You’ll notice that memory usage dips a little, or perhaps not at all. Certainly not back to where it should be. So there’s still unbounded memory growth happening somewhere.
The reason for marking a generation after each iteration of drilling into a search is that you can see what memory has been allocated between each generation. Take a look in the detail panel and you’ll see a bunch of generations.
Talkin’ Bout My Generation
Within each generation, you’ll see all the objects that were allocated and still resident at the time that generation was marked. Subsequent generations will contain just the objects since the previous generation was marked.
Look at the Growth column and you’ll see that there is definitely growth occurring somewhere. Open up one of the generations and you’ll see this:

Wow, that’s a lot of objects! Where do you start?
Unfortunately, Swift clutters up this view a lot more than Objective-C used to, filling it with internal data types that you don’t really need to know about. You can clean it up slightly by switching the Allocation Type to All Heap Allocations. Also, click the Growth header to sort by size.
Right near the top is ImageIO_jpeg_Data, and that’s certainly something that is dealt with in your app. Click on the arrow on the left of ImageIO_jpeg_Data to display the full list. Select one and then select the Extended Detail inspector (or press ⌘+3):
This shows you a stack trace at the point when this specific object was created. The parts of the stack trace in grey are in system libraries; the parts in black are in your app’s code. To get more context for this trace, double click on the second black frame from the bottom. It’s the only one prefixed with “InstrumentsTutorial” which indicates that it’s from the Swift code. Double clicking will take you to the code for that method – your old friend collectionView(_:cellForItemAtIndexPath:)
.
Instruments is pretty useful, but it can help you no further in this case! You’re now going to have to work through the code yourself in order to understand what’s going on.
Take a look through the method, and you’ll see it calling setImage(_:forKey:)
. As you saw when you were looking at Time Profiler, this method caches an image in case it is used again later on in the app. Ah! Well that certainly sounds like it could be a problem! :]
Again, click the Open in Xcode button to jump back into Xcode. Open ImageUtilities.swift and take a look at the implementation of setImage(_:forKey:)
.:
func setImage(image: UIImage, forKey key: String) { images[key] = image } |
This adds an image to a dictionary which is keyed on the photo ID of the Flickr photo. But if you look through the code, you’ll notice that the image is never cleared from that dictionary!
That’s where your unbounded memory growth is coming from: everything is working as it should, but the app never removes things from the cache — it only ever adds them!
To fix the problem, all you need to do is have ImageCache
listen to the memory warning notification that UIApplication
fires. When ImageCache
receives this, it must be a good citizen and clear its cache.
To make ImageCache
listen to the notification, add the following initializer and de-initializer to the class:
init() { NSNotificationCenter.defaultCenter().addObserverForName( UIApplicationDidReceiveMemoryWarningNotification, object: nil, queue: NSOperationQueue.mainQueue()) { notification in self.images.removeAll(keepCapacity: false) } } deinit { NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidReceiveMemoryWarningNotification, object: nil) } |
This registers an observer for UIApplicationDidReceiveMemoryWarningNotification
to execute the closure above which clears images
.
All that the code needs to do is remove all objects in the cache. This will ensure that nothing is holding onto the images any more and they will be deallocated.
To test this fix, fire up Instruments again (from Xcode with ⌘I) and repeat the steps you followed previously. Don’t forget to simulate a memory warning at the end!
Note: Make sure you launch from Xcode, triggering a build, rather than just hitting the red button in Instruments, in order to make sure you’re using the latest code. You may also want to Build and Run first before Profiling, as sometimes Xcode doesn’t seem to update the build of the app in the simulator to the latest version if you just Profile.
This time the generation analysis should look like this:

You’ll notice the memory usage dropped after the memory warning. There’s still some memory growth overall, but nowhere near as much as before.
The reason there’s still some growth is really due to the system libraries, and there’s not much you can do about those. It appears that the system libraries are not freeing all of their memory, which may be by design or may be a bug. All you can do in your app is free up as much memory as possible, and you’ve already done that! :]
Well done! One more issue patched up! It must be time to ship by now! Oh, wait – there’s still the issue of the first type of leak that you haven’t yet addressed.
Strong Reference Cycles
Finally, you’re going to find a strong reference cycle in the Flickr Search app. As mentioned earlier, a strong reference cycle occurs when two objects hold strong references to one another, can’t be deallocated, and eat up memory. You can detect this cycle using the Allocations instrument in a different way.
Note: To follow along with this section of this tutorial, you must profile your app on a real device. Unfortunately at the time of writing there appears to be a bug when running the Allocations instrument against apps in the simulator which means that most of the classes used in this project won’t show up in Instruments.
Close Instruments, head back to Xcode, and ensure that your device is selected as the build target for the app. Choose Product\Profile once again, and select the Allocations template.
This time round, you won’t be using generation analysis. Instead, you’ll look at the number of objects of different types that are hanging around in memory. You should already see a huge number of objects filling up the detail panel – too much to look through!
To help narrow down only the objects of interest, enter “Instruments” as a filter in the field above the Allocations Summary list. This will show only objects that feature the word “Instruments” in their type name. Because the sample app is named “InstrumentsTutorial”, the Allocations list will now only show types that are defined as part of this project. That makes things a bit easier!
The two columns worth noting in Instruments are # Persistent and # Transient. The Persistent column keeps a count of the number of objects of each type that currently exist in memory. The Transient column shows the number of objects that have existed but have since been deallocated. Persistent objects are using up memory, transient objects have had their memory released.
You should see that there is a persistent instance of ViewController
– that makes sense, because that’s the screen you’re currently looking at. There’s also the AppDelegate
, and an instance of the Flickr API client.
Back to the app! Perform a search and drill into the results. Notice that a bunch of extra objects are now showing up in Instruments: the FlickrPhotos
that have been created when parsing the search results, the SearchResultsViewController
, and the ImageCache
amongst others. The ViewController
instance is still persistent, because it’s needed by its navigation controller. That’s fine.
Now tap the back button in the app. The SearchResultsViewController
has now been popped off the navigation stack, so it should be deallocated. But it’s still showing a # Persistent count of 1 in the Allocations summary! Why is it still there?
Try performing another two searches and tap the back button after each one. There are now 3 SearchResultsViewControllers
?! The fact that these view controllers are hanging around in memory means that something is keeping a strong reference to them. Looks like you have a strong reference cycle!
Your main clue in this situation is that not only is the SearchResultsViewController
persisting, but so are all the SearchResultsCollectionViewCells
. It’s likely that the reference cycle is between these two classes.
Unfortunately, at the time of writing, Instruments’ output for Swift still isn’t particularly useful in some cases. Instruments can only give you a hint here as to where the problem lies, and show you where the objects are allocated; it’s then your job to work out what the problem is.
Let’s delve into the code. Hover your mouse over InstrumentsTutorial.SearchResultsCollectionViewCell in the Category column, and click the small arrow to the right of it. This next view shows you all of the allocations of SearchResultsCollectionViewCells
during the run of the app. There are quite a lot of them – one for each search result!
Change the Inspector to show the Extended Detail inspector, by clicking the third icon along at the top of the panel. This inspector shows the stack trace of the currently selected allocation. As with the earlier stack trace, the black parts are your code. Double-click the topmost black row (that begins “InstrumentsTutorial”) to see where the cell is allocated.
The cells are allocated at the top of collectionView(cellForRowAtIndexPath:). If you scan down a few lines, you’ll see this (with no help from Instruments to bring it to your attention, unfortunately!):
cell.heartToggleHandler = { isStarred in self.collectionView.reloadItemsAtIndexPaths([ indexPath ]) } |
This is the closure that handles a tap on one of the heart buttons on the collection view cells. This is where the strong reference cycle lies, but it’s kind of hard to spot unless you’ve come across one before.
The closure cell refers to the SearchResultsViewController
using self
, which creates a strong reference. The closure captures self. Swift actually forces you to explicitly use the word self in closures (whereas you can usually drop it when referring to methods and properties of the current object). This helps you be more away of the fact you’re capturing it. The SearchResultsViewController
also has a strong reference to the cells, via their collection view.
To break a strong reference cycle, you can define a capture list as part of the closure’s definition. A capture list can be used to declare instances that are captured by closures as being either weak or unowned:
- Weak should be used when the captured reference might become nil in the future. If the object they refer to is deallocated, the reference becomes nil. As such, they are optional types.
- Unowned references should be used when the closure and the object it refers to will always have the same lifetime as one another, and will be deallocated at the same time. An unowned reference can never become nil.
To fix this strong reference cycle, click the Open in Xcode button again and add a capture list to the heartToggleHandler
in SearchResultsViewController.swift:
cell.heartToggleHandler = { [weak self] isStarred in if let strongSelf = self { strongSelf.collectionView.reloadItemsAtIndexPaths([ indexPath ]) } } |
Declaring self
as weak means that the SearchResultsViewController
can be deallocated even though the collection view cells hold a reference to it, as they are now just weak references. And deallocating the SearchResultsViewController
will deallocate its collection view, and in turn, the cells.
From within Xcode, use ⌘+I again to build and run the app in Instruments.
Look at the app again in Instruments using the Allocations instrument as you did before (remember to filter the results down to show only the classes that are part of the starter project). Perform a search, navigate into the results, and back again. You should see that the SearchResultsViewController
and its cells are now deallocated when you navigate back. They show transient instances, but no persistent ones.
Cycle broken! SHIP IT! :]
Where to Go From Here?
Here’s a download of the final optimized version of the project, all thanks to Instruments.
Now that you have this knowledge under your belt, go and instrument your own code and see what interesting things appear! Also, try to make Instruments a part of your usual development workflow.
You should be running your code through Instruments relatively often, and performing a full sweep of your app before release to ensure that you’ve caught as many memory management and performance issues as possible.
Now go and make some awesome – and efficient – apps! :]
Instruments Tutorial with Swift: Getting Started is a post from: Ray Wenderlich
The post Instruments Tutorial with Swift: Getting Started appeared first on Ray Wenderlich.