WWDC2019 Session 258

Transcript

[ Music ]
[ Applause ]
>> My name is Janum Trivedi, and
I'm an engineer on the UIKit
frameworks team.
First, let's talk about
architecting your app for
multiple windows.
On iOS 13, supporting multiple
windows is a fantastic way to
make your existing apps more
useful and allow your users to
be substantially more
productive.
We'll be going over three
primary topics today.
We'll get started by doing a
quick overview of the changes to
the application life cycle that
will allow multiple windows on
iOS 13 to be possible.
Then, we'll dive deeper into the
new UIScene delegate and we'll
talk about what kind of work you
should be doing there.
Finally, we'll go over some best
practices in ArchitectureKit so
you and your team can take
advantage of to make sure you
provide your users with a
consistent, seamless
multitasking experience.
Now, to get started, let's talk
about some of the roles and
responsibilities of the App
Delegate in iOS 12 and before.
The App Delegate had two primary
roles.
The first was to notify your
application of process level
events.
So, the system would notify your
App Delegate when your process
was launching or was about to
terminate.
But it also had the second role,
which was letting your
application know the state of
its UI.
So, via some methods like did
enter foreground and will resign
active, the system will let you
know your UI state.
And this is totally fine in iOS
12 and before, because
applications had one process and
also just one user interface
instance to match it.
So, today your App Delegate
probably looks a little bit
something like this, except
maybe not quite this short.
So, in your did finish launching
with options, you do a couple
things.
First you do some one-time
non-UI global setup, like
connecting to a database or
initializing your data
structures.
And immediately after that, you
set up your user interface.
And again, this is totally valid
in 12 and before, but iOS 13's
pattern is invalid.
Why? Because applications now
still just share one process but
may have multiple user interface
instances or scene sessions.
That means the responsibility of
the App Delegate needs to change
a bit.
It's still responsible for
process events and life cycle,
but it's no longer responsible
for anything related to your UI
lifecycle.
Instead, that'll all be handled
by your UIScene Delegate.
Well, what does that mean for
you?
Any UI setup or teardown work
that you used to do in your App
Delegate now needs to migrate to
the corresponding methods in
your Scene Delegate.
In fact, on iOS 13, if your
application adopts the new scene
lifecycle, UIKit will stop
calling the old App Delegate
methods that relate to UI state.
Instead, we'll call the new
Scene Delegate methods, and it's
pretty simple since there's a
1-to-1 mapping for most of
these.
But don't worry - if you want to
adopt multiple windows support
on iOS 13, that doesn't mean you
need to drop support for 12 and
before.
If you're back deploying, you
can simply keep both sets of
these methods and UIKit will
call the correct set at runtime.
Now, before we dive into the
exact delegate methods, there's
one more additional
responsibility that the App
Delegate gets.
And that is the system will now
notify your App Delegate when a
new Scene Session is being
created, or an existing Scene
Session is being discarded.
Let's make this lifecycle a
little bit more concrete.
Say I've been working on this
little blue app right here and I
want to launch it for the first
time.
Let's go through the call stack.
First, your App Delegate's going
to get the same did finish
launching with options call.
Again, it's still fine to do
your one-time non-UI setup here.
Then immediately after that, the
system's going to create a scene
session.
But before it creates the actual
UI Scene, it's going to ask our
application for UIScene
configuration.
This configuration specifies
what scene delegate, what story
board, and if you specified,
what scene subclass you want to
create the scene with.
It's worth noting that you can
define these scene
configurations either
dynamically in code or
preferably statically in your
info.plist.
This also gives you an
opportunity to select the right
configuration.
You may have a main scene
configuration, and you may have
an accessory scene.
So, you should look at the
options parameter that's
provided here, to use that as
the context to pick the right
scene configuration.
Once you've defined these, for
example in your info.plist, it's
really simple.
You just refer to it by name,
making sure you pass in the role
of the incoming sessions role.
Great.
Now our app is launched.
We have a scene session.
But we don't see any UI, and
that's where our scene delegates
did connect to scene session
comes in.
Let's look at what kind of work
we should do here.
This is where you set up your UI
window with the new designated
UI window initializer.
We'll notice that we pass in the
window scene provided.
But importantly, we need to also
check for any relevant user
activities or state restoration
activities to configure our
window with.
We'll talk about that more in a
second.
Yay, now we see our app.
So, what happens when one of our
users actually swipes up to go
back home?
Well, the old familiar will
resign active and did enter
background methods get called on
your scene delegate.
But, now there's something
interesting.
At some point in time after,
your scene may be disconnected.
Well, what does that mean?
Well, in order to reclaim
resources, the system may at
some point after your scene
enters the background, release
that scene from memory.
That also means your scene
delegate will be released from
memory and any window
hierarchies or view hierarchies
held by your scene delegate will
be released as well.
This gives you an opportunity to
deallocate and release any large
resources in memory that were
retained by somewhere else in
your application that related to
this scene.
But it's important to not use
this to actually delete any
user data or state permanently,
as the scene may reconnect and
return later.
But then let's talk about what
actually happens when our user
actually swipes up on a scene
session on switch and explicitly
wants to destroy it?
Well, the system will call our
App Delegates didDiscardSceneSessions.
This finally gives us an
opportunity to actually
permanently delegate any user
state or data associated with
the scene such as an unsaved
draft in a text editing app.
Now, it's also possible that one
of your users removed one or
more UIScenes from the switcher
by swiping up while your actual
app process was not running.
If your process was not running,
the system will keep track of
the discarded sessions and call
this shortly after your
application's next launch.
Now, let's talk about some
architecture patters that you
can consider integrating into
your apps.
We'll start by talking about
state restoration.
In iOS 13, state restoration is
no longer a nicety.
It is crucial for your
application to implement
scene-based state restoration.
Let's go over why that is.
Here's our app switcher.
I have a document app and I'm
planning a road trip and I have
four different sessions of
different documents open right
now.
But I'm really focusing on
packing list and agenda.
At some point, these other two
backgrounded road trip and
attendee scenes, have been
disconnected and released by the
system.
If I don't implement state
restoration here, when I go back
to the road trip, I'm not going
to return to the state that I
was previously in.
I'm not going to scene the
document that I was editing.
Instead, I'll just start over,
like it's a brand-new window,
and that's not a great user
experience.
Well, how can we resolve this?
iOS 13 has a brand-new
scene-based state restoration
API.
And it's super simple.
It works by not any more
encoding view hierarchies, but
instead just encoding the state
which will allow you to recreate
your window.
This is all based on NSUser
activity as well.
So, if your application
leverages powerful technologies
like spotlight search or
handoff, you can use these same
activities to encode the state
of your app.
It's also worth noting that in
iOS 13, the state restoration
archive that you give back to
the system will match the same
data protection class of the
rest of your application.
What does this look like in
code?
Well, in our scene delegate, we
implement state restoration
activity for scene and then I
call a method that finds the
most active relevant user
activity form in the current
window.
Then we return that.
After some time, when that scene
re-enters the foreground and
becomes connected, we check if
the session contains a state
restoration activity.
If it does, we use that
activity.
If not, we can create a
brand-new window without any
state.
This means that no matter what,
our users will never notice when
scenes get disconnected in the
background because they
shouldn't.
Finally, let's talk about one
more important issue that you
may run into when adopting
support for multiple windows.
And that is how to best keep
application scenes in sync.
Let me make this more concrete.
I've been working on a new chat
application right here, and as
we see, I just recently added
support for multiple windows on
iOS 13.
And I have a chat with my
friend, Giovanni, who will join
me on stage in a couple minutes,
and notice that we're viewing
the same conversation in two
different view controllers and
two different scenes at the same
time.
So, let's say I want to send
Giovanni a message and let him
know I'm ready for lunch.
Oh, only one of our scenes
updated.
So, why is that?
Well, it turns out that on iOS,
a lot of apps are structured in
this kind of way in which view
controllers receive an event,
maybe via button tap, me
pressing the send button.
And then the view controller
itself updates its own UI.
After that, our view controller
notifies our model or model
controller.
And this is mostly okay when we
just are talking about one user
interface instance.
But now if we introduce a second
view controller in a different
scene that's showing the same
data, at no point is this new
view controller being notified
to update itself with this new
data.
That's an issue.
Well, we can resolve this.
Architecturally, now if our view
controllers, upon receiving an
event, immediately and only
notify our model controller,
then we can have our model
controller actually notify any
relevant subscribers or view
controllers, telling them that
they should update with this new
data.
There are a number of ways that
we can do this.
We can use delegates,
notifications.
We can even use the fantastic
new Swift Combine framework
released this year.
But let's go over a lightweight
Swift example that you can
consider integrating into your
apps.
Here's the current method that
gets called when I press the
return button on sending a
message.
We create a message model
object.
My view controller updates its
own views.
And then we notify our model
controller to persist this.
And the first thing we need to
do is this view controller
should not be mutating its own
view state.
Instead, we're just going to get
rid of that code.
We'll add that back later in a
second.
Now, let's look at what that
model controller add method is
actually doing.
It's pretty simple.
All we're doing is persisting
that new message.
But we actually want the model
controller to now notify if any
other view controllers or
connected scenes that there has
been an update.
How are we going to send this
update down?
We want a structured way to
package this event such that
it's strongly typed and it's
easily debuggable and testable.
So, let's go ahead and actually
create a new type and we'll call
it update event.
It's a Swift enum with
associated values.
So, we'll add a new message
type.
This is the object that our
model controller is going to
create on receiving a new
message and will then send down
to any relevant view controllers
or scenes.
Because we want to post this,
we'll use NSNotification center
as the backing store for this.
So, we'll add this handy post
method that lets us in one line
create a new update event and
then post it to any subscribers.
The implementation is fairly
straightforward.
We just post a notification to
the new message notification
name channel.
But the trick here is that we're
including the update event
object itself in the
notifications object.
This will come in handy, as
we'll see in a second.
Now, when our model controller
is notified that a new message
was added, after we persist it,
we can just create this new
event and call post.
Then, if we look at how we have
to change our view controllers,
we observe this new event.
In this case, the new message
notification name.
And then we create a handler
method that we get the
notification from in the arguments.
And remember, when we pass the
update event as the
notifications object, we can now
pull that event right back out
from the notification.
Then, we can easily switch on
the kind of case of the event
and because we created an
associated enum, we can pull the
message out.
Now, we can update our user
interface here.
So, let's see what happens when
I send Giovanni that same
message after implementing this
new architecture.
Hey! All of our scenes update.
[ Applause ]
So, we've gone over a lot today.
We've gone over some of the
differences in the app versus
scene delegate and the
differences in responsibilities.
We've also gone over some of the
major scene delegate methods and
what kind of work you should do
there.
We also talked about why state
restoration is so crucial for
you to use on iOS 13 and how you
can leverage the new scene-based
API to do so.
Finally, we talked about some
high-level patterns for creating
a one-way data flow such that we
can keep all of our scenes in
sync while they share the same
data.
Thank you.
[ Applause ]