WWDC2017 Session 236

Transcript

[ Applause ]
>> Good morning everybody, I'm
Vincent Hittson, Cocoa engineer
along with Rachel Goldeen, Cocoa
engineer and welcome to Cocoa
Development Tips.
Rachel is going to start us off.
>> Good morning, we have a wide
array of tips to show you this
morning, we're going to speed
through them.
And it was a little tricky to
figure out how to order them,
what kind of sequence or
numbering system to use, we
think we got something really
good.
I'm going to start you out with
a quick tip to get things going.
Number pi internationalization
because it's all Greek to us.
Just take a look at the Xcode
Scheme Editor and in the run
section you'll notice there's
this location debugging section.
You can check the show
nonlocalized strings checkbox
and see where your app is
missing localized strings.
But I'd like to focus your
attention on this application
language menu.
Down at the bottom there's a
list of pseudolanguages that you
can use before your translations
have been made and this will
show you if your app is ready
for those translations.
Okay that's it, back to Vince.
>> Okay tip number zero, user
defaults since zero makes a
pretty good default value.
So, you have your standard user
defaults and this is what you
use to get and set your user
preferences, but it's not just a
big bag of preferences it's
actually a set of domains and
each of those domains have their
own bags and here's a subset of
those.
And when you ask the standard
user defaults for a value it
checks each of these domains in
order returning the first value
it finds.
And you can set values in these
domains using their own
techniques and we're going to go
through them in reverse order.
So, starting at the bottom is
the registration domain, it's
last in the list and you use the
register method, passing in the
dictionary of the user defaults,
keys and values.
And it's temporary so it's only
available for the lifetime of
this particular process.
The next time your app launches
you'll have to call register
again.
So, use this to provide initial
values for your defaults without
setting them permanently.
Next up is the global domain.
This is shared by all
applications and it's commonly
used by system frameworks to
store systemwide preferences.
And you can set it using the
defaults command line tool, but
it's persistent so any changes
you make are going to be
permanent so be aware of that.
Next up is the application
domain and when you set a value
on the standard user defaults
using the set method it goes
here.
And this domain is persistent so
the next time your user launches
your app it'll have the value
that you set so you'll want to
use this when your user actually
changes a preference from the
initial value.
Last or first, but not least is
the argument domain and the
argument domain is really cool.
So, you can pass your user
defaults key as an argument to
your application along with the
value and you prepend your user
defaults key with a hyphen.
And it's first in the list, so
it overrides the values of all
those other domains.
And it's temporary, so it's only
set when your app is launched
with these particular arguments.
If you run your app and these
arguments aren't there the
values won't be there.
So, this is really handy if
you're trying to reproduce an
issue involving user preferences
and you don't want to actually
set them on your machine or for
just enabling a debug flag.
And speaking of debug flags,
AppKit has a couple, here's some
that you can use.
NSiewLayoutFeedback
LoopDebuggingEnabled, you can
set this to yes if your app is
getting stuck repeatedly doing
layout.
The feedback loop debugger will
help you track down that that
issue.
Next is NSApplicationCrash
OnExceptions and use this in
NSApplication if it catches an
exception it'll crash your app.
So, it's a good thing to have on
during development so you find
those issues.
And you probably want to use the
argument domain to set these so
you don't shift with them so
that's a good idea.
Here's a neat thing about the
standard user defaults, it
supports key-value observing or
KVO.
You just add your observer with
the user defaults key as your
keyPath and any time that value
changes even if it's from
another process you'll get
notified.
So, here's an example of what
that looks like.
We add an observer using the
addObserver for keypad options
context method and pass our user
defaults key as a sting.
And of course, when we're no
longer interested in watching
for these changes we remove our
observer.
And in the meantime, you'll get
an observeValue for keyPath call
out any time your defaults value
changes.
So that's about that, here's
what that looks like in diagram
form.
So, if your application sets a
user default and you have an
observer registered it will be
notified of that change.
And similarly, if another
process sets a value for one of
your user defaults that you're
observing, you will be notified
of that as well.
So, it's pretty straightforward.
Here's another neat trick.
So, if you want that new keyPath
syntax and that block-based
observe method that you heard
about in what's new in
Foundation you can do that with
user defaults.
You just need to add an
extension and add at an @objc
dynamic property that has the
same name as your user defaults
key and that's really important.
If the names don't match this
isn't going to work.
And you can as a convenience
implement your property getter
to return the value.
And then you can use the
block-based observe method along
with the keyPath literal syntax
that's new and it works just
like you'd expect.
So that's it for user defaults.
Next up is tip number 64, what
is that?
Oh, base 64 right.
So, for those that aren't
familiar base 64 is an encoding
that lets you represent any data
using a simple set of ASCII.
So, this is handy of you want to
have a test or an easy to copy
paste and share snippet of code
that includes some binary data
and you don't want to worry
about packaging that up and
having to look it up and all
that.
And NSData supports base 64 and
you can create a data from the
base 64 string using the base 64
encoded initializer on data and
you'll get back your data.
And then you can use it [sound
effect] for example, to play a
sound.
And now if you want to create
these base 64 strings you can
use the base 64 encoded string
method on data to get those
strings.
There's also a base 64 command
line utility that you can use in
terminal to get your base 64
strings and that's base 64.
Now back to Rachel.
>> Number 2X, we're going to
talk about asset catalogs a
little bit.
Many of you are already using
asset catalogs, you know that
they organizer your images,
speed up launch times, reduce
storage space.
And I'd like to talk about a few
things that were added last year
in macOS Sierra and iOS 10 in
case you hadn't heard about
them.
First, there's layout direction.
This action image is symmetrical
and it doesn't matter, it'll be
the same both left to right and
right to left so the direction
is fixed.
But here's a GoForward image and
in right to left languages
GoForward means to the left
whereas left to right means it's
going to the right.
So, we want it to mirror for the
other language.
And then a little more
sophisticated is when you have
an image that needs to be
different for the layout
directions, but isn't a mirror
image, such as this autofill
image where the dots indicating
what has been written are to the
left of the pencil in left to
right and to the right of the
pencil in right to left, but the
pencil still tilts to the right.
So, they're not mirror images
and the designer decided it was
worth it to provide two
different images, but asset
catalogs will handle this for
you.
Next, I'd like to talk about
display gamut and that's the
different kind of color
representations that are
available on different displays,
the older displaces use sRGB,
the newer ones with brighter,
richer colors use Display P3.
I've overexaggerated the
difference between these images,
but just to show you that asset
catalogs allow you to specify
specific images for the two
different display gamuts.
In iOS 11 and macOS Sierra we
added colors to asset catalogs.
So, you can make a named color
just my simple accent color here
and you can also if you need to
specify sRGB and Display P3
versions of that color.
To use it in code we want to use
this new API.
You can add your name to the
extensible NSColor.Name enum and
then use the color named
initializer with your name to
create your color it's as easy
as that.
Next up number one, unit tests.
Sometimes there's a
psychological barrier to writing
unit tests, it seems like it's
going to be hard, but I'm here
to show you how easy it can be.
Create a new file in Xcode, make
it a unit test case class, Xcode
will give you a template, you
can fill it in with something as
easy as this.
Here I've created my addCatImage
and I'm checking to see that
it's not nil.
It's just a sanity check, is my
asset catalog on and working.
And once you've got one unit
test written it's much easier to
write additional tests and
prevent bugs from getting out to
your users.
Back to Vince.
>> Okay tip number 27, NSBox.
Sometimes all you want is a
simple view that shows a
background color.
And you control it may be like
this, so you can implement one
and you can make it layered back
even and that's fine or you can
let AppKit do it for you.
We have NSBox all you need to do
is set the box type to custom
and then configure it setting
the fill color.
And this would be more dynamic
than just setting the background
color on a layer because if the
color's a system color that
changes dynamically based on
appearance or context NSBox will
update automatically.
And even better, you can drag it
out of Interface Builder and
configure it from there.
And there are several properties
that you can use to configure a
custom box.
There's border width, corner
radius, border color and of
course, fill color so use that.
And you can even set a content
view on a box to show another
view inside the box, so it's
really easy.
And that's not all, sometimes
all you want is a simple
separator and NSBox has you
covered there too.
All you need to do is create a
box with the separator type and
position it however you like.
And of course, it's available
from Interface Builder.
So that's NSBox it should make
creating your user interfaces a
lot easier.
Next up is tip number eight,
restorable state.
So, macOS has a feature where
you can restart your machine and
all your running applications
come back and using this
invaluable trio of methods on
NSResponder you can bring your
user right back to where they
left off.
And most AppKit controls will
have these implemented for you,
but if you have your own
controls or restorable state,
you can implement them
yourselves.
And this is what that might look
like.
So, you have your properties
that are your restorable state,
when they change you invalidate
restorable state.
And then at some point, the
system will call in code
restorable state so you take the
values from your properties and
put them in the coder and then
when your application is
relaunched we'll call restore
state with coder and you take
the values out your coder and
put them in the properties.
It's pretty straightforward and
pretty easy, but there is an
easier way.
NSResponder has this class
property restorableStateKeyPaths
and this can point to any
NSCoding conforming type and
AppKit will use key value
observing to watch for changes
to these properties.
So that means when restoring
will automatically set the
values for you, so it's really
easy.
Do note that these properties
are marked @objc dynamic and so
that's key value observing can
do its automatic notification.
And you can still use this with
the other state restoration
callouts.
You can use this for your simple
properties and then use those
other callouts for other bits of
restorable state you may have,
they work together.
All right, now back to Rachel.
>> Okay I'm going to tell you a
sequence of tips about Core Data
that build upon each other and
I'm starting with number 13
because you may have been
unlucky enough in the past to
have to set up a Core Data Stack
before the existence of
NSPersistentContainer that was
added last year in Sierra and
iOS 10.
Core Data Stack consists of a
managed object model and then
you have a persistent store
coordinator that coordinates one
or more persistent stores and a
managed object context.
And to set that up took a fair
amount of code.
This is a reduced version of the
code you needed to write to get
that going.
But last year with the hard work
of the Core Data team there's
the NSPersistentContainer class
setting encapsulates the stack
and it's easy now.
You just have to do this.
The key line is to initialize
your persistent container with
your model name.
Moving on to number 21, arrays.
I've got my Cat Wrangler
application, one of those Cat
management applications and I
have a cat entity in my data
model with a name and a photo
attribute.
And I realize cats also have
behaviors, such as attacking
dust particles, staring at
walls, and playing keyboards.
So, I'd like to add an array of
behaviors because I think a
string is a good representation
of a behavior and I just want an
array of strings.
But there's no array type here,
well I just transformable
because that can be anything.
So, I make an array that's not a
good idea.
There's performance issues,
there's overhead for serializing
and deserializing arrays and
their contents and also any such
requests are going to be a lot
slower if you do things this
way.
So, we recommend instead making
a behavior entity and making a
relationship to the behaviors.
This would be a too many
relationships because there's
many behaviors and I want to
check the ordered checkbox if I
care that they're in a certain
order.
In addition, this has the
benefit of allowing me to use a
richer data model for my
behavior, I can add a duration
for example.
And so, your data model will
become more representative of
what you're trying to do.
Tip number 34, core data
migration.
Migration is one of the real
strengths of using Core Data.
I shipped Cat Wrangler and then
I realized cats also eat food
like grass, cupcakes, meat, and
of course broccoli.
So, I need a new data model, I
copy my Cat Wrangler data model,
make Cat Wrangler 2 and I can
add my food entity to it.
In Xcode, I simply change the
current model version to Cat
Wrangler 2 and then lightweight
migration is handled
automatically if you've used
NSPersistentContainer.
If you set up your own stack you
have a couple of options that
you need to set to make the
migration happen and that's all
you have to do.
Tip number 55, error handling.
In a perfect world we'd handle
all errors, but everybody knows
it's not a perfect world, the
world itself isn't entirely
round.
So, if you can only handle
errors in one area in Core Data
the most important part is when
you've added a persistent store.
If things don't work there
nothing's going to go right in
your application.
If you use persistent container
you can see there's the
loadPersistentStores method and
some of the things that can
happen is you can run out of
storage space, you can have
permission issues or data
protection or you can have an
older file that can't be opened,
you don't have a proper
migration strategy for it.
And if you're wondering how to
present those errors to users
Vince has something to tell you.
>> All right, tip number 404,
NSError.
So, in the rare event that
something goes wrong in your
application you want to give
your users great error messages
and Cocoa APIs like Core Data
give you error messages that are
fully localized and ready to
present to the user and that's
really easy to do.
Any responder has a presentError
method, you pass in your error
and it'll go up the responder
chain, if anything NSApplication
will catch it and show a
dialogue.
And if you have your own
responder subclass that's
well-suited to presenting an
error you can override this and
present the error yourself.
And here's the trick about these
Cocoa API errors, you can create
them yourself all you need to do
is create an NSError, pass in
the NS Cocoa error domain, a
code that matches what went
wrong and then a user info that
might have some additional
information like the file URL
you're acting on.
And you'll get a nice localized
user presentable error just like
that, just like the ones we
return.
And we have a lot of error
codes, so hopefully you can find
something that matches what went
wrong.
This is a snippet of foundation
errors.h, the generated
interface, so feel free to look
through this, we're not going to
go through them all.
And if the error is close, but
could be better you can
customize them with the user
info.
There are several user info keys
that will let you alter pieces
of the error message.
Here we're altering the recovery
suggestion because we know that
this error occurred in the
context of a download and we can
tell the user that to help them
recover.
And by the way, if you're using
Xcode 9 you can use this new
Cocoa error method to make
creating Cocoa domain errors
even more convenient.
Now if you need an error that
Cocoa doesn't provide it's still
easy to make your own.
All you need to do is define a
domain, your error codes, and
then you create an NSError with
that domain, one of those codes
and a user info with keys that
describe the error message,
provide the error message.
And you don't need to give those
keys up front in your user info
you can call this NSErrorClass
method, set user info value
provider, and you get at your
domain.
And when an NSError with your
domain looks up at a info key
and it's not found it will call
this block.
And so, you can lazily return
values for those keys.
And what that means is now it's
super easy to create your errors
you just make an NSError with
your domain and code and that's
it.
Now there are a lot of
properties on NSError, check out
the header for more.
I do want to note that a lot of
them have user info keys that
correspond so you can use them
in your user info value provider
and return values lazily, so
give those a look.
Now next up is tip number six,
shared keysets.
So, if you were creating a lot
of dictionaries with the same
known keys you can use a shared
keyset and your dictionaries
will be more compact and
performant, thanks to proof of
hashing.
So, you create a shared keyset
using this NSDictionary class
function and it comes up with
the perfect hash and then you
can initialize your
NSMutableDictationaries with
your shared keyset.
Here's what this might look
like.
So, you create your shared
keyset, it is not trivial to
compute a shared keyset so it's
a good idea to stash it away and
reuse it and create as many
mutable dictionaries as you
want.
And you create your mutable
dictionary and then you can use
it like any other mutable
dictionary and even you can put
in keys that aren't in your
shared keyset they just won't be
as performant.
So that's shared keyset.
Now back to Rachel.
>> We've come to hexadecimal A
for accessibility, get AX
accessibility?
And Apple has many ways to make
your products accessible to all
users.
I'm just going to go over a few
small things.
VoiceOver is not small, but I'll
talk about it briefly.
It comes built-in on all Apple
products and it's easy to set up
in Interface Builder, I have my
add cat button and I just fill
in the accessibility description
add cat and then VoiceOver knows
what to say.
Also remember to test your apps
at low resolutions, low vision
users want to see things bigger
on the screen.
So, for example, here's my Cat
Wrangler/screen that I'm really
happy with.
I try it at a lower resolution
it goes off the screen, don't
let this happen to you.
Last, I'd like to point out the
existence of the Accessibility
Inspector that's available from
the Xcode menu.
Launch it, run it on your apps
and see where you can improve
your apps in terms of being
accessible.
Back to Vince.
>> Okay and now toward infinity
and documents.
So, users today are very
demanding, they expect a lot
from document-based apps.
They don't want to worry about
saving.
They want it to be easy to name
and organize their documents and
they want powerful version
control.
And they even want features like
iCloud document sharing that's
new in macOS High Sierra that
you will never even have heard
about until this week.
What's a developer to do?
Well you can use NSDocument and
NSDocument has a class property
autosavesInPlace.
By default, it returns false,
but you can override it and
return true and you get all of
those features for free, so it's
pretty great.
Check out NSDocument if you have
a document-based application.
Next up is number 42, the answer
to reporting exceptions.
For the most part, when
NSApplication catches an
exception it will just log it
and let your app continue
limping along.
But if you want to add
information or handle those
exceptions differently you can
override this NSApplication
method to do your own thing.
Now back to Rachel.
>> All right, to err is human
and to [inaudible] computer,
especially when the humans have
erred so we'll talk a little bit
about debugging.
You know here's many, many
debugging tools and I'm just
going to show you a couple that
are somewhat hidden in Xcode.
Right above the debug console
there's this row of buttons,
there's the debug view hierarchy
button, debug memory graph and
simulate location.
I'm just going to show you a
little bit about the view
debugger.
Here I have this simple browser
sample application and I can
expand out the views in order to
see what's going on, is there a
view that's hidden by another
one, which view is doing what.
I can see where the views extend
off when they're clipped views,
there's many other things with
auto layout that you can use in
this view debugger so check it
out and it'll simplify your
ability to debug your view
problems.
Sometimes the bugs are in our
code, in which case I've come to
tip 30512012 right, bug reports.
To make it easier for us to fix
the bugs include steps to
reproduce that's the very most
important thing.
We love it when you put sample
apps in your bug reports,
especially when they build and
show the problem on the platform
that you care about.
Include any resources that are
needed to reproduce your
problem.
And then logs, such as
sysdiagnose.
There's a page on
developer.apple.com that shows
how to collect logs for all of
our platforms and look through
there see whatever might be
helpful to the person who's
taking a look at the issue and
that'll help it all go faster.
Back to Vince.
>> Okay tip number 44.1, here's
a really easy way to add bells
and whistles to your
application.
NSButton has a property it's
called sound, it takes an
NSSound and it's really easy to
set.
And if you do when the user
clicks the button it will play
the sound and you can even set
it in Interface Builder.
So, use this to give your UI
that extra pop that your users
are clamoring for [sound
effect].
So now tip number 29, I'm going
to give a demo not of an NSSound
playing from a button that would
be a silly thing to demo, we're
going to show you a macOS app.
It's a daring entry into the
overcrowded market of cat
management applications it's
called Cat Herder and it's
completely unrelated to that Cat
Wrangler app you might have
heard about and we're going to
build it from the ground up.
Okay, so we have our project
already and here we have our
specification for our Cat Herder
application and it looks like we
have a list of cats on the
left-hand side and some add and
remove buttons.
And then for the selected cat we
can edit the name and photo.
Okay that seems pretty simple
and pretty straightforward.
Luckily, we have a designer that
already created a nib for us and
we can go ahead and look at that
and it looks pretty good.
We can run it.
And there we go, but nothing's
really hooked up, nothing's
working so nothing does
anything.
So, it's time to write some code
to get this all hooked up
together.
But what if I told you that
there's a way we can get this
user interface working without
needing any code.
And macOS has a technology
called bindings that will let us
do exactly that.
So, we can go ahead and go to
our storyboard and we have our
table view here and a table view
is going to show a list of cats,
which is probably going to be
backed by an array.
And it turns out we have a class
called array controller in the
object browser here and we can
go ahead and drag that into our
scene.
And an array controller lets you
create bindings to an array of
objects.
So, we can go to the attributes
inspector here in the upper
right and select attributes and
you can have your array
controller manage an array of
whatever class you like or a
Core Data entity.
And we're going to use an
NSMutableDictionary as a default
since that just lets us get
things up and running.
So, we want our table view to
show our list of cats and we can
do that by drilling down into
our table view until we get to
the table column.
And we can go over here to the
Bindings Inspector, it's in the
upper right it's that little
swirly boxy looking thing and we
can click on that.
And bind the value of the table
column to the array controller's
arranged objects.
And the arranged objects is a
property on the array controller
that is your array of cats
that's meant for the view side
of things.
So now our table view will
create a row for each cat in our
array and that means it will
create a table cell view for
each cat in our array and have
the object value of that cell
view set to that particular cat.
So, we can go into our table
cell view and we have an image
view, a thumbnail and the label
for the name.
And we can go ahead over back
again to our Bindings Inspector
and select bind to the table
cell view's object value and
that object value again is going
to be one of our cats.
And we can keyPath into that and
specify the name of the cat and
it will look that up in the
dictionary and show it.
Similarly, we combined the photo
of the cat to the image view
here just like that.
Now that's our table view that's
it, it's now bound and wired up
to the array controller and
it'll show its contents.
Next up we have these buttons
here in the corner, so array
controller actually has ID
actions for adding and removing
objects.
And the add method does about
what you'd expect, it creates a
new instance of the object it's
managing, in our case an
NSMutableDictionary and adds it
to the array so we can just wire
that up.
And there's also of course, a
remove action that does what we
expect, it takes the selected
items and removes them from the
array.
And of course, we need to add
some bells and whistles to our
application, that is what we're
known for.
Just like that and that's the
buttons.
All that's left now is the
detail view on the side.
So, we can go back to our text
field -- go to our text field
here that is the name that will
allow us to edit the name and we
go back to the Bindings
Inspector and we can bind to the
array controller selection.
And the selection of an array
controller it's a property and
it's a proxy object that
represents the currently
selected items in the array.
So, we can keyPath into that
selection to actually allow us
to edit and show the name of our
cat and similarly, we can do the
same thing with the selection
for the photo and that's it.
We've now wired up our
application with no code.
So, let's go ahead and run it.
And we can add our cats, give
them names, give them pictures,
add more cats, it's a really
amazing application it's so
handy.
And since we're using bindings
you can update these and they'll
get updated immediately in the
table view.
Just like that we can drag in a
new picture or a Quick Look and
drag in a new picture and it
gets updated in the table view
as well.
And of course, we need to make
sure our bells and whistles are
working [sound effect], great.
So that's bindings.
[ Applause ]
So, you can use bindings to wire
up your user interface to your
model without having to write
any of that glue code.
Next steps might be using a real
cat class instead of a
dictionary or even a Core Data
entity that way you'll get
persistent -- your list of cats
will persist.
But you'll never have to write
and for that you will have to
write some code, but you won't
have to write that glue code for
your user interfaces and that's
bindings.
And back to Rachel.
>> Okay, so number 29 may have
seemed like the end, but we have
tip and plus one, add your own
tips.
Some ideas for how to get
information and learn more.
We have new revised
documentation this year, topics
are grouped by task.
There's a hierarchical structure
that allows you to drill down,
such as this example of going
down and looking at NSView.
There's three different styles
of presentation, there's
reference documents, there's
conceptual articles and sample
code.
So, there's different ways of
approaching the material.
We also highly recommend that
people check out the release
notes on all of our platforms.
This is information straight
from the engineer's fingertips
about what's new in every
release.
Header files are always a good
source of information with line
by line comments about all of
our APIs.
On Wednesday in what's new in
Cocoa we asked people to tweet
their own Cocoa development tips
and we collected some of our
favorites from Twitter.
It was hard to choose, but
here's some of the tips that we
liked.
NSHashTable is like NSSet, but
it can contain arbitrary
pointers such as void star.
And also, you can weekly
reference objects from
NSHashTable.
Sometimes generics in
Objective-C are a little bit
cumbersome looking and you can
use a typedef to simplify the
way they look and clean up your
code.
In Swift, you can use typealias
to do the same thing.
And if you're coming to the Mac
from iOS you may remember that
NSWindowController is often a
class that you want to use
rather than NSViewController.
So, check it out and think about
it.
And our last tip from Twitter is
that in Xcode you can use Add
Expression to get Quick Look
previews for arbitrary
addresses.
So, here are some screenshots
showing how that would work.
I'm stopped in the debugger and
I print out the image, this add
cat image.
I grab the pointer here and I
can use the Add Expression menu
item and paste in my image
pointer.
And then if I hit the spacebar
I'll get a Quick Look preview of
my image.
There's documentation on how to
make your own classes Quick
Lookable, so check that out.
And we've come to the end.
For more information, you can go
to this page, we have several
related sessions that are all in
the past.
Check them out on video and, you
know, cats because we all want
to see them.
Thanks for being here.