00:06 This episode is a bit different because it's a Q&A episode where we
answer questions that have been sent in to us. 00:32 We'd like to know if
people like these Q&A episodes, so if you have feedback, please send us an
email.
Storing Authentication Information
00:48 Let's start with the first question: Where do you store
authentication information when making a request? 00:57 This is a
question about the Networking
episode, where we presented a networking layer with a Resource
struct.
01:12 In many cases, we have to use an authentication token for almost
every request, as most endpoints are authenticated. Therefore, it would make
sense to put the token in the Webservice
class:
final class Webservice {
var authenticationToken: String?
}
01:50 Then we'd modify the request in the load
method to include the
token as a header field.
If it's important for your business logic to make a clear separation between
authenticated and non-authenticated requests, then you could put the token in
the Resource
instead.
HTTP Headers
02:16 The next question: Where would you add HTTP headers —
specifically, headers such as Content-Type
, which are also connected to how
you parse the resource? 02:30 A header like Content-Type
would be a
perfect candidate to put into the Resource
struct, as it's specific to the
endpoint. We could, for example, add a dictionary called headers
:
struct Resource<A> {
let url: URL
let method: HttpMethod<Data>
let parse: (Data) -> A?
let headers: [String: String]
}
Alternatively, we could add a separate property for the header:
struct Resource<A> {
let url: URL
let method: HttpMethod<Data>
let parse: (Data) -> A?
let contentType: String?
}
02:55 Having a headers
dictionary is more generic, so it's also a bit
more flexible. We could modify our initializer for JSON resources to
automatically set the Content-Type
or the Accept-Type
. Likewise, if we want
to accept XML, we would need a different parsing function, and we can support
this by adding another initializer to Resource
so that the parsing function
and Content-Type
automatically match up.
03:28 We could even combine the two questions: in our load
method in
the WebService
, we could add both headers that are Resource
specific and
headers that are set for all resources.
Multiple Cell Types
03:47 The next question: How would you extend the approach in the
Generic Table View
Controllers
episode to handle different cell types within the same table view?
04:14 At first, we thought it'd be easy by playing around with protocols
a bit, but when we actually tried figuring it out, it wasn't so easy. Our
conclusion was that it's possible, but none of our solutions felt as nice as the
generic table view controller for a single cell type. 04:39 For example,
one simple solution had a big drawback: we needed to move a force-cast from our
table view controller (the library) to the configure callback (the client of the
library). 05:03 Currently, within tableView(cellForRowAt:)
, we cast
our cell to the right type so the configure
callback gets called with the
right cell type.
05:23 No matter how we solve this problem, we always end up with a
compromise. 05:31 We haven't found a nice solution, so if anyone
discovers something, let us know.
Adding UITextView
Support to ContentElement
05:39 Another question: In the Stack Views with
Enums episode, if
you want to add support for a text view, how do you deal with all the callbacks?
06:01 A text view has multiple callbacks. If we only had a single
callback, we could use something similar to the .button
case, but what about
when there are multiple callbacks?
06:18 First, the ContentElement
approach is something we built for
convenience, so it might not be the best tool for this problem.
06:26 Second, we could also add a custom
case to the enum.
06:38 This way, we can use any view within the stack view and we'd pass
in a fully configured UIView
subclass. This allows us to have more complicated
views that we use together with the convenience cases:
enum ContentElement {
case label(String)
case button(String, () -> ())
case image(UIImage)
case custom(UIView)
}
06:57 Third, we could also add a dedicated case for the text view. To
keep all the delegate callbacks in check, we could first start with spelling
them out individually:
enum ContentElement {
case label(String)
case button(String, () -> ())
case image(UIImage)
case textView(String, didChange: (String) -> (), didBeginEditing: () -> ())
}
This get unwieldy quickly. Instead, we could have a single callback that groups
these events together in an enum, with one case per event:
enum TextViewEvent {
case didChange(String)
case didBeginEditing
case openURL(URL)
}
enum ContentElement {
case label(String)
case button(String, () -> ())
case image(UIImage)
case textView(String, (TextViewEvent) -> ())
}
By the way, we could also take this approach in other situations where the
number of callbacks gets out of hand — for example, in a view controller such as
the one described in our Connecting View
Controllers
episode.
Custom UIStoryboardSegue
Subclasses
08:50 The next question: Is the App
class in the Connecting View
Controllers
episode such a good idea? It will get complicated as we add more features. An
alternative approach might be creating subclasses of UIStoryboardSegue
, just
like in Andy Matuschak's talk, Refactor the Mega
Controller.
Rather than having all the logic in an App
class, he moved a lot of logic to
segues. That way, it wasn't in the view controller.
09:30 There are two parts to this question. Firstly, we'll have to
decide if we want to use storyboards at all. In our case, we decided not to use
storyboards, and then we ended up with an App
class that could become very
big. That's definitely a problem, but it should be easy to split it up into
multiple classes.
10:00 The second part is that we've never tried having a storyboard with
custom segue subclasses. However, it seems like a sensible approach when using
storyboards. There's no one right way. Our solution was definitely more
experimental and strange, but it worked well for our use case.
Storyboards, Nibs, or Code?
10:26 There's a related question that we get a lot: Should I use
storyboards, or nibs, or just code? There's no way we can answer this. We've
used all these approaches in past projects, and all of them worked well. Just
choose whatever fits you and your project. With all three approaches, you'll
have to be careful to keep your code maintainable by continually refactoring
your code.
Testing
11:08 There are also questions about testing: What should I test? How
much should I test? What's the best way to do this? Again, there's no right
answer. We've used different approaches ourselves. Once, for a Rails client
project, we used BDD and this worked really well. Once we handed over the
project, the next person could just start working without us having to spend a
lot of time transferring the code base.
11:59 We also have some episodes coming up where we develop things in a
TDD way because it's a perfect fit. In those particular instances, this made
development much faster and easier. 12:16 There are other parts of the
same app that we only tested manually. In a lot of projects, we only tested a
specific critical part or almost nothing. It depends on your project. Again,
there's no one right way to test. It depends on who you're working with, what
your project does, how important it is, and so on.
12:45 It's important that you figure out for yourself what helps you and
what doesn't help you, along with when testing helps you. It's not so important
what we do or how we come to our decisions, because everyone needs to figure it
out for themselves personally. Different people have different capabilities and
different ways of working.
13:13 When you think about testing, it's good to keep in mind the ways
in which it can help you. For example, testing can help you write the code
correctly as a verification, but it can also help you design your code. More
specifically, it can help you define the API of your code. Finally, tests can
serve as documentation. This is also useful when working with multiple people.