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.
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.
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.
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.