Session 410WWDC 2016

Discover Xcode's enhancements for debugging autolayout issues at runtime. Learn how issues inside complex GKStateMachine objects can be easily debugged at runtime. Gain insight into finding performance bottlenecks inside SpriteKit and SceneKit apps more easily with the enhanced FPS gauge. Understand how to fix leaked and abandoned memory in your app by inspecting the heap from within your typical Xcode debugging workflow.

[ Music ]

[ Applause ]

Good afternoon, everyone.

And welcome to Visual Debugging with Xcode.

I'm Chris, and I work on Xcode's debugger UI.

You know, debugging tools have come a long way.

It wasn't all that long agowhen our debugging tools looked something like this.

A little while later, with advancements in UI,our debugging tools began to look more like this.

But fast-forward to today,and our debugging tools have become much more powerful whileat the same time becoming much easier to use.

A big part of this is because the tools are becoming morevisual, which helps us to solve problems fasterand more intuitively.

Today, my colleagues and I are going to tell youabout the latest in Xcode's visual debugging tools.

First, a quick overview of what we'll cover.

We're going to tell you about a new feature of Xcode to be ableto report on issues detected by the tools at runtime.

We're going to tell you about the latest enhancementsthat we've made to Xcode's view debuggerand how we've made auto layout debugging easierthan ever before.

We're going to tell you about a new feature to be ableto visually debug state machines and enhancements we've madeto the FPS performance gauge to help with debugging SpriteKitand SceneKit frame rate issues.

Finally, we're going to tell you about a new feature of Xcodea visual memory graph debugger.

Any time you're creating any sort of game or visual app,maintaining good performance is key.

And in Xcode 8, we've expanded the FPS performance gaugeto help you with this.

Many of you may already be familiar with partsof the FPS performance gauge from Xcode 7.

And at the top of the report, you're provided a numberof real-time statistics.

This includes your frame rate, which is the current numberof frames being rendered per second,as well as your GPU utilization to see which partsof your GPU are being used the most, and your frame timefor both the CPU and the GPU.

This helps indicateto you whether you may be CPU-bound or GPU-bound.

Now, in addition to real-time statistics,Xcode 8 now provides you a timeline historyof your SpriteKit and SceneKit's frame timefor both the CPU and the GPU.

This is available on iOS and watchOS.

And what's great about this isthat we breakdown your CPU frame time and its individual partsso you're able to see exactly how much time is spentrendering, or running your update loop,or evaluating actions and physics,and even how much time is spent idle.

And when your app is paused, you're able to scrollthrough the history of your app's performanceso that you can see how it evolvesas you progress through your app.

And if there's a particular sample you're interested in,you can dive deeper to examine finer details on itand get some exact timings.

So let's take a look at how we can use these within our app.

So now that we've addressed the layout issues that we've got.

In our How To Play menu, let's go aheadand dive into the game itself.

The objective of our game is to convert allof the corrupted robots within our computer into good robots.

And to do this, I have a beam that can zap themand reconfigure them into good robots.

Now we see here I've already got a bad robot trying to comeafter me, so I'll use my beam to zap him.

So you see he's been convertedto a good robot indicated with green.

But I see that we still have a part of our beam presentabove our character, and this shouldn't be the case.

Now since we're using a state machine to manage the behaviorof our beam, this is a good candidateto use the State Machine Quick Look to figureout what's going on here.

So I'll go ahead and pause our app while I navigateto our BeamComponent.

Now our BeamComponent is where we createand update our state machinethat manages the behavior of our beam.

And I'll add a breakpoint here on our update loopand resume our game so that we hitthat break point immediately.

So now that we're paused, I can go into debug areaand find our instance of the state machine,and we can Quick Look it.

And from here, we can see the entire state machine.

In blue, we see the current state that we're in,which is the BeamFiringState.

And in gray, we see all of the additional statesthat comprise our state machine.

Now we also see the transitions between each of the states.

And one thing that I've immediately noticed isthat we have a number of transitionsinto our BeamFiringState but we have no transitions out of it.

So this means, as soon as we get into our firing state,we have no way of leaving it.

So let's go ahead and take a look at our BeamFiringStateto see what's going on here.

And I see here we have some logic to transition bothinto the CoolingState as well as the IdleState.

But down here in our methodwhere we're checking whether the state we're trying to transitionto is valid, we're always returning false,which shouldn't be the case because we want to transitionto either cooling or idle.

So I'll go ahead and fix that by checking whether the state we'retrying to transition to is either of the two valid ones.

And we'll go ahead and rerun our gameand check whether this has addressed the issuethat we were seeing.

Now when we transition to the FiringStateand meet the conditions to exit it, we should properly be ableto transition back into our IdleState.

So I'll jump back into the game and go ahead and fireat the corrupted robot to convert him into a good one.

And we'll see the beam is no longer present above our player,so it looks like we've addressed the issue.

So now we've also noticed a performance issuewithin our game.

We have a number of ground robots here on the bottom.

And I notice that when we get attacked by them,our performance drops dramatically.

So I'll switch to our FPS performance gaugeso that we can see our performance live whilewe're running.

And you can see on the right here we indicateto you your target frame time.

And in our case, it's 16.6 milliseconds, which correspondsto maintaining a frame rate of 60 frames per second.

We can also see that a good amountof our time is spent rendering,as well as running our client update,and we've got a good amount of wiggle room of CPU idle time.

So I'll go into our game and move to the right herewhere we have an enemy robot, and I'll let him hit meso that we can try to reproduce this performance issue.

So now I'll switch back to our performance gaugeto see what's going on within our update loop.

And I'm noticing that quite a bitof time is spent evaluating actions.

In fact, now our frame rate is dropping quite dramatically.

So I'll go ahead and pause our appso that we can take a closer look at what's going on.

Now that we're paused, I can scroll back in timewithin our frame breakdownto see our frame time previously within our app.

In fact, here we can see the time that we werein the main menu where we spent a little bit of time renderingbut most of it was spent idle,as well as the breakdown we were seeing within our game.

And now when we are seeing performance issues, I can clickand hold to examine detailsfor the performance issue we were seeing.

Here, I see we're getting 36.2 millisecond frame time,and 71% of that is spent evaluating actions.

So what that tells me is there may be oneof two issues present within our game.

We could have a single actionwithin our scene that's taking an exceedingly large amountof time to evaluate, or we could have a large numberof actions that's bottlenecking our update loop.

So now I know where within our update loop we're having issues.

So we've seen how we can use the State Machine Quick Lookto debug an issue we were seeing within our game,as well as how the FPS performance gauge can show uswhere exactly within our update loop we're having issues.

I'd like to now invite up Daniel Delwoodwho will show you a new memory graph debugger that we can useto determine where our issue with actions originatesfrom so we can fix it.

[ Applause ]

Thank you, Tyler.

So I'm very excited to tell youabout the new memory graph debugger in Xcode 8.

And like the view debugger,it's a tool for understanding your applications better.

So just as the view debugger understands your view hierarchy,the memory graph debugger helps you understand your memoryand how it's referencing each other.

The core question though that it's trying to answer is -why does a certain object still exist on your heap?

Now objects reference each other.

And, you know, this is more and more a question of referencesand annotation these daysin an automatic reference-guiding world.

So where can we go from having this problem of objectsthat we don't want, objects that are leaked, or abandoned?

Well, there's some command-line toolsthat can help, such as Heap.

And what Heap does is it snapshots your process,looks through it for a summary of the different typesand the counts of objects that are in your process.

And you can even use the "addresses" flag to lookfor a specific type of objectand get a list of those instances.

Once you have an instance you're interested in,leaks is where you go for the connectivity kindof information of, well, is it unreferenced?

Is it leaked?

Or is there some path from a global locationin your application that goes all the way down to your object?

Now, at any point in this investigation,you might need some further detail,such as the allocation stack trace.

And, that, you can get with malloc-history.

And this is all not a very visual experience,and that's why we pulled all of these three tools into the IDEfor the memory graph debugger.

And so just a quick overview of, you know, how this is laid out.

On the left, the Navigator is where you get that heap-typeof information to start your analysis.

The center editor area iswhere the connectivity information gets presented.

And on the right that's what we're showing you nowthe allocation stack trace via the Inspector.

So with that, I'd just like to jump right back into the demowhere Tyler left off and see if we can take a lookat those action problems that he was seeing.

All right.

So here we are with the FPS performance gauge, looking at,you know, actions that are probablyat fault here in our application.

So I'm just going to jump rightin by choosing the Memory Graph Debugger buttonin the Debug Menu Bar.

And on the left here,the Navigator shows me my application with allof the different types that are allocated in it.

And so they're broken down by a hierarchyof module and then type.

And then under each of these, there's an instance.

And so, in this case, I'm kindof interested in searching my heap.

And it's very easy to do.

I can just type into the filter, and I'll look for actions.

So here we are.

We've got types in SpriteKit.

And we see that, yeah, we have a lot of actions 559.

So it's probably that we have too many actions and notthat we have some long-running single actions.

So let me select one of these objects.

And the editor changes to show me the answer to the questionof why this object is still around.

In this case, it's showing me a root analysis graphwhich allows me to trace the object I've selected backto the left, back to the roots of my application.

So I can see that it's referencedby an SKC sequence by repeat.

There's an array holding onto this.

And I can even disclose further to seethat here we've got an SKNode with some actions.

So, OK, it's part of this SKNode actions list.

I can click on this and try Quick Looking it.

If I want to take some more looks at this action,I can select it and pull in the Inspector.

Now the Inspector shows me some memory details,such as the class name, the address, the hierarchyif it is a sub-class of some other objects.

What I'm interested in is where this action was createdso I can jump to there.

I can go ahead and collapse this stack trace and jump to my code.

And here, we see I've got this function refreshHurtAction.

All right, so it's running a HurtAction.

I can use the Quick Help to see that this action is addedto the list of actions on the node.

But I actually wanted to only have a single-player actionand make sure that this was replacing my previous actions.

So it's a pretty simple fix.

I'm just going to use the withKey variant hereand replace the player action.

And the Quick Help will show me that, yes,this is actually the one I was wanting.

If an action using the same key is already running,it is removed before the action is added.

Great. So that's a pretty simple way to jump to an investigationabout a specific type.

But one of the other things I noticedwhen I hit the Memory Graph Debugging button isthat the Runtime Issues Navigator alerted meto some issues.

So I can click on that.

And now I'm taken to the new Runtime Issues Navigatorwhich has a bunch of leaks that were reported in my application.

So I'll start out with a type that is defined in my modulesay, this LoadSceneOperation.

If I select it, now the graph isn't showing methat same style.

It's showing me a reference cycle,which is because this is a leaked object.

It's not reachable from those locations in my application.

And I need to find out what objectsin the leaked set are referencing each other.

So looking at this quickly, I have an operationwith some internal state.

It's referencing a completion block.

And then this has some captures as part of that blockthat strongly referenced my LoadSceneOperation.

Interesting.

So if I click on the block, I can see the back traceand go immediately there.

And here we aremy LoadSceneOperation completion block.

I'm even using a capture list for "unowned self".

But the graph showed me that "self" wasn't the problem.

It was the LoadSceneOperation capturing itself right therewithin the block.

So it's a pretty easy fix to make.

I just need to capture it unownedand I can get going again.

But, unfortunately, that's not quite the solution here.

Because, since it's a completion block,my LoadSceneOperation is just about done.

And so once it executes this block here,the LoadSceneOperation is going to end its lifecycleand it won't be around for much longer.

This means that when I dispatch async back to the main queue,this LoadSceneOperation may no longer be validand I'm going to get a crash.

So it just goes to show that these captures can be trickyat times and require a little bit of investigation.

And hopefully the memory graph debugger will help youin your investigations as well.

[ Applause ]

So, let's talk a little bit moreabout leaked and abandoned memory.

The memory graph debugger is a debugger mode, and so it pausesto inspect your target application.

This is so your application doesn't keep goingand changing its state, and you can get a consistent viewof the world.

It also lets you do things like Quick Lookor PO different objects as you go through your investigation,and it's available on all of our platforms.

Now, as I showed in the demo,there's two different graph styles.

And the first one is that root paths graph stylewhich shows you for referenced memorymaybe you've abandoned ithow are different roots in your application like globalsand currently-running threads referencing that memory.

Now with the progressive disclosure model,it lets you work back from your objecstto different intermediate objects and find the referencethat should no longer be there.

For unreferenced memory or leaked memory,that's when you see the cycles view.

And the goal there is to help show you what is stronglyreferencing itself.

And it'll let you figure out, again, a reference problem.

So, for stack logging integration that you sawin the Inspector, it's not quite free to record allof the mallocs and frees in your application.

And so this is a diagnostic that you'll need to opt into.

Just going to the Scheme Editor and selecting MallocStackLoggingin the Diagnostics Tab is enough to enable it.

And it will record, again, all of the mallocs and frees to diskso you can look them up later.

But for memory graph debugging, you don't really need allof the malloc and frees.

And previous lifetimesof a malloc block just aren't usually that useful.

So, new in our current OSs is a Live Allocations Only Mode.

And so this has a lower overhead, and it also allows youto get this rich information while you're memorygraph debugging.

So this will set the "lite" flag to MallocStackLoggingin target environment.

So one other thing that you may enjoyabout memory graph debugging isthat we've introduced a .memgraph file format.

Now, sometimes you'll be debugging an issueand you won't have the time to really dive into it.

And so you may want to save this off or have other engineerson your team take a look as well.

So, from Xcode, you can actually go to the File menuand select Export Memory Graph.

And what it'll do is save out allof the connectivity information and heap information,as well as some VM statisticsabout your application to a file.

Then at some later time, you can just double-click,load that file in the Xcode, and take a look at the memory graph.

Now this does mean that there's no process in the debugger.

So you can't get back traces, or Quick Look or PO the objects.

But it's still a very powerful techniquefor an app analyzing after the fact.

Now if you want to build thisinto your continuous integration,we've actually got some options from the command line.

So you can just run leaks-outputGraph, pick a path,and save out a .memgraph file for later.

So leaks, vmmap, and heap have all been enhanced to read this.

[ Applause ]

All right, now the fun part.

Let's talk about some usage tips here because this is all builton the leaks infrastructure.

Now what this means is the graph is conservative.

We're trying very, very hard not to report thingsas leaked when they're not.

And so in that attempt to avoid false positives,there may be some extraneous referencesthat you see in the graph.

Now these references will show up gray for being unknown.

They may be valid references, they may not.

We may not just have metadata available to the tool.

And so take them with a grain of saltas you're reading these graphs.

Now one thing you can do to improve the accuracy isto enable Malloc Scribble,which is another diagnostic in this scheme.

And this will mean that when allocation are free,it will write over the memory so that you don't havethat uninitialized memory in that new block.

So for references that are known to be strong, these will showup as bold in the graph.

And Swift 3 actually has a lot more reflectionmetadata available.

And so I encourage you to use thisbecause it definitely is a lot more accurate in termsof understanding captures and references.

And finally, I should putout that the memory graph debugger requiresthat you temporarily turn off sanitizerslike Address Sanitizer or Thread Sanitizer.

So this is a lot of information.

Where is a great place to get started with your application?

Well, validate your expectations.

Are there more objects of a certain type than you expect?

Are objects being deallocated when you expect?

Are there any leaks in your types as well?

Once you find an object that you're interestedin investigating, then the goal is to find a paththat shouldn't be there holding onto your object.

And two very common patternsthat you'll find are strong captures from blocksand closures, or potentially even references upwardin your graph that need to be marked as "weak" or "unowned".

So, that's a lot of information.

But I just want to thank you very much for listeningto our information about newand improved visual tools in Xcode 8.

Created by normalizing and indexing video transcript files provided for WWDC videos. Check out the app's source code on GitHub for additional implementation details, as well as information about the webservice APIs made available.