Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
>> Good morning everyone.
My name is Elizabeth Reid
and I'm an engineer
on the iWork Team.
And I'm here today with one
my co-workers to talk to you
about how to share code
between iOS and OS X.
So, you know, what we're going
to do today is first talk
about what code it's
possible for us to share
and what we probably
don't want to share.
And then how we can share
more code or have it easier
to write shared code
using some frameworks.
And specifically
also give an example
of some shared rendering code.
And then we're going to
talk about file formats
and how you can optimize
and build those
for a better multiplatform
experience.
And finally, my co-worker
Chris is going to talk to you
about how to set up
your XCode projects
to compile cross-platform.
Last fall iWork had a release
where we took our
iOS applications
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
where we took our
iOS applications
on our modern code base
and we took that code base
and brought it over
to compile for OS X.
So now we have all of
our applications for iOS
and OS X compiling from
a single code base.
And that was a really
big deal for us.
We really wanted to have
a single location for all
of our features, to have
a better user experience
and better engineering
experience
so that everyone's happier.
And so we're going
to talk to you
about that specific transform
from iOS code to OS X.
But, the concepts and
examples we're going
to give you are meant to
be, you know, examples
and not specific directives
on what you should do.
And so the ideas and concepts
should apply if you're going
in the other direction, you're
taking an OS X application
and bringing it to iOS.
Or even if you're building
both at the same time.
These are just, you
know, principles rather
than an obvious iOS
only to OS X only.
And we have a lot of
different examples
because iWork is a really
big suite of applications.
We have well over a
million lines of code
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We have well over a
million lines of code
and over 10,000 unique classes.
And of these 10,000 unique
classes, over 75 percent
of them are shared
between iOS and OS X.
Now, note that this number
is not nearly 100 percent.
It's a large chunk of
code, a lot of the code,
but not all of the code.
And that was intentional
on our part.
We wanted to choose which
code we wanted to share
and which code is really
meant to be only run
on one platform or the other.
And that gives us a really good
common experience for our users
in our applications
while still targeting
and embracing the strengths of
each device we're running on.
And that was really,
you know, a helpful way
to build the best
applications we possibly could.
So where did we start?
We started with our
iOS applications.
We already had iWork running
on iPad and on iPhone.
And we had a team of
really, you know, excited
and passionate developers
who wanted to take this code
and bring it to our Mac platform
and have everything
running from one location.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and have everything
running from one location.
And our code was
set up something
like this, a rough estimate.
Hopefully you guys are
familiar with this pattern.
But just a review, the Model
View Controller design pattern
is where the model is the
data of your application.
It's the content.
And maybe you're sending it over
the wires, saving it on disk.
The view is what your user is
looking at or interacting with.
And you don't really
want those two ever
to talk directly to each other.
If your model changes, and you
don't want your view to change,
if they're tied directly
to each other, that's -
you have to change both
sides or vice versa.
And so instead, we have
controllers that we use
as a translator to negotiate
between the two of them
so that you don't need to
worry about having your model
and your view too
tightly intertwined.
So we look at each
section of this.
We started with a model.
And we want to figure out if
we could share our model code.
And for us that was part
of one of the major points
of this particular rewrite.
We really wanted our files
to be the same everywhere.
And most - and a
very important part
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And most - and a
very important part
of that is having our
model code that talks
to our files also be
the same everywhere.
And since our model code isn't
talking to the view code -
we have our controllers in
between - we should be able
to share our model code.
I'll talk a bit more later about
the actual content of the model.
But for now we're going to say
that the model code,
we're going to share.
We'll mean shared
in this context.
Next, we looked at
the view code.
Can we share our view code?
We want to share our view code.
We really want our applications
to have the same content
for our users', you
know files, when we open
and display them onscreen.
We want those to look
the same everywhere,
because our users carefully
crafted their documents
to look exactly how they want.
But, there's some problems
with sharing view code.
First of all, there's actual
interaction per platform.
On OS X, the way
that users interact
with your applications are
with a mouse and a keyboard.
They have hot keys.
You know, they're
using the cursor,
which is a very precise
single pointer.
Whereas on iOS, you
have Multi-Touch.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Whereas on iOS, you
have Multi-Touch.
And you have 10 very wonderful
but less-precise fingers.
The human interface
guideline says
on iOS you need your buttons to
be at least 44 by 44 points just
to make sure that your
users are able to tap them.
And that's, you know,
less precise
than your OS X interactions.
And so that's, you know, one
thing we're going to have
to take into account
with our view code.
Another thing we need
to consider is the fact
that the actual display size
between the different devices
is going to be different.
The kinds of content we
can show, the space we have
for controls, is going to
vary based on platform.
It's even more extreme
on iPhone versus Mac.
And so we want to
take advantage of all
of the space we have available
to us when it is available
and handle the fact
that sometimes
that space won't be
available on smaller devices.
And we're going to
have to craft our code
for each platform a little
bit in that view space.
Also, on iOS your user
is looking directly
at your content, your
application and nothing else.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
at your content, your
application and nothing else.
That's how iOS devices work.
And on OS X you can
have multiple windows,
multiple applications.
They're doing multiple
things at the same time.
And you really need to take that
into account that there's going
to be other things happening
on your OS X platforms.
And you need to build
your view accordingly.
You don't get to use all of
the room all of the time.
On a more technical note,
you literally can't compile
view code cross-platform.
If you take a class that
inherits from UIView and you try
to compile it on OS X, XCode
will look something like this
and get kind of mad at you.
And so there's that
technical hurdle as well.
Now some of you may be
looking at this saying ah,
I know how to handle this.
I can fix this problem.
And you might be thinking of
something that looks like this.
We call this shimming in iWork.
It's also known as
conditional compilation.
And it's where your have
your subclass inheriting
from either UIView on
iOS or NSView on OS X.
And that change has
made it compile time.
And so you write the same code.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And so you write the same code.
And it has a different super
class on each platform.
Now, you know, this will
compile, absolutely.
And it does work.
But there are some
problems with shimming.
Now, views have - you
know, UIView and NSView -
have many similarities.
They serve the same basic
purpose in your code.
They're there to handle
user interaction events
and put content onscreen.
But there are a number
of differences between
them as well.
They have different APIs, they
support different features.
And there's some
subtle differences
in their common behavior
as well.
And so if you build, for
example, Drag and Drop support
on your OS X application with
your view, and you go compile
that on iOS, those
APIs don't exist.
And your code won't compile.
And so if you shim, you're going
to break the build fairly often
because of the differences
in APIs between platforms.
Also, it's going to be very
hard to target your fix
to the platform that
you're trying to fix.
So if you a particular issue
or change you want to make only
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So if you a particular issue
or change you want to make only
on OS X, but your code is
compiled in both places,
you're now going to have
trouble getting that code
to only fix OS X and not iOS.
And so you could do more
conditional compilation,
absolutely.
You could have that
in your code.
That makes your code
really hard to read
and also really hard
to maintain.
And so over time, more and
more problems may arise
as you reach the edge
cases of your behavior.
Also, if you build a
view hierarchy on iOS
and it looks great on your iPad
or your iPhone, and you shim it
to bring it over to OS
X, and you get everything
that can compile compiling,
and you put on screen,
your UI is going to look
like it's built for iOS.
And remember how our buttons
are supposed to be larger
for the fingers to tap them than
you would use for your cursor?
If you build the same
view code in both places
that view code is going to look
like it was designed wherever
you built it in the first place
and will look a little
unnatural for the platform
where it wasn't intentionally
designed.
And you really want to have
the best experience possible
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And you really want to have
the best experience possible
and embrace the human interface
guidelines and UI styles
of the platform on
which you're running.
So in general, we think that
shimming is something, that,
it's not - you know, it's
helpful in select cases.
It's not definitely the wrong
thing to do all of the time.
You know, it has its moments.
But just be aware of some of
the problems that may arise
if you're using shimming
in your own code.
If nothing else, you're
pushing all of the complexity
of understanding what the
platform differences are
onto your clients in a way
that isn't necessarily obvious
from the beginning,
rather than keeping it all
in a single location
where you could handle all
of the details yourself and
let everybody else not worry
about it.
Also a note about Swift.
If you literally translate how
you would shim with Objective-C,
this will not compile in Swift.
There are ways to shim
your code in Swift.
But it gets more complicated
than your basic conditional
compilation
that we can use in Objective-C.
So just a side note about Swift.
So for now we're going to
say that our view code needs
to be platform specific
so that we don't run
into these problems.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
into these problems.
Next up is the controller code.
Can we share our controllers?
We found that it really depended
on the kind of controller
that we're dealing with.
Some things that are
called controllers,
like ViewControllers,
UIViewController
or an NSViewController,
are really tightly coupled
with the views themselves.
And like the views themselves,
they won't cross compile.
And so controllers that are
meant to be used for, you know,
platform-specific behaviors,
user interaction events,
hot keys, mouse handling,
Multi-Touch gestures,
all of those are very
platform-specific controllers.
We probably don't
want to share those
for the same reasons
we don't want
to share the views themselves.
However, a lot
of our controllers do
contain more shared logic -
they're talking to the
model more directly rather
than dealing with the
intricacies of the view itself.
And so, we would like to
share those controllers
if we possibly can,
because that's where a lot
of the core logic of
our applications live.
So we're going to say that
you share some controllers,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So we're going to say that
you share some controllers,
but not all.
Hopefully you're going to share
more controllers than not.
It really depends on
how your code is set up.
To give you an example
of what this might look
like within your controller
code, in iWork, we had a file.
It was called Canvas
View Controller.
It inherited from
UIViewController
and it had a lot of stuff in it.
It did a lot of different
things for us.
And we realized that, you
know, the split of the code
in that file was kind of
similar ratio here where some
of the code was absolutely
platform specific.
It was part of its job
as a UIViewController.
It was controlling
the view as specified.
But a lot of the stuff we had in
there was more shareable logic.
It was about, you
know, managing states
and handling some rendering
stuff and, you know,
scrolling and zooming.
Some of the things that we can
handle in a more shared way.
And so we really
wanted to keep that code
and use it on both platforms.
So to solve this problem
we split it in half.
And now we have the iOS Canvas
View Controller that's really
in charge of actual view
controller behavior rather
than view controller behavior
plus some other stuff.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
than view controller behavior
plus some other stuff.
And then we have a shared Canvas
Controller whose job it is is
to actually control our
shared Canvas Object,
which I'll talk more
about later.
And then we created a
counterpart on OS X,
the OS X Canvas View Controller,
to handle the platform-specific
view controller needs
on that side as well.
And here we have a very
clear separation of concerns.
So we know exactly what
each object's job is.
And you can put the code
in the right place based
on what its real purpose is
and how it should be used.
A real-life example of this,
you know, with actual pictures
and so forth, is rotation.
And in iWork there are a
few different ways you can
rotate objects.
And it also really depends
on the platform you're using.
On iOS you put two fingers down
and you rotate them relative
to each other, and the
object will rotate.
On OS X you hold down
the Command Hot Key,
you click on the
handle and you drag it,
and the object will rotate.
And those are really different
interaction paradigms.
It's really - you know, both
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
It's really - you know, both
of them are very much using the
platform-specific interactions
that you can't do on
the other platform.
But the underlying model changes
are, in fact, exactly the same.
It's just an angle that
we're applying to an object
and all the math that
goes along with it.
So we wanted to share as
much of that code as possible
because there are some, you
know, exciting math in there,
while keeping the
platform-specific
logic separate.
So we set our code up like this.
We have a shared rotation
controller that does all of the,
you know, the real math
and state management.
And it talks to our model
object when appropriate.
And we have a platform-specific
gesture recognizer on iOS
that handles the Multi-Touch and
translates that into an angle
that our rotation
controller can understand.
And we have a platform-specific
mouse and keyboard handler
on OS X that handles
both our hot key
and clicking interactions
to tell us that, you know,
to again translate
that into an angle
for our rotation controller.
So that's already
pretty helpful.
We have shared code here and we
have platform-specific logic.
But there's actually more
ways for us to rotate
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But there's actually more
ways for us to rotate
in our iWork applications.
On OS X we have inspectors.
And you can actually just
type an angle directly
into a text field, hit Enter
and it will rotate the object.
And so, we also have
a handler that calls
into the exact same rotation
code that you would call
if you were doing it with your
mouse, or if you were doing it
with your fingers on iOS.
And everything is exactly
the same except the thing
that translates the user
interaction into an angle
for our rotation controller.
Also new in OS X Yosemite we
have NSGestureRecognizer APIs.
And they handle a lot like
iOS UIGestureRecognizer.
And we could also theoretically
plug the same thing
into our rotation logic and have
yet a third way for our user
to interact and rotate
our objects.
So if you look at just the
bottom half of this diagram,
you know, sharing code
between platforms aside,
this is a really powerful
abstraction for us.
We have three completely
different ways
to rotate an object, but most
of our logic is still shared.
And we could also plug this
in for things like testing.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And we could also plug this
in for things like testing.
If we wanted to just
test our rotation logic,
we could add a fourth path,
add just a tester that says:
I would like you to
rotate it by 10 degrees.
And all the rest of
the code is the same.
So we're actually
testing our logic
and not just our interface.
We can also use this
sort of abstraction
for Quick Look or plug-ins.
There's a lot of different ways
you can apply this abstraction
in your own code.
So now we have shared
model code.
Not shared U code and some
controller shared and some not.
And we wanted to share
more code than that.
So we looked at the - at the
frameworks supplied by Apple.
And we noticed that most of them
are the same one both platforms.
They have the same APIs,
they have the same behaviors.
And so we could use
them in both places.
If you write some
core data code,
it's going to run the
same on both sides.
There are some frameworks
that, however,
that aren't the same
on both sides.
If you're using QTKit,
that won't compile on iOS.
It's just not supported.
We recommend you try
out AVFoundation.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And an important thing to
note is that AppKit and UIKit,
while they serve the same
purpose in your code,
are not the same framework.
They have many similar APIs.
You can use them in
many similar ways.
But you should treat
them as separate objects
in your own architecture
and handle them accordingly.
We looked at this
list and we thought
that we could use Core
Animation and Core Graphics
to have more shared
rendering code that none.
Right now we have
non-rendering code shared.
And we'd really like to have
some commented code there.
Core Animation is useful because
it has these things called
CA layers.
CA Layers are used
to put content
on the screen and animate them.
And they behave exactly
the same on both platforms.
And that's really
helpful for us.
Core Graphics is good for more
advanced graphics operations.
You can build content with Core
Graphics that you can't build
with Core Animation alone.
You can take that content and
put it into Core Animation Layer
to get it on screen
and animate it.
There are some caveats.
Core Graphics contexts
in UIViews
and NSViews are flipped relative
to each other in the y-axis.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and NSViews are flipped relative
to each other in the y-axis.
The origin for UIViews
is on the upper left.
And for NSViews is
on the lower left.
And, you know, that makes
our math more complicated.
But, you can fix this by
overriding isFlipped on NSView.
And that makes our origins
in the same location
in both places.
And this is really helpful
for us not to shim our views,
but instead to have
shared rendering code.
It doesn't need to worry
about that extra flip
while we're calculating
where things should
appear on the screen.
And so we use Core
Animation and Core Graphics
to build our layer tree, a layer
tree for our - the iWork Canvas.
The iWork Canvas
is this part here.
It's the actual user content
that they interact
with onscreen.
And the code that renders
that canvas is exactly the
same on both platforms.
We use Core Animation
and Core Graphics
to have the same rendering
code in both places
so we can make sure that
the user's content will look
the same.
Now, because Core Animation
and Core Graphics are
platform specific,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and Core Graphics are
platform specific,
or are not platform
specific, rather,
we're losing the
platform-specific support
that we could get using Views.
So, you know, on iOS, if you
have your content and, you know,
you have a chart that's in its
own view, you can put a gesture
on that view directly
to turn it -
to interact with
just that chart.
However, if that chart
is in just a layer,
you can't directly target
a gesture to a layer,
so you have to add an
extra layer of support
to handle the platform-specific
aspects
of that rendering system.
So there are some
downsides to this.
But we found it was very
helpful for us in iWork.
So now we have a little
bit of shared view code.
But that view code was
very carefully crafted
to have a stronger
user experience
and a more unified
rendering situation.
But there's one last step
that we need before
we can actually get
that shared rendering
logic to compile.
We render a lot of images.
And you have NSImage on an
OS X and UIImage on iOS.
And they're both
really fantastic
and really helpful objects
that do some really
powerful things for us.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that do some really
powerful things for us.
They help, you know, memory
management and caching.
They get information
out of your bundle.
And we don't really want to
reproduce that logic ourselves.
But we also don't want our
shared rendering code to have
to care which one
it's talking to.
For us, all we really wanted
from our images was the ability
to get a CG image out from
the image or to render
that image directly into
a Core Graphics context.
And because those
behaviors are pretty simple,
we built what we call
an Image Wrapper.
And Image Wrapper is a way for
the calling code to not care
about the platform-specific
issues
or the implementation details.
You define a simple API,
and you implement it twice,
one for each subclass
that's private internal
for a class cluster.
So we have a myImage
super class.
It's an abstract super class.
And a platform-specific
implementation
that talks either directly to an
NSImage or directly to a UIImage
and translates our APIs based
on how each of them work.
And this is really helpful
because we're not losing the
power of NSImage and UIImage.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
because we're not losing the
power of NSImage and UIImage.
All of their great features
we still get to use.
But our shared code
doesn't have to worry
about which one is
actually getting used
when it's calling the code.
Now note here that we're going
to have to translate each
of our API calls directly
ourselves in our wrapper layer.
And that means that, you know,
wrappers are really useful
for simpler objects like images.
But it's harder to wrap
things like view controllers,
which are very complicated and
powerful and behave a little,
you know, there's enough
different behaviors that trying
to translate them all yourself
would be rather difficult.
So we found that wrappers are
most useful for simple objects.
So now we have our
application compiling
and running on both platforms.
We have a shared
rendering system,
so our content should look
the same in both places.
And one of our designers
made a deck.
And they put this image in it.
And they opened it on their Mac.
It looks like this.
But then they opened
it on their iPad,
and the image looked funny.
The colors were off.
And since we really wanted
our content to look the same,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And since we really wanted
our content to look the same,
that was part of the point
of having a shared rendering
system, we, you know,
scratched our heads and
debugged for a while.
And we realized that the
problem was that the image
that we had used had
a CMYK color profile.
For those of you unfamiliar
with color models, CMYK is cyan,
magenta, yellow, black.
It's designed - best
used for printers.
Those are the standard
printer ink colors.
And so, computers don't,
you know, your pixels -
you have pixels on your
screen rather than ink.
And so a lot of our content on
computers is made with the RGB
or red, green, blue color model.
And there are other color
models out in the world.
And so, you know, there's a lot
of different ways you can
specify colors in the universe.
But iOS devices do the best
job of rendering sRGB content,
which is this particular
kind of RGB.
And so we found that, you know,
any image that wasn't
sRGB was probably going
to look a little bit different
when you open it
on an iOS device.
And we didn't want
that to happen.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And we didn't want
that to happen.
So what we did is we
took all of our content
that wasn't already sRGB
and we converted it to sRGB
when the user inserted
it into our documents.
And so that was - and that way
our content could look the same
on both platforms.
So this is, you know, if
you want to learn more
about color management
in general,
there's a session you can
see online, "Best Practices
for Color Management."
There's also a lab right after
this talk downstairs at 10:15.
So now we have our
images looking the same
on both platforms.
So now we can open
our documents.
And they look the same, for real
this time, on both platforms.
But we built some large
documents with lots of images.
We were testing out
this behavior.
And we noticed that it took
a while to open the documents
when they got big enough.
And we figured, you know,
OS X devices have a lot
of resources available to us.
We should be able to make
this a better user experience
and be faster.
So what are the different
resources we have available
to us?
Well, there's, first
of all, between iOS
and OS X there are
different chips
that are actually
running in your hardware.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that are actually
running in your hardware.
And that's going to, you know,
those will affect how
the hardware behaves.
The performance will
be slightly different.
You know, things are
going to be faster
on one platform or the other.
Also, the memory
bin, what's available
to you is different based
on the device upon
which you're running.
The total RAM you have
available is also going to vary.
And the exact media that's
supported will be different,
again, based on the chips.
And so this, you know,
it's a combination
of all these factors.
We could have slightly different
performance profiles depending
on both the platform
we're running on
and also the specific hardware.
And so we wanted to optimize,
you know, various parts
of our applications
per platform.
In the case of opening
the document slowly,
and we want to get quickly,
we made a lazily loaded model.
Different parts of our
documents, you know,
each slide in Keynote is
a self-contained unit.
It doesn't need to
reference things
on other slides immediately
when you open them.
And so if I have a 300-slide
deck, and I'm only looking
at the first 10 slides on
my screen, I don't need
to read the 300th
slide from disk.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to read the 300th
slide from disk.
So we're only going
to read the parts
of the model the user
is actually looking at
and what they actually
need to interact with.
And also we can load each
of those in parallel,
because again, they aren't
referencing each other.
And these things combined will
give us a faster experience
opening our documents because
we're reading less data
and we're doing more
of it at the same time.
And again, this was
an optimization for,
you know, multicore.
You know, the more
cores we have,
the more our parallel
reading would help us.
And so this was kind of
targeted for OS X specifically.
We also had other
optimizations on iOS.
But you know, this is just to
give you an example of the kinds
of things you're going to want
to do while building your
applications for each platform.
So now we have, you know, our
documents and we have a model
that we can load
lazily and in parallel.
What's actually in our model?
What's in our documents?
One thing to keep in mind is
that your user's not going
to update all of
your applications
at exactly the same time.
They're going to be
running different versions
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
They're going to be
running different versions
on different devices
at the same time.
And you're, unfortunately,
just - you know, this is a fact
of life and you'll
have to deal with this.
And so, you know, the most
fundamental first step
of this is you need to version
your data so that you know
for a fact what version of your
application actually built this
document so that if you change
how your model is written
to disk, you can handle
that appropriately.
So that's the first
important step.
However, you can do something
a little bit more interesting
than that.
Just instead of versioning
it purely
on the application version,
you can version it based
on the features in the document.
So, if I have a version
of Keynote
that has some great features
and kinds of things we write
to disk, and we update
our application,
and we add a chart feature
so that our chart
model will be different
but everything else is exactly
the same, if I have a document
in my new Keynote and I send it
to somebody who's using the
older version of Keynote
or I open it on my own device
that's running an older version
of Keynote, and my document
doesn't have a chart
in it, that should open.
Ideally, you know, if you're
not using a new feature,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Ideally, you know, if you're
not using a new feature,
you should still be able
to open your document.
And that way you have
- you're more likely
to have your users have
a positive experience
if they can open their
documents as much as possible.
One real life example of this
that you might be more
familiar with are XCode xibs.
You can actually save a xib.
There's two different formats.
And you can choose
the format based
on the features you
actually want to use,
and XCode will handle that.
Another thing to keep in mind
when you're building
your on-disk model
and also translating it to your
memory in Memory Model, is that,
you know, the way that you store
your application data in memory,
if you rearrange that,
that doesn't necessarily
mean you're going to have
to change your on
disk representation.
They don't need to look
exactly the same as long
as the overall data that you're
saving on disk is the same.
And you can translate
from one to the other.
And this way, even if
you update how you are
in fact handling things
in your application
to make your life easier,
try not to change the actual
document model unless you
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
try not to change the actual
document model unless you
absolutely have to.
Finally, if you're working with
a document-based application,
we recommend that
you check out the
"Creating Document-Based
Apps" session,
which is probably online
now because it's Friday.
So, just to summarize what
we've been talking about.
When you're trying to share
code between iOS and OS X,
first look at how the
code is actually used.
Is it used in a way
that should be shared?
Or is it handling something that
should be platform specific?
In which case, you don't
necessarily want to share it.
Also, really recognize
and embrace
that the platforms do have
differences in the way
that users should
use them and the way
that your UI should look
should be different based
on the platform upon
which you're running.
Also, consider using shared
frameworks when possible,
which will make your life
easier and make it so that a lot
of your code doesn't need to
be rewritten per platform.
And investigate the
design patterns
of Model View Controller
and Wrappers.
And again, keep in mind
that we're not saying
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And again, keep in mind
that we're not saying
that these examples that I'm
giving you are absolutely going
to apply for you in your
applications, and what works
for us won't necessarily
work for you.
But these are meant to be
more like universal concepts
that we're giving
you examples of,
rather than universal solutions.
And now I'm going to
hand this over to Chris.
And he's going to talk
to you about how to set
up your XCode projects.
Thank you.
[ Applause ]
>> Thanks Elizabeth.
So now that we've learned
patterns in technologies
that made sharing code easier
for us, I'd love to walk you
through how we made
this transition
from an XCode configuration
perspective.
So as Elizabeth was saying
earlier, we had three iOS apps
that we were very proud of.
And we wanted to bring
each back to the Mac.
I work as a large code base,
so all of the things I'm going
to share with you may
not impact you now.
And in the event that
they do not today,
you'll be all the more prepared
should your app ever evolve
in a direction similar to ours.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in a direction similar to ours.
And since the techniques I'll
be sharing with you transcend
into all of our apps, I'm going
to focus just on what we did
with Keynote, because the same
overarching principles apply
to both pages and
numbers as well.
The first small, yet pivotal
step to moving Keynote
to the Mac was to create a
new Mac target for the app.
So, what's a target?
A target contains instructions
for building a single product.
The most common type of target
is an application target,
which builds apps.
But there's also unit test
targets, among many others.
Targets also organize
everything that gets passed
into XCode Build, which
is XCode's build system.
And lastly, targets
are a part of projects.
It's pretty easy to
add the Mac target
to the current iOS projects.
All we need to do is click on
the project in the navigator
on the left hand side and click
on the Plus button
on the bottom.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
on the Plus button
on the bottom.
Then, we select the Cocoa
Application Template
from the OS X category.
We give it a witty name,
something profound.
In this case, Keynote for Mac.
And we press Finish.
And voila!
We've accomplished the
first fundamental step.
We now have two application
targets, one for the Keynote
for iOS, and one for the Mac.
So right now, it doesn't
do anything too fancy.
We press Build and Run, we'd see
something that looks like this.
And this is, of course, a far
cry from the Keynote experience.
And the reason for this is
because we haven't
added any functionality
to our Mac target yet.
So let's take a look at what
functionality there is available
to add.
Luckily for us, we have a
fair number of subsystems
that we'll want to
take advantage
of for our Mac application.
One example is the iWork Canvas,
which Elizabeth was
sharing with us earlier.
Being able to use the
same canvas in all
of our apps is incredibly
important to us
because we want all of our
documents to look the same
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
because we want all of our
documents to look the same
across all of the
devices that we support.
We also want to make
sure that our file format
and model is the
same for both the iOS
and OS X versions of our apps.
So, we would want to pull
in our common persistence
framework as well.
And we also want to bring over
all of our common utilities.
These utilities include
mechanisms for logging,
convenience methods,
common data types
and many other classes
whose functionalities span
across our entire suite.
These elements we've
separated into libraries.
So, what's a library?
Simply put, it's a
bundled collection of code.
More specifically, they
are targets in XCode,
but they are not executable.
They just exist as a way
for you to share code,
primarily across projects.
To that end, we found it
didn't make sense to break code
into libraries unless
it was going
to be shared by multiple
projects.
Otherwise, we could just add
the shared code to both the iOS
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Otherwise, we could just add
the shared code to both the iOS
and Mac targets for our app.
And it's important to note
that if you have a
single project app,
this may be all you need to do.
And that is absolutely fine.
Again, we feel that sharing
libraries makes sense as a means
to share code across projects
and not necessarily
inside projects.
And lastly, libraries can
be either static or dynamic.
So, what does that mean?
All static libraries are built
with a project whenever any
other targets reference them.
And, they're included as part
of the final app that you build.
Dynamic libraries can be
external to the project.
And this makes them
favorable for a few reasons.
First, once you build them, you
don't need to build them again
until you change them.
And second, they can be put in
a shared location and not rolled
into the final application
you build,
which makes your
application smaller.
A concrete example of a
dynamic library is a framework.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Simply put, they're
a packaged up
and pre-compiled
dynamic library.
Frameworks have just
been brought to iOS 8.
And if you'd like to
learn more about how
to make modern frameworks, I
encourage you to look at the
"Building Modern Frameworks"
talk video available
on the Apple Developer Website.
So we currently have a framework
encapsulating functionality
shared across all
iWork for iOS apps.
And let's see what
happens when we try
to extend our shared
app framework to work
with OS X as well as iOS.
So here's the iWork
shared XCode project.
And we're going to add a
new framework target just
like we did earlier, by pressing
the Plus button on the bottom.
We select Cocoa Framework.
We click Next.
Again, we give it a name.
We press Finish.
And again, we have a new target.
The first part of
the battle is won.
So let's roll up our sleeves,
break out the sledgehammer
and create a new
common group in XCode.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and create a new
common group in XCode.
So let's talk about what
sort of things we might want
to put in this common group.
So we currently have a class in
iOS that represents a document.
And it's platform specific
because it extends UIDocument.
UIDocument does not
exist in OS X.
So we'll need to
introduce a new Mac document
that extends something
else as a basis
for our document functionality.
Luckily for us, NSDocument does
everything we need to read,
update and save our documents.
That said, we'd like all callers
of our model to be as agnostic
as the platform as possible.
So we'll want to expose
a common interface
between both documents.
And this we can express
in a shared interface
called iWorkDocument.
This interface that can
declare common behavior shared
across both the iOS and the Mac.
So let's take a look at what
iWorkDocument might look like.
Here's the shared
iWorkDocument interface.
It declares methods
for retrieving,
saving and updating
objects in a document.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And then, we can create
a Mac NSDocument subclass
and declare conformance
to the shared interface
we just created.
And then, we implement
the required methods
as declared in the interface.
Now, let's dive into
the project itself.
Bear with me.
I know this is going
to be a little tedious,
which is actually
part of our point.
I'll show you how to later
make this easier to manage,
but we want to show you what's
happening under the hood.
So let's - so there's
one thing we need to do
to the iWork shared
project before we move
on to the Keynote project.
And it's very important
that we do so.
Let's click on the iWork
App Shared Mac target
in the middle sidebar
and double click
on the Public Headers
Folder Path option.
A popover appears.
Let's remove what's in there
already and give it a name.
This name is the
first part that goes
after our import declaration
and before the slash in classes
that import logic
from this framework.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that import logic
from this framework.
OK. Now we're ready
to go back to Keynote
and use this new
Mac document object.
So I've opened up my document
window controller and the header
for the Keynote for Mac project.
I add the import declaration.
I press Build.
And it fails.
What happened?
I thought I had just declared
the iWork Shared Header
Folder path.
Well, we still need to
tell the Mac target to look
in the place we just
said we were going
to deposit the headers.
So we navigate to the
Header Search Path section
of the Mac Build Settings.
We double-click on the item.
A popover appears.
We type $(CONFIGURATION BUILD
DIR) and then we hit Enter.
This tells XCode that when we're
building Keynote for the Mac,
that it should look in
the Headers directory
where things are built, which
in this case is the location
of the shared framework.
Then we dismiss the popover.
And now the Keynote Mac
target knows how to look
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And now the Keynote Mac
target knows how to look
for the shared Mac
library headers.
So, I start coding
my implementation
and then I try to
build it again.
And I get a bunch
of linker errors.
Yikes! So, what happened here is
that I didn't link the Mac
app target with the library.
Nor did I list the
library as a dependency.
So that's why it failed.
Let's fix that.
So I go back to the
Keynote for Mac Target
in the Keynote project.
I click on the Build Phases tab.
I expand the disclosure triangle
next to Target Dependencies.
I click the Plus button.
I select our library,
and I press Add.
Note that if you're not seeing
the library as an option,
you will need to drag in the
project owning the library
into your containing
app project.
Our shared library now
shows up as a dependency.
This means that before we build
the Keynote for Mac target,
XCode will make sure this
library is built first.
Next, we need to link the app
binary with the framework.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Next, we need to link the app
binary with the framework.
To do this, we click on the
disclosure triangle next
to the Link Binary
With Libraries section
and click on the plus.
We select our framework.
We press Add.
And then it shows up.
Then, when we try to
build and run, it works.
So now, Keynote for
Mac is building on top
of the shared Mac framework.
And it's not too hard to imagine
extending the same process
to other frameworks
and libraries used
by Keynote as well.
So combined with extending
our shared frameworks
to be Mac friendly, we also need
to create Mac-specific views
and view controllers
that use these frameworks
to produce the Keynote
experience on the Mac.
And with a bit of time,
elbow grease and hand waving,
a wonderful thing happens.
We have a fully functioning
Mac app.
So at a high level, one can now
imagine our projects looking
something like this.
We have XCode projects for
pages, numbers and Keynote.
And each project has
a Mac and iOS target.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And each project has
a Mac and iOS target.
These targets build on top
of app-specific libraries,
which in turn build on top
of the cross-platform Canvas,
Persistence and Shared
Core utilities frameworks.
So while this is great, we're
not quite out of the woods yet.
One big problem is that
our build settings are
somewhat disorganized.
Remember how we manually
set the header search path
on the Keynote Mac
target and the same thing
with the public header
search path?
So at present, each target
has its own build settings
associated with it.
While this works,
if ever I wanted
to change the build setting
somewhere, I would need
to change it for every
target in the project.
And I would need to
trust myself to make
that change everywhere
consistently.
How we fix this is with
XCode Config files.
And they look like this.
They simply contain
a title and a value.
Each line controls
something specific
about your build process.
And you can customize every
aspect of your build here.
Some common examples
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Some common examples
of properties you can set
are header search paths,
which dictate the
directories in the file system
that XCode should use for
finding public headers.
Compiler warnings or what
sorts of potential bugs
in the code you'd like
XCode to tell you about.
Architecture, which
defines the types
of CPUs your target can build.
SDKs, which define the
platform and version of the SDK
of the target you'd like to use.
And deployment targets, which
declare the minimum version
of OS X and iOS a user
needs to have in order
to build and run your app.
These are a few of the
examples of popular properties,
but many other remain.
And, for the full
list, you can look
at for configuring your
XCode configuration files,
please refer to the XCode Build
Settings Reference available
on Apple's Developer Website.
The real advantage of
XCode Config files is
that you can use inheritance.
In other words, you can create a
common XCode Config file for iOS
and Mac, and then have
that extend a common one.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and Mac, and then have
that extend a common one.
In a common file, you would
place build settings that apply
to your whole project.
And in a platform-specific
configuration files,
you would add directives
that are specific
to either iOS or the Mac.
Another strength of
XCode Config files is
that they can be
reused across projects.
In other words, you can write
an iOS Config file for numbers,
pages and Keynote
and then have all
of your iOS apps use
the same iOS Config.
So let's create an XCode
Config file for our Mac target
and implement it together.
To create an XCode Config
file, we select File New
from the XCode Main Menu.
And we select the Configuration
Settings File and press Next.
We give it a name,
in this case Mac.
And for starters,
let's associate it
with the Keynote Mac target.
We click Finish.
And now we have an XCode
Config file for the Mac.
But this Config file
does not know
about the common configuration
it is building on top of yet.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
about the common configuration
it is building on top of yet.
To accomplish this,
we simply add #include
to this child config
followed by the name
of the xcconfig file
we'd like to extend.
And of course, we later add
any Mac-specific settings
that we care about.
Once we've done that, we
can associate our target
with these configurations
using the project editor.
We'll still need to set this up
for each target in each project.
But after we set this
up, we may never need
to touch build settings
of each target ever again.
So, let's try it for
the Keynote project.
We can do this by
expanding the Configurations
disclosure triangle.
And then for every
configuration,
setting the appropriate
Config file.
And we can verify
that these settings
of our Config files
propagated to the targets
when we take a closer look
at their build settings.
And here is what's provided
by the Mac XCode Config file.
For example, we can see
here that we're building
against the latest OS X SDK
as opposed to the current SDK.
And we can also see that the
Config file had an impact
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And we can also see that the
Config file had an impact
on the supported platforms
and valid architectures
of the Mac target as well.
So now we have a sustainable,
highly customizable
build process
and a great native Keynote
experience running on the Mac.
And as such, our
work is done here.
So let's take a step back
and review what we accomplished
together this morning.
We made a target
for each platform
that we wanted to support.
Then we broke some
of the functionality
across multiple projects
and platforms
into reusable frameworks.
We told the app targets how to
use these shared frameworks.
And then we streamlined
our build configuration
across every one of our targets
using XCode Config files.
So, while there's
no silver bullet,
as Elizabeth mentioned earlier,
we found the design principles
and XCode Project Configuration
we shared with you this morning
to be incredibly helpful to us.
And we can only hope that you
find the ideas we've shared
with you as useful to
you as they were to us.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
with you as useful to
you as they were to us.
Also, we compiled a simple
project called Photo Memories,
which exercises some of
the cross-platform concepts
and XCode Configurations
mentioned in this talk.
This sample code is available
on Apple's Developer Website
and explains how to
implement some of the patterns
and practices that
we talked about today
in even deeper detail.
If you're interested
in learning more
about cross-platform
applications,
there is no shortage of other
sessions that take a deep dive
into some of the topics we
touched on this morning.
And if you're interested, all
the session presentation videos
and demo materials
will be available
on Apple's Developer Website.
And finally, if you
have any questions
about what you heard
today, please don't hesitate
to contact Jake Behrens
or Dave DeLong
or consult the developer
forums at devforums.apple.com.
And with that, we wish
you the best of luck
in your cross-platform
application adventures,
and enjoy the rest of your
time in San Francisco.
Thank you very much.
[ Applause ]