An exploration of Apple business news and technology. We talk about how businesses can use new technology to empower their business and employees, from Leo Dion, founder of BrightDigit.
Leo Dion (host): Welcome to
another episode of empower apps.
I'm your host Leo Dan today.
I'm joined by Brandon Williams.
Brandon, thank you so
much for coming on.
Brandon Williams (guest): Yeah.
Thanks for having me.
Leo Dion (host): I know a lot
of people are fan, fans of your
work point free with Steven.
But I'll let you go ahead and introduce
yourself before we get started.
So
Brandon Williams (guest): Yeah, sure.
Yeah, I'm Brandon.
I do run a site called Point
Free with Stephen Sellis.
It's a video series, educational
video series, kind of concentrating
some of the more complex and advanced
aspects of the Swift language and we
do a lot of open source work we'll
probably be talking about at least
one of the libraries today, but
but yeah that's the general thing.
Leo Dion (host): you've got a big
talk coming up at Swift Toronto
which is going to be awesome, it is
about controlling your dependencies,
not letting them control you.
And now initially when I saw that
I was like, Oh, it's going to be
like about swift package manager,
Cocoa pods or something like that.
But in fact you have a broader
and probably more correct
definition of what a dependency is.
Do you want to explain
exactly what that means?
Brandon Williams (guest): Sure.
Yeah, I think anything you probably
add into your SPM package of course,
you would call that dependency because
you're bringing in someone else's code.
But I think the yeah, the definition
could be a little bit broader.
And it's really just when you have
to touch API's and other people's
code that you can't control.
And so that would even
include Apple's code, right?
You don't ever add a dependency
in SPM to get access to core
location or anything like that.
That's, it's just kind of ambiently
there ready for you, but that still
is a dependency because whenever
you make a direct call to some core
location API, you're touching code
that you can't possibly control.
And that puts you in kind of a
compromised position when it comes to
running your code and certain situations
that you would want to, but it turns
out you can't because you just, you
don't have control over that code.
Leo Dion (host): And I think that's
the key point, is that this is pieces
of your code you have no control over.
So like you, you talk about core
location and how that works.
Anything networking,
obviously, anything database.
Brandon Williams (guest): Yeah
Leo Dion (host): even sleep
timers and that kind of
Brandon Williams (guest): timers,
user defaults it could be it's, and
I'll say even date generators and
UUID generators, all those things
that just can extract out like
new information out of the void.
Like when you ask for a new
date, you get the current
time, all that kind of stuff.
Yeah.
Needs, it can wreak
havoc in a code base.
Leo Dion (host): Yeah, I like, that was
the part that I found interesting is
when you talked about UUIDs and dates.
Because I don't think of those
as you can't control them.
But in a sense, you do get a wild
value every time you call them.
Brandon Williams (guest): Right.
Right.
Yeah.
Every time you do like capital UUID,
open, close, print, that initializer is
you just get a whole new value of it.
And you may not think it's.
that big of a deal to get
a new value out of it.
That is the point of it, but it can
cause trouble for that one in particular
can cause trouble with testing
because you may want to assert that
some model equals some other model.
But if you can't predict the ID that
was generated in the first model,
then you've got No way of comparing
those two as two like models together.
You can't have to compare them
field by field rather, and then
it's on you to remember when you
add a new field, you got to go add
a new assertion for that new field.
Whereas if you just asserted that
this model equals this model, it would
take all of care of all of it for you.
Leo Dion (host): Yeah, so let's
jump into testing, because I
think that's the big thing.
There's two things you mentioned
Xcode previews and testing is
where you run into these issues.
You want to go over some let's start
with the testing part since you
just mentioned that, what are the
particular issues that people will run
into when it comes to testing these
kind of uncontrolled dependencies?
Brandon Williams (guest): Yeah it
just so if you start sprinkling in
code like access to location managers,
network request into your code, you
it becomes very difficult, if not
impossible to unit test and unit
testing is a very particular way
of running your code in which you.
Kind of put it in a little sandboxed
isolated execution environment
where you need to be able to run
it without having spun up a view
controller or spun up a SwiftUI view.
It needs to be just able
to run all on its own.
And if you reach out to a
network request you have no
way of predicting what is the
data that's going to come back.
You know, if you reach out
to some API that loads like
Mastodon posts, you don't know.
what's going to come back.
And so you have no way of then
testing how that data flows
through your application.
And the point of controlling
the dependency so that you can
predict what is coming back.
It's not so that you can exercise what
the Mastodon API is doing where we just
have no choice but really to assume that
they're going to do the right thing.
And they'll send us back some posts.
What we want to test is.
When the post come back, how
does that flow through our logic?
Where does that data get
filtered and transformed and
put into everything else?
That's what we want to test.
And you can't do that unless you in
some way control that dependency.
Leo Dion (host): Yeah, I think
that's one of the biggest challenges
I feel like people get when they
start unit testing is they're like
how do I do this with the database?
How do I do this with the network?
It's no, like you're not testing
that the database works or
the networking call works.
You're testing your behavior
based on what you get back.
Brandon Williams (guest):
Yeah, it's tricky.
I think, and a lot of people
stumble on that when they're first
learning about testing and it
can, I think it can set you back.
If you look at it, you're like, wait.
I don't want to test,
you know, Apple's APIs.
I don't want to test core location.
So why would I do this?
And that's kind of not the, that's
the point is that to not test those
things and to get just to pretend that
their API worked the way you expect
and it fed data into your thing.
But it's very tricky.
Leo Dion (host): So before we
get into solutions what let's
talk about Xcode previews.
What are some things that Xcode previews
like, what are some challenges that
Xcode previews gives you different
from just what unit tests do.
Brandon Williams (guest): Yeah
there are so well, there's a couple
of aspects to this for the first
part, it's the more you compile in a
particular project, the bigger chance
you have of just breaking previews.
And so your first step to Improving
the stability previews is kind of
break up your project into lots
of little modules and that can
greatly improve the stability.
So once you've done that, you've got
a far more stable preview, but then
there are a lot of APIs out there that
will just completely break the preview.
And in a couple of ways first of all,
like things like a core location,
if you just indiscriminately.
Request permissions for location and
you use that check to kind of block
other things happening in your UI, what
will happen is you'll load the preview.
You'll never see that little iOS
alert saying, you know, do you
want to get permission because
previews do not support that.
And I don't know if that's.
considered a bug in previews, or if it's
just how previews are expected to work.
But you'll never get that little
permission box, and therefore you'll
just never be able to see in your
preview what happens afterwards.
And so that just completely ruins that
kind of iterative cycle on previews.
You're just not allowed to use it.
And then there's other kinds of things
that just will completely crash the
preview, because some APIs require
That you have a little info P list in
your application that describes why
it is you want to access that thing.
And that's like anytime you
access the microphone, the
contacts, even the core location.
But for some reason, core location
doesn't do this, but other ones do.
They will just simply crash your
app if you try to access the API
without that info P list being there.
And so then what happens is you've
got your preview and it just crashes.
You just, you literally can't run it.
And that's honestly just the beginning.
Like these dependencies a lot of them
will just make your previews very
flaky, not stable, make them inert and
functionless or just completely crash.
Leo Dion (host): So now we're going to
add it into some like terminology fun
here, but the way I've done it, and
it's kind of what you kind of get at.
And your talk is using mocks, but I
might be using the wrong term because
there's 50 different definitions
of mock stubs and blah, blah, blah.
But that's essentially what I
ended up doing is creating mocks.
And in some cases I'll create a
special mock for previews or a
special mock for unit tests, but
it does whatever you tell it to do
essentially where it's like, Oh here,
in this case, give me this lat long.
Just hard code it essentially, or,
you know, with networking, like
here, give, throw this error in it.
And that seems like basically
what it comes down to as a
solution for this kind of thing.
Is that correct?
Brandon Williams (guest): Yeah.
That's the entire solution is rather
than reaching out directly to like
core location or URL session to
load data, you put a little, a
lightweight interface in front of it.
And then in previews and
tasks, you can swap out.
A implementation to put into your
feature so that it just immediately
returns you some data and I agree.
I don't know whether that's a mock
or a stub or a fake or a spy or
whatever I like that zoo of terms.
It doesn't really matter, because all
that really matters that we have an
interface and at runtime we want to
swap it in with something that does
something a little bit different.
Like when you run in the
simulator on your device, you
want to use the real thing.
You want to use core location.
You want to access those APIs,
but in almost all other cases
you want to put in something.
Just simpler, something just, you say,
give me the current location, and it
just hands you back immediately some
lat long, and then your preview will
hum right along, and it'll be good.
Leo Dion (host): So in your talk,
you talk about protocols is typically
the way I've done it, but you
had some reservations about that.
You want to explain what those are?
Brandon Williams (guest): Yeah alright,
this is like a long, I mean, Steve and
I have been talking about this thing
for many years, and protocols are, yeah,
certainly the most popular, the de facto
way of putting interface in front of
something, but it's not the only way.
There is this slightly different
way in which you can use a struct
that has closure properties.
And I can substitute in for
a protocol for the most part.
And in particular, when it comes to
controlling dependencies protocols, you
typically just got two implementations.
You've got the live one, and then
you got kind of like a mock one.
And in such situations, the protocols
may be a little too high powered,
although Protocols have gotten a lot of
features recently that make this a lot
nicer you know, Rewind Early Swift the
Struct version was a lot more enticing.
Now protocols do satisfy a lot of
things that allow this to be easy.
But anyway, so if you use the Struct
interface, it allows you to do some fun
things, like you get the ability to kind
of hot swap out one single endpoint in
the dependency for new functionality.
So you could.
You could have a mostly live
dependency that accesses the true API
endpoints but you just swap one of
the endpoints to do something with
a mock or something, you know, more
custom and that can be really powerful
for really sculpting very specific
situations that you want to test.
Leo Dion (host): okay.
Interesting.
What were the things that you were
going to mention with protocols that
are new, that make it more enticing?
Brandon Williams (guest): Oh,
just the fact that we have more of
the existential machinery and the
protocol with associated types.
That stuff,
Leo Dion (host): primary
associated types, yada.
Okay.
Brandon Williams (guest): All that
stuff makes it so that it is far
less painful to deal with protocols.
Like prior to swift five, I don't
know if that was six or seven.
It was to me, it was an absolute
no brainer that you would use a
strike for these things afterwards.
I'm just like, whatever suits your
fancy, just, you know, go for it.
But I do think there are pros
to the stroke, the struck style.
But yeah, it's if you're used to the
protocols and go for the protocols.
Leo Dion (host): Yeah, I think I do
protocols probably like 80% of the time
and then the other 20% if I can throw
in a closure and just do it that way.
I'll do that as well.
If it's simple enough, it's just when
I end up having other dependencies
or when I have the protocol needs
to do a little bit more complicated
stuff, it's yeah, I'll probably end
up Do you need to do a protocol?
Brandon Williams (guest): the
more complicated the protocol,
the more it pushes towards like
protocol being the way to go.
But I also say that Apple's, some of
their recent APIs have even skewed
protocols in favor of just simple,
like basically bags of closures.
Like some of their collection view,
like data source stuff, historically
that would have all been protocols.
You would have made your object or
Leo Dion (host): Delegates,
probably delegates,
Brandon Williams (guest): yeah, Delica.
So all that stuff has like slowly
been going more towards Closure Base.
So I mean, it's, yeah there's
nothing all that strange about it,
but they're, yeah, you just got to
think about it a little bit more.
Leo Dion (host): right?
Right.
So one of the other things you
ran into with testing was the
problem with persistence, right?
Especially UI testing where you're
like running, like A series of
tests and something does set
some value, then you run the test
again, but that value had been set.
So now you messed stuff up.
Do you in those cases, I mean, for
me, what I typically do is my UI test
just ends up resetting it every time
it runs, maybe as part of setup or
maybe as part of the test itself.
What do you see as a solution
in that, in those cases?
Brandon Williams (guest):
Yeah, that's, the UI test is
particularly tricky because it
runs in a fully separate process.
So even from the XE test file,
you get no way to do stuff.
You can't clear out.
It's user defaults or
anything like that.
Whatever it does with it, you
know, it happens in a process.
That means you get to add the cleanup
code to your actual application project.
And the way you communicate
between the two is to set like a,
I think an environment variable
or something like that to kind of
communicate to your process that,
all right, I'm running in a UI test.
And then you would set up your
dependencies to use like a fake
file system, something that doesn't
write to the desk, but instead
has a little dictionaries mapping.
URLs to data blobs and then and
the application was like, Hey,
load me some data from this URL.
It'll just give you this data blob.
It won't even touch the
file system or anything.
And so that's the way
you typically do it.
Leo Dion (host): I was going to ask,
going back to the talk about protocols
and you're mentioning file system.
Do you end up having a protocol for
pretty much everything that needs to be?
Like mocked essentially.
So Oh, you are, you never use
URL session, use the protocol
and the protocols implementation
might have URL session.
Is that how you do it?
Brandon Williams (guest): Yeah,
essentially you need an interface in
front of each of your dependencies.
So one, whether it's struct or
protocol, but yeah, interface in
front of your API client, interface in
front of your file system, interface
in front of user defaults database,
core location, all those things.
Leo Dion (host): Do you were you
ever an Objective-C developer?
Brandon Williams (guest): Oh yeah.
Leo Dion (host): Do you miss
swizzling and just being
able to change the classes?
I don't, I'm not that kind of developer
but I can, I see a lot of older, like
Objective-C folks who are just like,
man, I hate how like everything is.
Strong typed and so and that's
like I okay now I see a little bit
of the benefit with Objective-C,
Brandon Williams (guest): Yeah, I
don't really miss it just because
I also remember the terrible
things that when it works great.
But like it injects so much uncertainty
into the rest of your application.
That's really hard to feel comfortable.
And I think other people.
Maybe people are just better
at Objective-C than me.
They just had a higher threshold
for understanding that complex
system all in their head at once,
and I just, I struggled with it.
Leo Dion (host): Yeah.
Yeah.
Yeah.
Brandon Williams (guest): but I think
a lot of this pain around writing these
dependencies, these little interfaces,
a lot of that pain could probably
be mitigated with macros and stuff
Leo Dion (host): Oh Yeah, right.
I figured you're gonna say that.
Brandon Williams (guest): yeah,
so I think we're at least swift as
getting to the point where you will
be able to keep the strong types,
but you'll also have the tools
necessary to put the interfaces in
front of them in a lightweight way.
Leo Dion (host): Yeah.
Yeah Mac I was gonna say macros
is pretty much where that head
that tells you where it's going
was there anything specific you
wanted to mention on testing or
protocols or mocking before I keep?
Keep going further.
Brandon Williams (guest):
yeah all right.
I think one thing I'd like to mention
and is that testing, I just don't think
it's super popular in the iOS community.
I think, if you, I also did a lot
of Ruby back in the day and that
testing and that community is
gigantic and JavaScript's got it.
So and I just I think it's
a little bit unfortunate.
I would kind of encourage people
to just dig in a little bit deep
to see what testing really means.
And this idea of being able to run
your feature in this altered like
execution context is extremely powerful.
All the more you're able to do that.
All it means is your feature is Super
isolatable, super decoupled and can
just be understood all on its own.
And that is incredibly powerful.
And you can only write unit tests
if your feature satisfies that.
And so regardless, I know it's a
lot of people have got like really
hot takes and unpopular opinions on
all these things about testing, I
just, I feel like hopefully just look
through all that fog and kind of.
See what testing can do, because
I think it's incredibly important.
And once you're convinced of that,
I think, yeah, the dependency
stuff is falls out naturally.
You got to do the
dependency controlling.
Leo Dion (host): Yeah.
I agree a hundred percent.
I feel like once you start doing
testing, it's then it's Oh, okay.
Now modularization
makes a lot more sense.
Why do you think, like, why do you think
the community is so testing averse?
Do you think that's a Apple's
fault or tooling fault?
Or is it like, I don't know what
Android is like, but I can imagine it
might be similar with Android as well.
Like just.
Brandon Williams (guest): Java's got
a big testing ethos and history too.
So they do quite a bit of testing also.
I really can't give like a universal
answer why I think it is by some
things that just come to mind are like.
Basically, none of Apple's
sample code has tests.
It's, none of it.
It doesn't matter if they're Fruta, Food
Truck, Scrumding, or all those things.
None of them have tests.
Now, I know Apple does do
testing internally, and I
know it varies team by team.
I know some teams take
it extremely seriously.
But the sample code, I think Apple's
approach with sample code is they
like to build Fun applications in
the absolute simplest way possible so
that anyone can kind of just roll in
and start messing with things that I
think that's their goal with it because
Leo Dion (host): Yeah.
Yeah.
Yeah.
Brandon Williams (guest): They're
making sample code for millions of
people, but that doesn't necessarily
mean you would go and build your
next company, the infrastructure,
you'd build it in exactly the same
way that Apple does their code.
You know, it's a learning tool.
Leo Dion (host): Yeah.
It's a learning tool and it's also
showing off of this particular API
and to, to them, I'm not agreeing
with them, but to them it's oh unit
tests would just distract away from
the fact that we want to show off.
Brandon Williams (guest): Yeah.
And I think it's, I think it's
under, I think it's reasonable.
I, you know, I certainly wouldn't
agree that, you know, people should be
building their applications in the exact
way that the Fruita truck was built,
but I absolutely understand why they
wouldn't add tests and stuff like that.
They really just want to show here are
all of our fancy new APIs this year.
Here's how to use them.
But yeah, I just don't
think we can look at.
Apple is being like kind of the
pinnacle, their sample goes the pinnacle
of how you should build these things.
Leo Dion (host): So you mentioned
I wouldn't want to build a
whole company app around Fruita.
And that kind of brings us to like
scaling and speed because in the
real world we all work in teams
and have to deal with other people.
And we have to deal with speed issues
with, as far as like you said, if you
don't do this, you have to run this
crap in the simulator every single
time, or you can't even run it in the
simulator, you have to run it on a
device, and that just slows things down.
Yeah, you want to kind of expand
on that, I guess, and why those
are big issues with scaling.
Brandon Williams (guest): Yeah, I think
what, just like with modularization can
help kind of paralyze build times and
speed up compiled times, dependencies
can improve developer experience and
iterative cycle on things because
there are a lot of APIs out there
that we already talked about, yeah,
that don't work in previews, but then
there's a lot of APIs out there that
don't even work in the simulator.
If you're making something that
Leo Dion (host): Core Motion.
Brandon Williams (guest): yeah,
core motion, if you're using
something that uses the gyroscope.
That doesn't work in
the simulator at all.
And so you actually have
to run on the device.
Now you may be thinking of
course I have to run the device.
It's a gyroscope.
I want to be able to move my phone
around but that's not all testing you do
sometimes what you want to do is just.
A simulate that the device is being
thrown around all over the place, and
maybe you've got a little graph that's
showing something, like you don't have
to take your device and do this, instead
you could provide a dependency where
Core Motion is pretending like it's
doing that, you can have the little
gyroscope meters going crazy all over
the place, and you know, that would
massively speed up your developer,
the development cycle, you could even
run that in a preview, you wouldn't
even have to run it in a simulator.
And these kinds of benefits just
really start to compound and explode.
There, there is so much that we
do as developers that is just kind
of working around the quirks of
our code base that we've learned.
We've just learned to do it.
It's just, we've been in the mud for
so long that we know that we don't
want to ever clean this project
because it takes so long to rebuild.
We know that we can't ever run this
project, this feature in isolation.
So I have my little flow of.
Open up the simulator and I tap
tap and I get to the feature
and I can finally test it.
All that stuff starts to kind of peel
away when you can just control your
dependencies and allow yourself to start
up your app in the exact state you want.
You don't have to be susceptible
to all of that dependency
corrupt and everything.
You should control all that.
Leo Dion (host): Yeah.
Yeah, I'm sorry.
I was like thinking about Apple
trying to unit test their like crash
detection and how they possibly do that.
Do they like throw the phone
against the wall or what do they do?
Because, yeah, I would hope
that they have some mocking
system for something like that.
Because, yeah, like you said, there's
all these little quirks that you run
into where it's you need the flexibility
of putting something in to replace it.
Brandon Williams (guest): That brings
up, you bring up an interesting point
also is that there's also a bit of
a spectrum with this testing, too,
because you've got the unit tests
at one end where you mock things
because you just want to say, I
assume the outside world is doing
what I expect, but I want, I just want
to test my little nugget of logic.
And then at the other end, you've got
integration testing where you start
to pull in more of the outside world.
And that's extremely important.
It's, I'm not saying that one side
is better than the other there.
You absolutely should be testing along
multiple points of this spectrum.
And like this crash testing they, I
assume they probably do actually have
some little laboratory setup, where
they literally throw phones around.
And that's great, because, you
know, they wanna know, like,
when, at the end of the day,
this thing is working that way.
But then that other thing they
want to test is just that an
alert pops up with this messaging.
I don't think you need to
throw the phone across the
room just to test an alert.
Leo Dion (host): right.
Brandon Williams (guest): would
instead, you would just, you
know, make it fake like it did a
crash and then you see the alert.
So there's like a spectrum
there and you got to test at
every level of the spectrum.
Leo Dion (host): Do you think
one side of the spectrum is
more important than the other?
Brandon Williams (guest): I do not.
Leo Dion (host): Okay.
Brandon Williams (guest): I think
you really need a full around overall
you know, testing on all sides of it.
You really do.
Leo Dion (host): What was I gonna ask?
Brandon Williams (guest): I will
say that certainly the integration
side is the hardest side to test.
And so typically you've got fewer
tests over there because then you
can't really make absolute assertions.
You can't say, oh, I made this network
request and I got this exact thing.
Instead, you can just say, oh, I made
this network request, it fed into my
system and, you know, I can assert
that at least something happened,
but I can't assert that exactly what
happened because you can't predict it.
But.
Yeah, integration testing is
far harder than unit testing.
Leo Dion (host): I think now we can
jump into the muck that is how do
we deal with all these dependencies?
So the way I watch your try swift talk
or new york swift talk, excuse me and
Brandon Williams (guest): right?
Leo Dion (host): No,
you're It was your talk.
Yeah.
In New York.
Brandon Williams (guest):
the one from in April
Leo Dion (host): Yeah.
Sorry.
So I watched that and I was like
my solution is supply default
implementations that are the real thing.
And then in your previews and in your
tests, you're just customize them.
Right.
That's the way I do it.
But you don't like that.
And I liked it.
You had a really good quote that
I say defaults are ergonomic,
but not safe and requirements
are safe, but not ergonomic.
Do you want to explain that?
I guess.
Brandon Williams (guest): Yeah.
So they so yeah, the easiest way to
provide dependencies to your various
features is you have an initial you
hold the dependency as a property and
you know, whatever your thing is a view
controller or observable object and
you provide an initializer that has
a default, which is the live thing.
So the thing that.
makes network request or the thing
that actually accesses core location.
And that way, if you construct
that feature and don't provide
anything, it gets the live dependency
and it all seems really good.
And then over in previews, you
get the opportunity to then.
provide one of those mock things.
All right.
So that can be a really good
way to get your feet wet and
start getting things going.
And it's very ergonomic because it means
you could have some deep leaf feature.
You could be drilled down
multiple levels into your
application and you could be
like, Oh wait, this feature now.
A network client.
So I'm just going to provide
it, provide that default.
Everything compiles.
It's all good.
So that's super ergonomic.
You get to add dependencies
with no trouble whatsoever.
However I call that not being safe
because what it means is say some
other feature up the hierarchy
also wanted a network client.
All right.
If it doesn't pass its network
client down to every feature
along the way so that leaf.
And then you run into the situation
where you could be running a
preview of that parent feature.
The parent feature is using the
mock network client, but that leaf
feature is using the live one.
Because no one passed it all the
way down to the leaf feature.
And so that's why it's unsafe.
That you will accidentally use live
dependencies when you don't expect it.
Unless you're strict with yourself
to pass them explicitly along.
Leo Dion (host): That's what I
mean, that's kind of what I do.
And I would just pass them along.
I would still supply the default, but
I would pass it along, but I understand
where you're coming from completely.
Brandon Williams (guest): Gives
the ergonomics, but it takes away
the safety because then it's on
you to remember to pass along
because the compiler can't help you.
If you don't pass it, it's you
got a default, no big deal.
So that's where the safety bar comes in.
Leo Dion (host): Macro save us, please.
Yeah.
I mean, essentially what you
get I like that solution.
And part of that.
I don't want to have to add another
dependency, which is kind of what
you get at is having to add some
sort of dependency injection library.
And I'm actually in my previous
life we used when I was a.
net developer C sharp, we did
we used what was it was Castle
Windsor, I believe was the
dependency injection tool.
And I thought it was awesome.
Like it was amazing how it worked.
And I wish.
And I think maybe macros actually
might help with this as far as
dependency injection is concerned,
but basically there's a plethora of
library and of course you at point
three, you have one as well that
kind of try to solve this problem.
I don't, I know what dependency
injection is, but I'll let you go ahead
and explain that and then what these
tools actually do and how they're.
that kind of fit the role that
you think is better than the the
way we suggested with defaults.
Brandon Williams (guest): Sure.
Yeah.
All right.
So if you go down the route of finally
controlling your dependencies, and you
provide initializers to pass them in
explicitly, and you find that to be non
ergonomic, so then you throw in some
defaults, and then you find that to
be not safe, The only point of adding
a library to this entire system is to
find a balance between something that is
pretty ergonomic and also pretty safe.
You'll never have the fullness of
ergonomics and safety at the same time.
There
Leo Dion (host): It's a compromise.
Brandon Williams (guest): Yeah
but initializers that don't
have defaults are extremely
safe, not ergonomic at all.
And then initializers with defaults are
extremely ergonomic, not safe at all.
So you're trying to find
somewhere in between those two.
And that's the only purpose of a
dependency injection framework is
allow you to provide The network client
and the core location client to the
feature that wants it in a way that's
slightly ergonomic and slightly safe.
And where you draw the line
there is like up to the library.
And so we've got our own library.
There's probably a dozen or more
other libraries in the Iowa community.
And we, yeah, we try to strike our
balance between those two things.
Leo Dion (host): The idea being is
you can say you know, use this type
and then you tell the dependency
injection, use this type essentially.
And then basically a singleton, you
kind of resolve it and then it gives
you the type that you had already set.
Is that right?
Brandon Williams (guest): Yeah,
essentially you'll have some kind
of way of annotating in your feature
like a view controller observable
object saying this object wants
a dependency of this type and it
will be the job of the dependency
injection library to figure out where
does it get that thing concurrency.
Pretty much the only way to do this
would have been you do have this
global Singleton of dependencies and
you get to pick from it and provide
it to to features, but then there
would be additional Functionality.
Maybe you can scope that global
singleton to something smaller so that
this set of features gets a little
bit slightly different dependencies.
There's there's other things you
can do and there's something called
containers and stuff like that you
can really dive into if you want.
But post Swift Concurrency, there's
now something called Task Locals.
And Task Locals allow you to
have something that looks...
global, but it's actually quite safe.
It can be thread safe or it is
thread safe and it plays nicely
with structured concurrency.
And so that's the tool we use
to build our dependency library.
All of dependencies are
basically held in a task local.
Leo Dion (host): I mean to me like the
drawback with that is, For one thing,
do you ever see Apple coming in or?
I'm just saying here's a
dependency injection way to do
things using macros or whatever.
Do you think that could ever happen?
Or do you think that we're pretty
much going to have to bring in
third party libraries to do this?
Brandon Williams (guest): I think
it could theoretically happen.
It would be surprising only for
the fact that Apple has never once
shown any interest whatsoever in The
idea of controlling dependencies.
They do not even build APIs that are.
control friendly.
Like, all right, for example, the
standard library has some, like,
all right so when Swift concurrency
first came out, we had task.
sleep, and that was a way to
sleep an async context for some
amount of time, but that is a
completely uncontrolled dependency.
If you sprinkle that into your
code and then try to test that
code, Your test will just literally
have to wait for time to pass.
So if you needed to wait 10 seconds
before something happened in your
feature, like confetti or something,
you would just, your test would
have to just wait 10 seconds.
So then in the next, like in a update
to Swift concurrency, they released the
clock protocol, which is an abstraction
and interface in front of sleeping.
And then that allows you
to substitute in clocks.
You get to use a continuous
clock when run on a device,
but you could use an immediate
clock or a test clock and test.
So that is one of the only examples
I can think of Apple providing
interfaces that actually make testing
easier, like all their other APIs
core location, core, locate, motion,
context, framework, all of those,
you just get types, you don't get
interfaces in front of those types.
Leo Dion (host): Right.
Right.
Brandon Williams (guest):
it would be surprising to me
if Apple came out with some.
You know, wonderful way of
doing dependency management.
Now, however, I'll say SwiftUI is.
From its very foundation,
a gigantic dependency
management system, essentially.
Like environment variables
and environment objects.
That is all about how do you
take something and push it
deep into a view hierarchy.
So we've got the tool at the view
layer but I think that was probably
out of just pure necessity of wanting
the idea of if you set a foreground
color on this view, the fact that
it needs to be able to trickle deep
into the view hierarchy, I think
That, that drove that nec necessity.
It wasn't that they were like,
oh, we've got dependency clients.
We need to control, therefore
we need the environment value.
I don't think that was their line of
Leo Dion (host): Right.
Right.
Brandon Williams (guest): and so
what our dependency library, what we
take inspiration from the environment
values and we try to give a tool
that looks like environment values,
but you can use it in observable
objects and stuff like that.
Leo Dion (host): I was wondering if that
was like a limitation of Objective-C and
they're like, yeah, we're not going to
do because we can do a bunch of other
stuff we had talked about previously.
But then I'm like, yeah, all
these new Swift APIs are all.
said not protocols or interfaces.
They're just types like Swift data.
It's all strongly typed So yeah, okay.
Yeah.
Yeah
Brandon Williams (guest): Yeah I
can definitely buy the argument
that Apple didn't really ever see
a need for dependency management
control and stuff like that.
And Objective-C day, because
everything was so loosey goosey with
message dispatch and stuff like that.
I just, I don't know what
their plan is for swift.
I don't know how people
within, you know, app like the
people building the weather
Leo Dion (host): Have you ever
looked at any of the open source
stuff and seen how they do it?
Like
Brandon Williams (guest):
what open stuff do they
Leo Dion (host): I don't know like
foundation or collections or algorithms
Brandon Williams (guest): those are
like foundational libraries that
don't benefit from this kind of thing.
It
Leo Dion (host): Yeah, right,
Brandon Williams (guest): If they
ever open source the weather app,
I'd be very curious how they deal
with things in the weather app.
You know, something like that.
Leo Dion (host): right, right.
Yeah, I think we
covered most everything.
I do, was there anything
else you want to talk about
before we jump into WW at all?
Brandon Williams (guest): No.
I mean, maybe something
will come up, but
Leo Dion (host): Yeah.
Brandon Williams (guest):
to mind right now.
Leo Dion (host): So I was going
to ask more focused on WWDC
has any of let's start with the
Swift UI and observation stuff.
Has that changed any of your
views as far as how dependencies
should be managed or is it pretty
much, Oh yeah, it's all the same.
Cause there was a lot of stuff, you
know, with like property wrappers
that essentially are kind of like.
Not needed anymore
because of observation.
Has that changed any of your views
on dependency management when
it comes to especially Swift UI?
Brandon Williams (guest): It
has not changed any of the core
views of dependencies, but the
observation tracking stuff does
open up a new possibility, new
powers we could possibly give a
dependency library like our library
or anyone can add to the library.
So one of the things that we do
with our dependency library is that.
If you start using it in your feature
and then you write a unit test for
that feature and you don't override
the dependency in the test, you
get a test failure the moment your
feature even accesses the dependency.
So if you got like a
method that says the.
You know, load data button
tapped and inside there, you
go into your network client.
You're like, oh, load data that
will trigger a test failure because
the opinion of our library is that
if you accidentally use a live
dependency in a test, that is most
likely not the thing you want to do.
And so we complain really loudly
saying you're using a live dependency.
You probably don't mean this.
If you do mean this, you can be
explicitly say, I do mean this, but
Leo Dion (host): Right.
Right.
Brandon Williams (guest): right?
And so then there's so we force you
to override that dependency and we
have a tool that allows you to write
your test where you get to override
the dependency, but there's a flip
side to that where if you then you
construct your model in the test and
you override all its dependencies,
but say you override too many
dependencies, there is the idea
that maybe that should be a failure.
If you override dependency that
wasn't actually used, yeah.
Maybe that should be a failure
because then you get to trim
down the dependencies you
override to the bare minimum.
And that really allows you to prove that
you know how this feature is working.
And that kind of, being able to have
that insight of what features are being,
what dependency endpoints are being
used and which ones are not being used.
I think all the observation
tracking will help with that.
And that's a completely non
SwiftUI application of that tool.
Just the idea of the observation
in general I think will be.
Really, and it's just going to allow us
to build some very interesting tools.
But the idea of controlling
dependencies, observation alone,
doesn't really change that story.
Leo Dion (host): Right, right.
How about as far as like using macros?
Brandon Williams (guest): Macros
just will make, finally, controlling
your dependencies more ergonomic.
It may be using our library more
ergonomic, but, yeah, it doesn't
change the reason to do it or
Leo Dion (host): Right, right.
But now you have syntactic
sugar to spread around to
make it a lot easier to do.
Yeah.
Brandon Williams (guest): The, for
example, like registering a dependency
within the library so that you
can start using in your features.
It is a multi step process, just like
with environment values, you have to
create this like little environment.
For SwiftUI environment values, you
create an environment key, you conform
it to a protocol, you extend environment
values, you add a computed property.
Leo Dion (host): Right, right.
Brandon Williams (guest): all of
that could be macrofied and, you
know, a one stop kind of thing,
Leo Dion (host): Have you jumped
into creating your own macros yet?
Brandon Williams (guest): Yeah, we've
experimented with it quite a bit.
Mostly for our KSPAS library
and composable architecture
and stuff like that.
Yeah.
Leo Dion (host): Pretty easy,
comfortable getting into swim
syntax and stuff, or what?
Brandon Williams (guest):
Swift Syntax is a beast.
It's it's
Leo Dion (host): know it is.
Brandon Williams (guest): yeah.
And you really need autocomplete to
help you out every step of the way.
Cause that is, yeah,
there's so much there.
There's like this AST, Swift Syntax
Explorer website that allows you just to
paste in some Swift code and it prints
out the Swift syntax code alongside it.
So you can kind of just see what every
little thing corresponds to and that
Leo Dion (host): Yeah.
Yeah.
Brandon Williams (guest):
what the heck is going on.
But yeah, it's Swift Syntax is a beast.
Leo Dion (host): I'm glad you said that.
Not chat GPT.
That's good.
Brandon Williams (guest): Yeah.
Leo Dion (host): And then last but not
least with data, where do you see that
fits in as far as dependency management
is concerned and mocking that?
I mean, I guess you've already
had stuff with core data, which
of course is a dependency as well.
That's certainly a wild card.
Is it kind of the same
way with Swift data?
Brandon Williams (guest): So what?
Yeah, it's basically the same story.
I think so.
So it's really just to enter like
a nicer API on top of core data
Leo Dion (host): Oh yeah, exactly.
Yeah.
Yeah.
Brandon Williams (guest): I
think that story is the same.
But what?
Sorry.
So one thing that's interesting
about core data, and I think I
misspoke a little earlier when
I was like, I'm trying to think
of examples where Apple is made.
APIs that are control friendly
or testable friendly core data
actually does have a little
bit of that because it has the
concept of the in memory store.
You know, usually when you're running
in the simulator on device, you're
Leo Dion (host): it writes
it to a SQLite file.
Yeah.
Brandon Williams (guest): and
there is the option where you get
to say, all right I want to load
up my application and I don't want
to use that sequel light file.
I just want to say I want
it all to be in memory.
That way, when the application is
killed, no data was persisted and that
can be incredibly helpful for you.
I test and unit tests
and stuff like that.
So that's a really good tool, but
That's the same story, regardless
of Swift data or core data that's
just kind of been carried over.
So Swift data, I, to me, I don't
know of anything special about
it that changes dependencies.
I mean, the most special
thing about it is just.
of how it plays nicely with SwiftUI,
like when, because you know,
historically, if you threw a reference
type into a SwiftUI view just in the
most naive way, none of its changes
would cause updates to the view.
And now we've got the machinery that
allows you to throw in a reference type,
and you make a mutation to something,
and it does update the SwiftUI view.
So that is big, and I think
that will be highly applicable.
Outside of Swift data
and observable objects.
I think you will be able to you could
have the idea of a, at one of our at
dependency property wrappers could
potentially hook into that so that
you could actually use it in the view.
Whereas typically right now you use it
in the observable object or the view
controller, but there are other places
I think you could start using it.
Yeah.
Leo Dion (host): Anything else you
want to mention before we close out?
Any sneak previews of
new point free stuff?
Brandon Williams (guest):
Yeah, I mean, let's see today.
So next week we're releasing 1.
0 of the composable architecture.
It's been in development for four years.
And so that's a big release.
And yeah, all the observation
and macro stuff, it really
fundamentally changes so much of what.
We've always wanted to do with
the composable architecture and
what wasn't really possible.
And so there's a lot of big stuff there.
And yeah, we've got this Swift
dependencies library just github.
com slash pointfreeco
slash swift dependencies.
And it's, and there, and we've got
links in the readme to all the other
dependency libraries out there.
Cause I am in no way saying
people should go and use ours.
Like we, we have made some very
particular decisions in the
design of our library that.
Makes it so that we can't even
do some of the things that
the other libraries can do.
But then, on the flip side, is that we
can do some things that they can't do.
It's just, there's tradeoffs
all over the place.
And I highly recommend people
go look at those libraries.
But yeah, we've got our
own version of the library.
And yeah.
I encourage people to check it out.
Leo Dion (host): Brandon, thank
you so much for coming on.
It was great to have you.
Where can people find you online?
Brandon Williams (guest): Yeah it's
MbrandenW on Twitter and then also on
Mastodon on the Hackaderm instance.
Leo Dion (host): Yeah.
Yeah.
Awesome.
And then of course, point free
we'll have links to that as well.
People can find me on
Twitter at Leo G Dion.
My company is bright digit.
If you're watching this on
YouTube, please and subscribe.
If you're listening to
this, please post a review.
Thank you so much for joining us.
And I look forward to
talking to everyone again.
Bye everyone.
Brandon Williams (guest): Thanks.
Bye.