Transcript
[ Music ]
[ Applause ]
>> Hello and welcome to advanced
debugging with Xcode and LLDB.
I'm Chris Miles, one of the
engineering managers on the
Xcode team, and I'm really
excited to be here today.
Now, I'm well aware there's
nothing between you and Beer
Bash but our session, so thanks
for coming.
We'll get you out on time, but
we've packed in lots of great
content for you guys today.
So let's get straight into it.
I'd like to start by talking
about Swift debugging
reliability.
The main point to make here is
that there's good news.
The team has landed a lot of
reliability fixes in Xcode 10.
The -- thank you.
[ Applause ]
The compiler and debugger teams
have been able to smooth over
many of the rough edges that
were causing Swift debugging
headaches.
I'd like to tell you about a
couple of them.
In some cases, usually with more
complex projects or build
configurations, attempts to po
an object or evaluate an
expression in the console may
have failed with an error such
as this.
The AST context that this error
refers to is an expression
context that LLDB needs to
reconstruct the state of the
compiler from when it built your
project.
And, in some cases, such as if
there are module conflicts, the
expression context cannot be
reliably reconstructed, and your
expression would fail.
In Xcode 10, LLDB have
implemented a fallback mechanism
for when this problem case
occurs.
So if it can't reconstruct the
context, it will fall back to
creating a simpler context for
the current frame and use that
to evaluate your expression.
Another failure case that some
developers may have had was due
to the debugger being unable to
materialize variable types while
debugging.
This would manifest itself in
Xcode looking something like
this.
Where on the left, you can see
the variables' view shows all
the names of the variables in
the frame but there'd be no type
information or values visible.
And attempts to print out the
value of a variable would fail
with errors like these.
Now, thanks again, in big part
to your bug reports, the team
has been able to track down and
fix these many edge cases where
debug information was not being
reliably generated.
So I want to thank you guys on
behalf of the team again for
filing issues when you've
encountered any problems while
debugging.
And if you find any more
problems with Xcode 10 while
debugging a project, please
continue to file bug reports for
us.
For those of you at the
conference, if you find a
problem with your project,
please come along to the labs.
We have an Xcode debugging and
profiling lab tomorrow morning
from nine till 12.
Bring your project and ask for
an Xcode debugger engineer or an
LLDB engineer, as they would
love to see your projects.
Now I'd like to move on to
telling you about some of my
favorite debugging tips and
tricks that I like to use to
enhance my debugging webflow.
In fact, I'm not just going to
tell you about it, I'd like to
show you with a demo.
[ Applause ]
Now the project we'll be using
today is an iOS app called Solar
System.
And you may have seen Solar
System appear in such keynotes
as the Keynote and State of the
Union.
We're going to be debugging an
area of the Solar System called
Moon Jumper.
Moon Jumper allows the user to
hold a phone in the hand and
jump in the air.
The app measures the force of
your jump and then translates
that to moon gravity and shows
you a visual representation of
how high you would have jumped
if you were standing on the
moon.
You can set a bar to choose a
limit, so you can try and
challenge yourself to jump as
high as the bar, in terms of
moon gravity.
Now in making some enhancements
to Moon Jumper, such as some
visual enhancements and a
GamePlay mode, while we're not
ready to ship, we've done a test
pass and come up with a list of
bugs that we need to look at.
So these are the bugs for Solar
System.
I'll be tackling the iOS bugs
first, and later on, Sebastian
will come out and solve the
macOS bugs.
Now, as it says, none of us are
getting out of here to the Beer
Bash until we fix all of these
bugs, so there's nothing more
exhilarating than peer
programming with 2000 people.
Let's get straight into it and
start with the first bug.
The jump failure animation does
not match the specification.
So what's that talking about?
Well, switching to the simulator
because we're using the
simulator to speed up debugging
and development.
I've wired up a tap just to
recognize them, so every time I
tap the astronaut he performs a
successful jump to the height of
the bar.
Now this bug is talking about
the case where the astronaut
doesn't reach the height of the
bar, so let's reproduce that.
Switching to the editor, I'm
going to use the jump bar to
navigate to the jump function
and set a breakpoint at the
start of that function.
Now I'll tap the astronaut, and
we'll start a jump and now we
are paused in the debugger.
The first thing to point out is
actually up here in the tab bar.
We now have four tabs.
This debug tab was just created
for us by Xcode, and for those
of you that like working in tabs
like I do, this is an Xcode
behavior that you can define.
[ Applause ]
So to do that you use the Xcode
menu to edit behaviors and that
takes you to the preferences in
the behavior's tab.
And here you can configure many
behaviors.
In this case, you would need to
configure the pause's behavior
in the running section.
And this is the behavior that
will be actioned when Xcode
pauses in the debugger.
So you can see here I've
configured it to show a tab
named debug, and Xcode will
always switch to that the tab
when pausing in the debugger.
So that's great for users who
like to work in tabs like I do.
Now switching back to the code,
we can see that there's a
condition based on this property
did reach selected height, so
I'd like to have a look at the
value of this property.
So switching to the debug
console, I can use po to look at
the value of that property and
it's currently set to true.
So the [inaudible] always sets
this to true, and I'd like to
change it to false so we can
reproduce the bug.
Now I could go to the code and
modify it, tap just to recognize
it to set it to false, but I
don't like to make changes to my
code just for debugging purposes
if I can avoid it.
So in this case, what I can do
is use the debugger to do it for
me.
I can use the expression
command.
And here I can give it any Swift
expression, such as did reach
selected height equals false,
and it will evaluate that and
execute it.
So now, we can see that this
property has indeed changed to
false.
And if I step over the line
using the debugger, we can see
that we've entered the false
side of the branch.
So now, when we continue, we can
see that the astronaut doesn't
quite reach the height of the
bar and falls down.
So that's the case we're trying
to reproduce.
I'd like this to happen every
time I tap on the astronaut, and
I don't want to have to pause
and type this expression each
time, so what I'm going to do is
configure this breakpoint to do
that for me.
If I right click on the
breakpoint, I can select edit
breakpoint and this gives me a
popover window with some
configuration to customize how
the breakpoint behaves.
I'm going to select the debugger
command action and enter the
expression command with the
command that I used in the debug
console and make this an
auto-continuing breakpoint.
So what we've configured here is
a breakpoint that once execution
enters this function, the
breakpoint will trigger execute
this command for us to change
the value of the property and
then automatically continue.
So now, every time I tap the
astronaut, he'll perform this
unsuccessful jump and fall back
down.
Now what's the problem that we
need to fix?
The specification said that
after falling down, the
astronaut should stand back up
again, so let's fix that now.
I'm going to navigate to this
function update UI for jump
failed, and we can see this
function uses UIKit Dynamics to
simulate failing the jump.
So it starts by creating a UI
Dynamic animator and then calls
a function to add behaviors to
create the physics effects.
And then the astronaut is meant
to be reoriented and recentered
in the dynamic animator did
pause delegate callback.
Now we can scroll down and see
that that delegate callback is
implemented successfully, so
that looks fine.
But I'm noticing that no
delegate is set on this object,
so if I add that code in here, I
think this is the change I'll
need to fix the problem.
Now, at this point, I can
recompile and rerun and try and
verify my fix, but I'd like to
shortcut that whole cycle if I
can.
So what I'm going to do is use a
breakpoint to inject this change
for me so I can see if this does
fix the problem quickly and
conveniently.
So I'll create a break point,
double click to bring up the
edit window, which is a shortcut
for bringing up the edit window.
And then, once again, use a
debugger command action to
inject an expression, and I can
type in that line of code that I
think will fix the problem and
make this an [inaudible]
continuing breakpoint.
So what I've configured here,
even though I've made the change
in the code, I haven't
recompiled, of course.
So I'm using the custom
breakpoint to inject that change
for me so I can test it using
the current live application.
So now, if I tap the astronaut,
he performs this unsuccessful
jump and falls over and stands
back up again, so it looks like
we fixed the problem.
I like this effect, so I'm going
to do it again.
[ Applause ]
Now let me just launch notes
again.
And we can tick off the first
bug as fixed.
Nothing better than checking off
fixed bugs.
Now the next three bugs are to
do with the new GamePlay mode
I've been working on.
So I'm going to press play in
the simulator to show you that,
and the challenge here is to try
and jump higher than the bar 10
times.
And the bar starts low and
raises each time.
And you can also see some score
labels have been added to the
top.
So if I tap the astronaut,
you'll see he's still configured
to perform an unsuccessful jump,
and then the attempts label at
the top should have incremented
but it didn't.
And that's the first bug we've
got here, where these labels
flash but do not change.
We've also got an issue with the
end-of-game state not being
handled properly and a problem
with the layout of those score
and attempts labels, but we'll
come back to those.
So returning to this bug, if you
didn't notice it, let me tap the
astronaut again and keep an eye
up on the top on the attempts
label.
You'll see that it does flash
but it doesn't update.
So what this tells me is that
the label is getting a value set
on it because we're seeing the
transition animation but the
value is incorrect.
So I'd like to find the code
that is modifying this label to
have a look at what the logic is
at that point.
So we note UI label is getting
the text property changed, so
what I'm going to do is switch
to the breakpoint navigator and
down at the bottom here select
this plus button to create one
of many specialized breakpoints.
You can see we've got
breakpoints for Swift errors and
exceptions and even test values,
but for this case, I'm going to
use a symbolic breakpoint.
So that creates a new breakpoint
and brings up the editor for
that, and here we can enter any
function or method name such as
UI labels, set text, which we'll
need in this case, and we enter
it in Objective-C format because
UIKit is in Objective-C
framework.
The thing to point out, if I
dismiss that, is that below the
breakpoint, there is a
[inaudible] added and this is
the feedback from the debugger
to tell us that it was able to
resolve this breakpoint to one
location in UIKit, Core.
Some symbols may result in
multiple locations, and you
would see them all here.
And if you saw no [inaudible]
entries, that would indicate
that the debugger wasn't able to
resolve your breakpoint, so it
wouldn't hit.
So with that in place, I'm going
to tap the astronaut again, and
now we see that we've hit the
breakpoint in UI label set text.
Now we don't have the source
code for UIKit, of course, so
we're looking at assembly code,
but there's no need to remain in
the dark even if you're in
assembly code of one of the
system frameworks.
We can inspect the arguments
passed into a function.
You just need to know the
calling convention for the
architecture, and you can
inspect the registers to see the
arguments.
Well, I'll admit I never
remember what those registers
are but thankfully, I don't have
to because the debugger provides
pseudo-registers.
Dollar arg one is translated to
the register that holds the
first argument.
So in this case, we can have a
look at the receiver of that
Objective-C message, which is a
UI label instance.
Now we see that it has a value
of 17 feet, and that indicates
to me that it's this height
label here, so it's not the
label we're interested in, but
while we're here, let's look at
the other arguments.
If you're familiar with
Objective-C message send, you
may remember that the second
argument should be the selector.
We don't see that here but
that's because LLDB doesn't
implicitly know the types of
these arguments.
So in some cases, we need to
typecast it and now we see the
selector for this message.
Now the third argument is the
first parameter passed into the
method.
In other words, it's the string
passed into the set text.
So this is a great convenience
for inspecting the arguments, if
you're in an assembly frame for
one of the frameworks.
But like I said, this wasn't the
object or the label we're
interested in, so let's press
continue, and now we've hit the
breakpoint again.
So we can inspect dollar arg one
to see what the receiver is, and
it looks like it's the same
height label with zero feet now.
And I can see the problem with
my strategy.
While the astronaut is jumping,
the code is updating the height
label in real time, so the
breakpoint is going to hit on
this object quite frequently,
and it's going to take us a long
time.
It'll be very difficult to hit a
breakpoint on UI label set text
for the attempts label.
So what I think I should do is
only set this symbolic
breakpoint after the jump
animation has completed, so let
me show you a way to do that.
I'm going to switch to the
breakpoint navigator.
And if I double-click on the
indicator for the symbolic
breakpoint, we can bring the
editor back up and we can use
this condition field.
We can enter an expression here
that returns true or false, and
the breakpoint will only trigger
if that expression evaluates to
true.
So if we had a property such as
jump animation in progress, we
could edit an expression to test
if that was false and then the
breakpoint would trigger.
I don't have a property in this
case, so I'm going to show you a
different method.
I'm going to delete that
symbolic breakpoint and instead,
scroll down to this function
jump completed and set a
breakpoint here.
Now jump completed is called
after the animation has finished
so that it can update UI and
update game state.
However, we don't want a break
in this function.
What I want to do is configure
this breakpoint to actually set
the symbolic breakpoint in UI
label set text for me.
And I can do that by adding a
debugger command action, which
is breakpoint set, and I'm going
to use the option one shot true.
And a one shot breakpoint is a
temporary breakpoint that only
exists until it's triggered and
then it's automatically deleted.
And we can give it the symbolic
name, UI label set text, and
make this an order continuing
breakpoint.
So what we've created, in this
case, is a breakpoint that when
the execution enters the jump
completed function, it sets a
temporary breakpoint in the
location we're interested in and
then continues.
So then we'll only hit that set
text breakpoint after flowing
through this function.
So let's press continue, and we
see that the jump animation
completes in the simulator, and
now we hit the breakpoint on UI
label set text.
So now we can have a look at the
receiver of that message by
using po dollar arg one.
And we see that is indeed a
different UI label instance with
a value of zero, so that's
likely to be one of these at the
top.
So we think we've found the
right object, so let's have a
look at the code that's
modifying this label's value.
We can do that in the debug
navigator by selecting the next
frame up in the stack.
And now we've found the code
that's modifying the label
value.
It's currently passing in a
string of zero using the label
text variable, and looking up,
we can see that label text is
always set to the label's
current value, so that's not
going to change.
It looks like value text is the
variable that contains the new
value, so probably just a typo;
let's fix that.
Let's make it value text.
And then, what I'd like to do,
rather than recompile and rerun
to test this change, I'd like to
actually test this change in the
context of the current running
application like before.
So I'm going to add a breakpoint
below the current line because
remember, we've changed the code
but we haven't recompiled.
So while the label will still be
set, we'll add our line below
that to set it to the value we
think it should be.
So another custom breakpoint,
and we can use expression once
again to inject that code and
make this auto-continuing.
So now, if I press continue,
code execution will continue to
flow through here, and we see
that the attempts label is
indeed updated.
I'd like to make sure that also
works.
Well, thank you.
[ Applause ]
I'd like to make sure that also
works for the score label after
a successful jump, so let's
navigate back to here.
I can remove this jump-completed
breakpoint that was creating the
one shot breakpoint because we
don't need that anymore.
And I can disable the breakpoint
in jump because we don't want to
modify did reach selected height
anymore.
And now, when I tap the
astronaut, he performs a
successful jump, and we can see
that all the labels update
properly, so that bug looks
great to me.
So let's return and check that
one off.
All right.
The next bug is a problem with
the end-of-game state.
So the game is meant to end
after 10 attempts, so I could
tap the astronaut, wait for the
animations to play out, and
progress through the game to get
through that state to try and
reproduce it.
But the animations take some
time, and I may need to do this
numerous times while I test and
verify my fixes, so I'd like to
skip all the jump animations.
So let me show you how I'll do
that.
I'm going to navigate to update
UI for jump succeeded, and we
can see this function modifies
some colors and then calls jump
astronaut animated true.
So it looks like I just need to
call jump astronaut animated
false.
I could change the code and
recompile, but like I said
before, I don't like to change
my code for debugging purposes
if I can avoid it, so let me
show you the technique I'll use
instead.
I'm going to set a breakpoint on
this line.
Let's clear the debug console
and initiate a new jump by
tapping the astronaut, and now
we're paused on this line.
So I need to ask the debugger to
replace this line with a call to
jump astronaut animated false.
Well, the code's compiled in, we
can't replace it, but what we
can do is ask the debugger to
skip over this line, to not
execute it but skip over it.
And then we can use expression
to inject the change that we --
or the call that we'd like to
make.
So how do we skip over a line?
Well, let me draw your attention
to this green annotation label
thread one.
We call this the instruction
pointer.
It points to the line containing
the instructions that will be
executed next.
And this icon here, that kind of
looks like a grab handle, in
fact, is a grab handle.
If I hold the mouse down, I can
move this around, and I'm able
to change the instruction
pointer while paused, so I can
move it down one line and let go
and then we get a scary message
from Xcode.
And basically, what it's saying
is, with great power comes great
responsibility.
And I'll be honest with you,
this is the riskiest feature
I'll tell you about today.
But it's only risky because the
debugger allows you to move the
instruction point wherever you
like.
It does not care, but it cannot
guarantee that the application's
state will remain intact.
So you could, for example, cause
memory management issues if you
end up referencing an object
that has not yet been
initialized or over-releasing an
object, for example.
But we've all been to the
advanced debugging session now,
so we know what we're doing.
So let's press the blue button.
All right.
So we've skipped over that line,
and now in the console, we can
use expression and call jump
astronaut animated false.
And now we press continue to see
if all this worked, and indeed,
the game state updated, and we
skipped all the jump animations.
So I'd like that to happen every
time I tap the astronaut, so I'm
going to configure this
breakpoint here to do that for
me.
So first, we need a debugger
action that skips over one line,
and the command for that is
thread jump.
And I give it the option by one,
and this tells the debugger to
jump over one line of code for
the current thread.
And then we just need to call
our expression and we can do
that by pressing the plus button
and adding another command
action.
Expression jump astronaut
animated false.
And we make this order
continuing.
So what we've got here is a
breakpoint then when execution
reaches this line but before it
executes this line, the
breakpoint is triggered.
It will perform the command to
jump over that line and then use
expression to call the function
call that we'd like to make
instead.
So now, if we tap on the
astronaut, we can rapidly
progress through the game state
and skip all the animations and
easily reproduce our bug.
So, as I said, the game was
meant to end after 10 attempts,
and we've gone well beyond that,
so let's have a look at the game
state.
That is all stored in a property
up here called GamePlay, so I'm
going to set a breakpoint on
that property and start a new
jump.
And now we've paused at the next
reference to that property.
I'm going to use po to look at
the current state of that
object, and here we see the
debugged description for this
GamePlay object.
And we have a custom debugged
description here, so it's worth
pointing out that po requests
the programmatic debug
description of an object and you
can customize these.
I'll show you how we did that
for GamePlay.
If I switch to the source code
and scroll to the bottom, you
can see that we've added an
extension to conform GamePlay to
custom debug string convertible.
And conformance requires that
you implement a property called
debug description and return a
string.
And you can return whatever
string you like to represent
this object for your debugging
purposes.
You can do the same for
Objective-C objects as well by
implementing debug description.
Compare that with the command p
GamePlay.
P is an alternate LLDB command,
which uses LLDB's built-in
formatters to represent the
object.
So here, we see two
representations of the same
object, and default formatter
shows you the full [inaudible]
type name, the memory address,
and then a list of all the
properties and their values.
So we can see here that there is
a max attempts property, and
it's correctly set to 10, so it
looks like there's perhaps a
logic error where, after
attempts is incremented, it's
not successfully determining
that it's past the maximum.
So I'd like to find the code
that's modifying attempts to see
what the logic looks like.
I'm going to open the variables
view and expand the view
controller to see all its
properties and down at the
bottom, I'm going to use the
filter to enter GamePlay to find
that property.
Expand its properties and then
I'm going to select the attempts
property.
And down here, I'm going to open
the contextual menu and select
watch attempts.
Now what this does is creates
what's called a watchpoint.
In the breakpoint navigator,
below all the breakpoints,
you'll see there's a new group
called watchpoints, and we have
one watchpoint for attempts.
And a watchpoint is like a
breakpoint, but it pauses the
debugger the next time the value
of that variable is changed, so
we can remove this property
breakpoint because we no longer
need it and press continue.
And now we've paused at this
watchpoint, and we found the
code that's modifying the
attempts variable.
I can disable this watchpoint
because I no longer need it.
And we can look at the code here
while the game's playing,
increment attempts, and if
successful, increment score.
So I'm not seeing any logic that
will detect if attempts has
exceeded maximum and transition
to the end-of-game state.
So I think that's all that's
needed, but in this case, I'd
like to test my hypothesis
before I make any actual code
changes.
So I'm going to create a
breakpoint and configure it to
inject that change to see if it
fixes the problem before I make
any code changes.
So once again, I can add a
debugger command action with an
expression and I think what we
need is if attempts is greater
and equal to max attempts, then
we change the game state to
ended and make this order
continuing.
So now it's easy just to test if
that actually fixes the problem
by pressing continue.
Execution will continue through
this breakpoint.
Inject the code and we can see
that it does look like it fixes
the problem.
I'd like to verify that from the
start of the game and I can
quickly and easily do that by
clicking play again and rapidly
progressing through 10 attempts.
And at the tenth, we see that it
does indeed detect end-of-game
state, so it looks like that's
the fix we need.
[ Applause ]
And now, don't forget to apply
that to your code.
So I'm just copy that out, drag
the breakpoint to delete it.
And then I can paste that in and
that looks good.
So let's check that one off, and
we've only got one more left for
this section and that's the
layout of the attempt and score
labels.
Now, the layout of this
application has been left up to
the engineers, and as good
engineers do, we've found an
efficient location stuffed right
up in the top corners.
But the team decided that wasn't
very appropriate, so they've
sent it back and asked us to try
again.
So I'd like to mock up a new
layout for these score labels.
Now I could get out my graphical
application and start mocking it
up, but I'm an engineer and I
like to mock up using code.
In fact, I'm a debugger
engineer, so I like to mock up
using the debugger with a live
application and real data.
So let me show you how to do
that.
Let's navigate back and put
breakpoint in the jump function.
What we need to do is first find
a reference to a view that we
can play around with, so I'm
going to clear everything and
open that up and start a new
jump.
So we're paused in the debugger
in this jump function within the
view controller.
So if you have of course a
property or an outlet for a
view, then that's a good
reference.
But if you don't, then you need
to get the memory address of a
view.
So let me show you some ways to
find the memory address and how
to manipulate a view only by
memory address.
Well, like we said before, the
debug description contains a
custom description.
So looking at the view
controller's view, we can see
the default debug description
for a UI view has the class of
the view and then the memory
address.
So one way is to just get the
debug description for objects.
So that's easy to get it for
this one because there's a
property for it.
But how about all of the views
below this view controller's
view?
Well, we need to look at the
view hierarchy and one way to do
that is to use this button here,
which invokes Xcode's visual
view debugger.
It will snapshot the hierarchy
and give you a 3D exploded view,
and you can use that to inspect
views that way.
Sebastian's going to talk more
about that in a few minutes, so
let me show you an alternative
way, which is good for simpler
hierarchies and keeps you in the
debug console.
And that's using a debug
function on UI view called
recursive description.
So we should be able to call po
self.view recursive description.
However, that doesn't work.
Why is that?
Well, recursive description only
exists for debugging purposes.
It's not part of the public API
and so isn't [inaudible] to
Swift.
And Swift is a strict language
and doesn't allow you to call
functions that haven't been
strictly defined.
However, Objective-C [inaudible]
code can run wild and free in
Objective-C world and you can
pretty much do whatever you
like.
I mean it's a dynamic language
so you can call functions like
this.
So what we need to do is to tell
the debugger to evaluate this
expression in an Objective-C
syntax.
And the way to do that is to use
expression with the option - l
objc.
That tells expression that
you're about to give it
Objective-C code even though
you're in a Swift frame.
And we'll give it -O, tell it
that we also want the debug
description the same as po would
do and -- to indicate that there
are no more options.
The rest of the line is just raw
expression input.
So we should be able to then
give it the Objective-C format
of this method call.
Unfortunately, that doesn't
quite work and the reason for
that is that expression will
create a temporary expression
context for the Objective-C
compilation, and it doesn't
inherit all the variables from
the Swift frame.
So there's a way around that,
though.
If we just put [inaudible] view
in back ticks.
Back ticks is like a [inaudible]
step that says first, evaluate
the contents of this in the
current frame and insert the
result, and then we can evaluate
the rest.
And now we get the recursive
description.
[ Applause ]
So using this, we can see all
the debug descriptions for all
the views.
And I'm interested in the
scoreboard views, which host
these labels, so we can find the
memory address for one of those.
And now we can use [inaudible]
po memory address, which you
might be familiar with if you're
an Objective-C developer.
Well, that doesn't work and
that's because Swift doesn't
treat numbers as pointers and
de-reference them for you.
So once again, we need to do
this from an Objective-C
context.
So we could do the same thing we
did before, but I find this to
be so convenient that I like to
shortcut this down to just a
simple short command.
So I'm going to do that by using
command alias, and I'm going to
call that command poc.
So now that I've created an
alias, I can simply poc that
memory address and see the debug
description for that object.
I'd like to show you another way
to look at the description of an
object if you only have its
memory address.
And in Swift, you can use a
function called unsafe bit cast.
Give it the memory address and
then it's unsafe because it's up
to you to provide the correct
type, so I'll give it scoreboard
view.self.
And now we see we can use unsafe
bit cast to see the debug
description for an object.
Now the great thing about unsafe
bit cast is that it returns a
typed result, so we can call our
functions and property names on
it such as .frame.
And in this case, I'd like to
inspect a center point and then
modify that center point.
Let's change it to 300
[inaudible] we can see it has
changed to 300, but the view in
the simulator hasn't moved.
Well, why not?
Well, we're paused in the
debugger, so cronomation isn't
currently applying any view
module changes to the screen's
frame buffer.
But we can ask cronomation to do
that for us, just use the
expression ca transaction.flush
and that tells cronomation to
update the screen's frame
buffer.
[ Applause ]
So now, I can just use these two
lines to fix the new positions
and continue flashing and we can
move [inaudible] around.
And in fact, I find this to be
so convenient that I kind of
wanted to wrap all this up in
just a single command to nudge
views around, and so that's what
I did.
Let me show you that.
I'm going to switch to terminal
and open a Python file.
Why a Python file?
Well, LLDB is scriptable using
Python, where you get full
access to the LLDB API.
So I've created an LLDB Python
script to create a nudge
command, which takes an x
offset, a y offset and a view
expression, and you can use that
to nudge views around while
paused in the debugger.
Now, it might look like a sort
of long script but most of that
is argument pausing.
The core of it, in the middle,
is just calling out to the
expressions we would call in
manually.
We don't have time unfortunately
to go into detail in this
script.
But we're going to make this
available for you guys to
download, so you can see how it
works and use it as the basis
for your custom debugging
commands.
Let me show you how to enable a
script like this.
Just edit your .lldb in it file
in your home directory and add a
line command script import.
I'd also like to add some of the
aliases that I find convenient,
such as the poc alias I created
before, and an alias for
flushing the transaction.
I think I'll remember this one.
I'm going to copy command script
import so we can just paste it
in to the debug session to save
us restarting that session.
And now we have a command called
nudge.
So I can, let's say, nudge zero
horizontally, minus five
vertically, give it the memory
address of that view and start
just nudging it around in the
simulator.
[ Applause ]
The great thing about LLDB is if
you just hit enter on a blank
line, it repeats the previous
line, so it's great for nudging.
And I can nudge it across to the
right a bit, just to get it
right.
And then let's do the other
view.
We can give it any view
expression, say the attempts
view down to here.
The other feature of nudge is
once you've given it a view
expression, you don't have to
repeat that expression.
It remembers that and applies it
to the same view that you've
specified previously.
So something like that looks
fine.
It's a better layout than we had
before.
And what I can do now is take
the information provided by
nudge, such as the total offset
applied to that view relative to
its original center point, and
then your frame value.
Back to my code and modify my
layout code or my auto-layout
constraints, and I've easily
mocked up a new layout for my
scene.
Now the last thing to do --
well, firstly, don't forget to
check off debug, very important.
And then the last thing to do
before restarting or recompiling
and rerunning is to disable or
remove any breakpoints that are
injecting expressions because
you don't want those lines to be
executed twice.
Simply selecting them or a group
of them and hitting delete is a
quick way to delete those.
And so those are some of the
debugging techniques that I like
to use to enhance my debugging
workflows.
Notice how we were able to
diagnose and fix all four bugs
without having to recompile or
rerun.
This can be a huge timesaver,
especially for complex projects
and can be crucial when trying
to solve hard to reproduce bugs.
So thanks for [inaudible] I hope
you enjoyed that and can use
these techniques in your
debugging sessions.
[ Applause ]
I'd just like to quickly recap
all of the features and tricks
that we went over during that
debug session.
So firstly, we looked at how we
can use Xcode behaviors to
dedicate attempt to debugging
and how to use LLDB expressions
to modify program state.
We can use auto-continuing
breakpoints with debugger
commands to inject code live,
and we can create dependent
breakpoint configurations using
breakpoint set one shot as a
debugger command action for
another breakpoint.
Even when in assembly frames, we
can easily inspect the function
arguments using po dollar arg
one, dollar arg two, et cetera,
and we can skip lines of code by
dragging the instruction pointer
or using the command thread
jump.
We can request that the debugger
pause when a variable is
modified using watchpoints, and
we can even evaluate Objective-C
code in Swift frames using
expression -l objc.
We can request that view changes
are flashed directly to the
screen, even while paused in the
debugger, using the expression
ca transition flush.
And you can add custom LLDB
commands, either aliasing
commonly used commands to
correct shortcuts or by
completely customizing and
creating your own command using
LLDB's Python scripting.
And don't forget to check out
our session website.
We'll be posting that nudge
script soon, so you can download
it, check it out, and use it as
the basis for your commands.
There's one more thing I wanted
to cover with you guys and
that's just the current LLDB
print commands.
So you might be familiar with
po.
We used it a lot during the
demo, and we saw that po
requests the debug description
of an object, and you can
customize that.
And that's because po is simply
an alias for expression --
object description or expression
-O, compared with the p
commands, which is simply an
alias for expression.
And it uses LLDB's built-in
formatters to show a
representation of that object.
The third command that's
important to know is frame
variable.
It differs from the previous two
in that it doesn't have to
compile and evaluate an
expression at all.
It simply reads the value of the
name variable directly from
memory and then uses LLDB's
built-in formatters.
So the choice of which command
to use is more personal
preference and the type of
information you want to see
while debugging.
But it's important to remember
that if you ever end up in a
situation where expressions are
failing or so po and p may not
be working for you, if you need
to inspect a variable in the
current frame, then frame
variable should still work for
you.
And with that, I'd like to hand
over to Sebastian, who's going
to tell you about some advanced
view debugging techniques.
Thank you.
[ Applause ]
>> Thank you, Chris.
I'm excited to show you tips and
tricks how to get the most out
of Xcode's view debugger.
And we'll also take a look at
the enhancements we made for
Xcode 10 to provide a great
debugging experience when you
adopt a dark look in macOS
[inaudible].
And we'll take a look at this in
a demo.
So let me switch to the demo
machine, and I'll be using the
same project that Chris has been
using and you already saw that
there are two more bugs we have
to solve.
However, I'm not going to be
using the iOS App, I'm going to
be using the [inaudible].
So we can see the Mac version of
our Solar System app here, we
can see that looks pretty good
in dark mode but there are two
bugs that we have to solve
today.
First of all, the planet image
is not centered horizontally
correctly and that is a very
obvious bug.
You can see on the right-hand
side this Earth image is shifted
to the right-hand side, so we'll
take a look at this problem.
And the second bug is that the
description in a popover is not
readable in dark mode.
Let me show you what that is
referring to.
When I switch to this app, I can
bring up the orbital details
information in this popover
here.
You can see that the labels at
the top and nice and readable;
however, the label at the bottom
is so hard to read, I really
have to select the text to read
it.
So these are the two bugs we
have to take a look at.
Let me hide the to do list and
let's jump right in.
So what I want to do is I want
to capture the view hierarchy of
this application with Xcode,
then inspect it.
We'll find the problem and then
hopefully find the fix, so we
can all have a beer.
The problem is when I switch to
Xcode to capture the view
hierarchy, this popover will be
dismissed since the application
goes into the background, and we
won't be able to capture its
view hierarchy.
So what we have to do is we have
to capture the app in its active
state, and I will show you two
ways how to do that.
And you can see when I now
switch to Xcode how this popover
is being dismissed.
First of all, we can use the
touch bar, and I'll show you
what that looks like by bringing
up the touch bar simulator from
Xcode's window menu.
I'll switch back to the Solar
System app and bring up the
popover again.
And taking a look at the touch
bar, you can see that there's
this spray can icon, and when I
tap that on the touch bar, you
can see that there's a subset of
the debug option that Xcode
provides in its debug bar.
So it's a very convenient way to
access these from your touch
bar.
And as you can see, I can bring
those up with Xcode being in the
background, so you can access
them even when you're, for
example, developing your app in
full screen mode.
And one of these options allows
me to capture the view
hierarchy.
Now I'm not going to do that
because I know that not
everybody has a touch bar
[inaudible] so I will show you
an alternative.
I'm going to close the
simulator, and I will make use
of command click to perform the
click on the button in Xcode's
debug bar.
Command click is a system-wide
gesture that allows you to
perform mouse events without
activating the application that
the mouse event is performed on.
So this allowed us to invoke the
capture of the view hierarchy.
The debugger paused the
application while it was still
in its active state, and we can
see that the UI still renders as
if the app was front most and
the popover hasn't been
dismissed.
If you're wondering why that
spinning [inaudible] is coming
up, that's because the
application is paused in the
debugger and doesn't respond to
mouse events anymore.
Now, you may be wondering, if we
take a look at the view debugger
here, why the popover isn't
visible.
Don't worry, the view hierarchy
has been captured.
I'll get to how we take a look
at the popover once we get to
that bug, but first I want to
take a look at the layout issue
of this image view here.
So I'll select the image view
here and I'll zoom in a little
bit.
When we take a look at the
inspect on the right-hand side,
we can see that this is an
underscore NS image view simple
image view.
Now the fact that this is prefix
with an underscore usually hints
at the fact that this is an
internal class from a system
framework and not what we use
when we set up image views in
code or in interface builder.
So let's take a look at this
object in the view hierarchy.
And I can do that by using the
navigate menu, select reveal in
debug navigator.
We can now, on the left-hand
side, see it relative to its
super and sub views.
Now, we can see that super view
is in fact an NS image view, so
that's what we're looking for.
We can also see that its super
view is a planet globe view, and
the super view of the planet
globe view is an NS effect view.
So I will select the image view
here and we can now also see
other properties of the image
view on the right-hand side.
So let's inspect the layout of
this view.
I'm using auto-layout in this
application, so I want to take a
look at the auto-layout
constraints.
I can show the constraints using
this button here, and we can now
see all the constraints that
currently affect the layout of
this view.
You can see that there's, for
example, an [inaudible]
constraint here.
We can also see this vertical
line here, which is an alignment
constraint.
When I select this constraint,
we can see all the properties in
the inspect on the right-hand
side.
If you're wondering why the view
debugger only shows you
wireframes right now, that is
because it only shows the
content of the views that
currently participate in the
layout of the selected view.
And since all those views
themselves don't have content,
we currently only show
wireframes.
So with the constraint selected,
let's take a look at the inspect
on the right-hand side.
We can see that it aligns the
horizontal center of the image
view with the horizontal center
of the planet globe view and it
does so with a constant of zero.
So it aligns it horizontally
centered in its super view.
Now let's select the planet
globe view from the debug
navigator, and we can see that
it's a little bit larger towards
the left-hand side but it aligns
on the right-hand side.
So that's a little bit weird
because we just saw the
constraints aligning them
correctly or exactly
horizontally, but that's not
really what we see in the view
debugger.
So let's take a look at the
constraints of the planet globe
view and see if we can
understand what's going on.
I'll select the leading
constraint here, and taking a
look at the inspect again, we
can see that it aligns the
leading edge of the planet globe
view with the leading edge of
the NS visual effect view, which
we saw in super view.
So it simply inserts it relative
to its super view and it does so
with a constant of 30, so that
makes sense.
And then the trailing constraint
here aligns the trailing edge of
the planet globe view with the
trailing edge of the super view,
and it also does so with a
constant of 30.
Now the fact that this
constraint doesn't attach to
anything on the right-hand side
is a little bit suspicious and
makes me wonder if we see the
whole picture here.
In such a situation, it's
usually a good idea to see if
there's any content that's
currently being clipped that we
don't see by default.
And you can do so using this
button down here to show clip
content.
Now, when I enable this
functionality, you can see that
the planet globe view actually
extends past the window bounds
towards the right-hand side, and
now the horizontal centering
constraint makes sense again
since it actually correctly
centers it in the super view.
However, the super view just
extends past the window.
This is a very common issue if
you set up your constraints in
code, where you accidentally
swap first and second item and
thereby get the layout direction
wrong.
Or you accidentally invert the
constant, and in this case, we
used 30 instead of using
negative 30 to insert it towards
the left-hand side.
So what I want to do is I want
to try out that fix to invert
the constant.
I will make use of the same
technique that Chris introduced
earlier by simply applying it
through LLDB.
So, with this constraint
selected, I'll select edit,
copy.
And I will bring up the console
area at the bottom, and what
this copying gives me is the
[inaudible] pointer to the
selected object, and that works
for all objects that you select
in the view debugger as well as
in the memory graph debugger.
It makes it super convenient to
use them in the console.
So let's --
[ Applause ]
Thank you.
Let's print the debug
description and we can confirm
that the constant is in fact
positive 30.
That's what we saw in the
inspector as well.
So let's set the constant to
negative 30.
I'll type e, which is the super
short form of expression by
casting the pointer and then say
set constant to negative 30.
We have the same problem that
the path application doesn't
update that Chris saw earlier,
so what I'm going to do or what
I have to do is I have to
[inaudible] the ca transaction
to [inaudible] application
schedule update of its UI.
I can use the handy command that
Chris added earlier, so I can
take the command from here.
And we can now see that the
planet image is horizontally
centered correctly, so inverting
the constant was the correct
fix.
So we were able to confirm this,
so let's apply this change in
our code.
With the constraints selected,
you can see that on the
right-hand side I can see this
back trace here, and that is the
allocation back trace of the
constraint and tells me exactly
in which frame it was allocated.
So it allows me to jump right to
the code where I created that
constraint.
Now to have this creation back
traces show up, you have to
enable mailx backlogging and let
me show you how you enable that.
You go to your scheme up here
and [inaudible] edit scheme.
And in the diagnostics tab of
the scheme options, under
logging, you can enable mailx
stack logging for all
allocations in prehistory.
And that will provide you with
these handy allocation
backtraces for all selected
objects in the view debugger as
well as in Xcode's memory graph
debugger.
Now, when I mouse over this
stackframe here, we can see the
full name of that frame.
And we can see that it is the
set up planet globe view layout
method in the scene view
controller.
And I can jump to these points
in code using this jump to
arrow.
And I will do so by holding down
shift control option, which
brings us this navigational
[inaudible] and allows me to
open this file in a separate
window.
Now you can see that the line of
code where the constraint is
allocated is highlighted.
We can see the constant of 30,
and I can invert it to negative
30.
I'll save this file and close it
and we're done with our first
bug.
Perfect.
So the second bug was that we
wouldn't be able to read the
description inside that popover,
so let's take a look at that.
First of all, I want to disable
constraint mode and clipping so
we see the constant again.
And I will also clear the
console.
Now, I showed you at the
beginning how to capture this
[inaudible] in its active state
so we would be able to get to
the view hierarchy of the
popover while it's open;
however, we don't see it.
That is because the view
debugger only shows you a single
window at a time.
Let me show you how to take a
look at other windows.
When I scroll up in the view
hierarchy and walk up the view
hierarchy to eventually hit the
window of the current window
that we see, we can see that
it's hosted by a window
controller.
And if I collapse this root
level item here, we can see that
there's actually another root
level item, which is exactly
what we're looking for.
Our popover.
So if your application has
multiple windows, and that is
true for macOS and iOS, they
show up as multiple root level
objects in the outline on the
left-hand side.
So take a look there, if you
think your application should
have multiple windows that
should be captured by the view
debugger.
So we can take a look at this in
3D, and we can see that there's
this large view that's obscuring
clicks to the labels.
I would like to take a look at
the labels to inspect them.
There's a trick to click through
views in the view debugger.
You can simply hold down
command.
So I can click through this view
in front and select this blue
label.
So let's take a look at the text
color for this label.
I want to first look at the
labels that look great in dark
so that we can hopefully derive
a solution for the problematic
label at the bottom.
So let's take a look at the text
color and we can see that this
is a RGB color resulting in this
blue and we can also see that
the inspector provides us with a
name for this color.
Now this indicates that this
color is coming from an asset
catalogue in our project and
with Xcode 10, you can provide
multiple variants of a color for
a single color that you define.
So, for example, you can provide
one for light and one for dark.
And which variant is picked is
determined by the appearance of
a view, and you can get that
information in the inspector as
well.
And I scroll down here to the
view section in the inspector.
You can see that there's
appearance and effective
appearance.
Now appearance is not set
explicitly on this view.
And this is a very common
scenario because most views
inherit the appearance from
either one of the super views,
the window or the application.
But you can see the inherited
effective appearance here.
And that is vibrant dark, so
that determines which color of
the color that you define in
your asset catalogue is
selected.
Okay. Actually, while I'm down
here in the inspector, I want to
point out the description
property, which is the debug
description of that object.
And Chris showed you earlier how
to provide a custom debug
description for your objects.
So you don't only benefit from
providing a great debug
description in your console when
you use po on an object but you
also benefit from it in the view
debugger, since it shows up
right in the inspector.
Okay, so let's get back to the
text color.
And I want to select the second
label since that also looks good
in dark.
And in this case, we can see
there's also a name color.
It's a label color, but it's
prefixed with system.
And that indicates that it's not
coming from our own asset
catalogue but from the system,
and of course, system colors
automatically adapt to
appearance changes as well.
Now, taking a look at the
problematic label here, we can
see that it's this very dark
gray, and it doesn't have a
name.
That means it's a custom color
that doesn't adapt to appearance
changes.
So what we want to do is we want
to change its color, its text
color, to a system color.
So with this object selected,
I'll hit command c.
I'll type e, paste in the
[inaudible] pointer and say set
text color to NS color text
color.
Again, I have to [inaudible] to
see a transaction.
And we can see that the popover
now updates, and the font is
nice and readable.
Now, I'm not going to apply this
fix in my storybook file.
But one thing I want to point
out is it's very important that
you verify that your application
still looks good across all
appearances when you make a
change to your [inaudible] now
that there are multiple system
appearances.
And I will show you how to do
that.
I will continue running, and
instead of switching my entire
system appearance to light to
take a look if the label still
looks good on a light
background, Xcode 10 provides
you with a way to override the
appearance only for the target
application.
And you can use this button in
the debug bar.
And I can select light here, and
you can see that the application
now is presented in a light
appearance.
And I can bring up the popover,
and we can verify that the text
is nice and readable.
So that confirms that we fixed
our issue.
Now, since this is a very common
action to take a look at your
application in different
appearances, we actually made
that option available in the
touch bar.
I will show you [inaudible].
I'll bring up the touch bar
simulator again, and with the
popover opened, I can select
this option here.
And you can now see all the
override options right in your
touch bar, so even if your
application is in full screen
mode, for example, I can access
these.
Let me switch to high contrast
light, which enables the
accessibility feature of high
contrast as well as overrides
the appearance to light, so you
can make sure that your app
looks good in that configuration
as well.
And, of course, I can go back to
the system appearance as well.
So we confirmed that our issue
is fixed.
And that allows me to check off
this on our bug list [inaudible]
with this, we're at the end of
the demo and I will go back to
slides.
[ Applause ]
So let's recap what we just saw.
I showed you how to use reveal
in debug navigator to orient
your current selection in the
hierarchical outline on the
left-hand side.
I showed you how to show clip
content, and we made use of the
auto-layout debugging
functionality to track down our
constraint problem.
I showed you how to use the
object pointers that you can
easily access by just copying
the selected objects and use
them in LLDB.
We had a look at the creation
backtraces, which are available
if you have mailx stack logging
enabled in your scheme options
to jump right to the code and
applied a change that we needed
for our constraint.
We had a look at the debug
description that's conveniently
available in the inspector of
the view debugger.
And we had a look at click
through to select a view that
was behind another view.
And regarding debugging dark
mode, we saw that you can easily
override the appearance of the
target application right from
Xcode's debug bar or from the
touch bar.
I showed you how to capture a
Mac app in its active state.
And we had a look at the name
color information and its
appearance information that is
now available in Xcode's view
debugger inspector.
If you want to learn more about
adapting the dark mode in your
Mac application, please check
out the videos for these two
sessions.
That brings us to the end of
this talk.
If you want more information
about the talk, including the
[inaudible] script that Chris
showed you earlier, it will be
posted on the session's website.
And if you have any questions to
any of the content of this
session, or debugging in
general, there's a profiling and
debugging lab tomorrow morning
at 9 am.
Chris and I will be there.
We'll be happy to answer any
questions you may have.
And there's also an iOS memory
deep dive talk, in case you're
interested, in memory debugging
that is also tomorrow.
With that, I hope you have a
fantastic time at the Beer Bash
and enjoy the rest of the
conference.
[ Applause ]