- A different application with different features and systems should be clearly separated in terms of their function and scope of influence, which is of great interest in software development. So there are many structures and techniques + principles that have been introduced over the years that make it easier to code in Swift and many other languages.
- For whatever type of structure we choose to apply in a specific project, we need to ensure that each type has a clear and unique set of responsibilities. This becomes especially difficult as the project continues to be developed and adds many new features.
- Let’s take a look at a few techniques that can help us do that by separating types once their responsibilities are beyond the allowed limits.
1 / States and scopes:
- A source code becomes complex when a single type to handle has multiple
scope
effects as well as various statestate
. For example, we work on thenetworking layer
of the application and we have implemented the entireclass
in a singleclass
calledNetworkController
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <span class="token keyword">class</span> <span class="token class-name">NetworkController</span> <span class="token punctuation">{</span> <span class="token keyword">typealias</span> <span class="token builtin">Handler</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token builtin">Result</span> <span class="token operator"><</span> <span class="token builtin">Data</span> <span class="token punctuation">,</span> <span class="token builtin">NetworkError</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Void</span> <span class="token keyword">var</span> accessToken <span class="token punctuation">:</span> <span class="token builtin">AccessToken</span> <span class="token operator">?</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token keyword">func</span> <span class="token function">request</span> <span class="token punctuation">(</span> <span class="token number">_</span> endpoint <span class="token punctuation">:</span> <span class="token builtin">Endpoint</span> <span class="token punctuation">,</span> then handler <span class="token punctuation">:</span> @escaping <span class="token builtin">Handler</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> request <span class="token operator">=</span> <span class="token function">URLRequest</span> <span class="token punctuation">(</span> url <span class="token punctuation">:</span> endpoint <span class="token punctuation">.</span> url <span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token keyword">let</span> token <span class="token operator">=</span> accessToken <span class="token punctuation">{</span> request <span class="token punctuation">.</span> <span class="token function">addValue</span> <span class="token punctuation">(</span> <span class="token string">"Bearer <span class="token interpolation"><span class="token delimiter variable">(</span> token <span class="token delimiter variable">)</span></span> "</span> <span class="token punctuation">,</span> forHTTPHeaderField <span class="token punctuation">:</span> <span class="token string">"Authorization"</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// Perform the request</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Although implementing an entire feature or system in a
class
is not a bad thing, in this case we have a confusingsource code
. Because we use the same API to request both a publicendpoint
and anendpoint
, as well as when authentication is required. - It would be easier if we could use the Swift type system to prevent all
endpoint
requiring authentication to be called without a valid access token. That way, we will be able to validate our network code much more closely intocomplie time
. - To do that, move all the
authentication
related codes and access tokens fromNetworkContoder
and into a new instance of thatclass
, we namedAuthenticatedNetworkContoder
. Thecontroller
allows us to makenetwork call
based onendpoint
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <span class="token keyword">class</span> <span class="token class-name">AuthenticatedNetworkController</span> <span class="token punctuation">{</span> <span class="token keyword">typealias</span> <span class="token builtin">Handler</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token builtin">Result</span> <span class="token operator"><</span> <span class="token builtin">Data</span> <span class="token punctuation">,</span> <span class="token builtin">NetworkError</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Void</span> <span class="token keyword">private</span> <span class="token keyword">var</span> tokens <span class="token punctuation">:</span> <span class="token builtin">NetworkTokens</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token keyword">init</span> <span class="token punctuation">(</span> tokens <span class="token punctuation">:</span> <span class="token builtin">NetworkTokens</span> <span class="token punctuation">,</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span> <span class="token punctuation">.</span> tokens <span class="token operator">=</span> tokens <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">request</span> <span class="token punctuation">(</span> <span class="token number">_</span> endpoint <span class="token punctuation">:</span> <span class="token builtin">AuthenticatedEndpoint</span> <span class="token punctuation">,</span> then handler <span class="token punctuation">:</span> @escaping <span class="token builtin">Handler</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> refreshTokensIfNeeded <span class="token punctuation">{</span> tokens <span class="token keyword">in</span> <span class="token keyword">var</span> request <span class="token operator">=</span> <span class="token function">URLRequest</span> <span class="token punctuation">(</span> url <span class="token punctuation">:</span> endpoint <span class="token punctuation">.</span> url <span class="token punctuation">)</span> request <span class="token punctuation">.</span> <span class="token function">addValue</span> <span class="token punctuation">(</span> <span class="token string">"Bearer <span class="token interpolation"><span class="token delimiter variable">(</span> tokens <span class="token punctuation">.</span> access <span class="token delimiter variable">)</span></span> "</span> <span class="token punctuation">,</span> forHTTPHeaderField <span class="token punctuation">:</span> <span class="token string">"Authorization"</span> <span class="token punctuation">)</span> <span class="token comment">// Perform the request</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- We also provide
network controller
type ofend point
dedicated its ownAuthenticatedEndpoint
. That also clearly identifies ourendpoint
definitions, so an authentication requestendpoint
was accidentally passed to our previousNetworkContoder
. - Because that
type
no longer has to handle any authentication requests, we can greatly simplify and rename it (and itsend point
type) better describe its new role in thenetwork layer
of we :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">class</span> <span class="token class-name">NonAuthenticatedNetworkController</span> <span class="token punctuation">{</span> <span class="token keyword">typealias</span> <span class="token builtin">Handler</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token builtin">Result</span> <span class="token operator"><</span> <span class="token builtin">Data</span> <span class="token punctuation">,</span> <span class="token builtin">NetworkError</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Void</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token keyword">func</span> <span class="token function">request</span> <span class="token punctuation">(</span> <span class="token number">_</span> endpoint <span class="token punctuation">:</span> <span class="token builtin">NonAuthenticatedEndpoint</span> <span class="token punctuation">,</span> then handler <span class="token punctuation">:</span> @escaping <span class="token builtin">Handler</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> request <span class="token operator">=</span> <span class="token function">URLRequest</span> <span class="token punctuation">(</span> url <span class="token punctuation">:</span> endpoint <span class="token punctuation">.</span> url <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> ``` <span class="token operator">-</span> <span class="token builtin">Chúng</span> ta cần một kiến trúc và độ rõ ràng của <span class="token constant">API</span> thì cũng phải chấp nhận một số lượng code bị trùng lặp <span class="token punctuation">.</span> <span class="token builtin">Trong</span> trường hợp này cả <span class="token number">2</span> `network controller` của chúng ta cần tạo các phiên bản ` <span class="token builtin">URLRequest</span> ` và thực hiện chúng <span class="token punctuation">,</span> cũng như xử lý các tác vụ như lưu trữ và các hoạt động liên quan đến mạng khác <span class="token punctuation">.</span> ```swift <span class="token keyword">typealias</span> <span class="token builtin">NetworkResultHandler</span> <span class="token operator"><</span> T <span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token builtin">Result</span> <span class="token operator"><</span> T <span class="token punctuation">,</span> <span class="token builtin">NetworkError</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Void</span> |
- We can begin to transfer the deployed
network
base off thecontroller
and into thetype
smaller, more specialized. For example, we can create a separateNetworkRequestPerformer
that both of ourcontroller
can use to executecontroller
requests while keeping the top-levelAPI
completely separate andtype
safe:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <span class="token keyword">private</span> <span class="token keyword">struct</span> <span class="token builtin">NetworkRequestPerformer</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> url <span class="token punctuation">:</span> <span class="token constant">URL</span> <span class="token keyword">var</span> accessToken <span class="token punctuation">:</span> <span class="token builtin">AccessToken</span> <span class="token operator">?</span> <span class="token keyword">var</span> cache <span class="token punctuation">:</span> <span class="token builtin">Cache</span> <span class="token operator"><</span> <span class="token constant">URL</span> <span class="token punctuation">,</span> <span class="token builtin">Data</span> <span class="token operator">></span> <span class="token keyword">func</span> <span class="token function">perform</span> <span class="token punctuation">(</span> then handler <span class="token punctuation">:</span> @escaping <span class="token builtin">NetworkResultHandler</span> <span class="token operator"><</span> <span class="token builtin">Data</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token keyword">let</span> data <span class="token operator">=</span> cache <span class="token punctuation">.</span> <span class="token function">data</span> <span class="token punctuation">(</span> forKey <span class="token punctuation">:</span> url <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">handler</span> <span class="token punctuation">(</span> <span class="token punctuation">.</span> <span class="token function">success</span> <span class="token punctuation">(</span> data <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> request <span class="token operator">=</span> <span class="token function">URLRequest</span> <span class="token punctuation">(</span> url <span class="token punctuation">:</span> url <span class="token punctuation">)</span> <span class="token comment">// This if-statement is no longer a problem, since it's now</span> <span class="token comment">// hidden behind a type-safe abstraction that prevents</span> <span class="token comment">// accidential misuse.</span> <span class="token keyword">if</span> <span class="token keyword">let</span> token <span class="token operator">=</span> accessToken <span class="token punctuation">{</span> request <span class="token punctuation">.</span> <span class="token function">addValue</span> <span class="token punctuation">(</span> <span class="token string">"Bearer <span class="token interpolation"><span class="token delimiter variable">(</span> token <span class="token delimiter variable">)</span></span> "</span> <span class="token punctuation">,</span> forHTTPHeaderField <span class="token punctuation">:</span> <span class="token string">"Authorization"</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
-Now we can allow both of our network controller
to focus solely on providing type-safe API
to fulfill requests while their basic implementations are being kept in sync through the utility type:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">class</span> <span class="token class-name">AuthenticatedNetworkController</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token keyword">func</span> <span class="token function">request</span> <span class="token punctuation">(</span> <span class="token number">_</span> endpoint <span class="token punctuation">:</span> <span class="token builtin">AuthenticatedEndpoint</span> <span class="token punctuation">,</span> then handler <span class="token punctuation">:</span> @escaping <span class="token builtin">NetworkResultHandler</span> <span class="token operator"><</span> <span class="token builtin">Data</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> refreshTokensIfNeeded <span class="token punctuation">{</span> <span class="token punctuation">[</span> cache <span class="token punctuation">]</span> tokens <span class="token keyword">in</span> <span class="token keyword">let</span> performer <span class="token operator">=</span> <span class="token function">NetworkRequestPerformer</span> <span class="token punctuation">(</span> url <span class="token punctuation">:</span> endpoint <span class="token punctuation">.</span> url <span class="token punctuation">,</span> accessToken <span class="token punctuation">:</span> tokens <span class="token punctuation">.</span> access <span class="token punctuation">,</span> cache <span class="token punctuation">:</span> cache <span class="token punctuation">)</span> performer <span class="token punctuation">.</span> <span class="token function">perform</span> <span class="token punctuation">(</span> then <span class="token punctuation">:</span> handler <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
2 / Loading versus managing objects:
- Next, consider another type of situation that can make certain parts of the
source code
more complex than necessary, when the same type is responsible for bothloading
andmanaging
a certainobject
. A very common example is when everything related to a user’ssession
has been implemented in a singletype
– such asUserManager
. - For example, here such a
type
is responsible for both the user logging into and exiting the application, keeping the currently logged-inUser
in sync with our server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <span class="token keyword">class</span> <span class="token class-name">UserManager</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token punctuation">(</span> <span class="token keyword">set</span> <span class="token punctuation">)</span> <span class="token keyword">var</span> user <span class="token punctuation">:</span> <span class="token builtin">User</span> <span class="token operator">?</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token keyword">func</span> <span class="token function">logIn</span> <span class="token punctuation">(</span> with credentials <span class="token punctuation">:</span> <span class="token builtin">LoginCredentials</span> <span class="token punctuation">,</span> then handler <span class="token punctuation">:</span> @escaping <span class="token builtin">NetworkResultHandler</span> <span class="token operator"><</span> <span class="token builtin">User</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">sync</span> <span class="token punctuation">(</span> then handler <span class="token punctuation">:</span> @escaping <span class="token builtin">NetworkResultHandler</span> <span class="token operator"><</span> <span class="token builtin">User</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">logOut</span> <span class="token punctuation">(</span> then handler <span class="token punctuation">:</span> @escaping <span class="token builtin">NetworkResultHandler</span> <span class="token operator"><</span> <span class="token builtin">Void</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- The first thing we will do is extract all the
code
associated with downloading theUSer
versions fromUserManager
and into a separate newtype
. We will call itUserLoader
and use ourAuthenticatedNetworkControll
beforehand to request theuser
‘sendpoint
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">struct</span> <span class="token builtin">UserLoader</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> networkController <span class="token punctuation">:</span> <span class="token builtin">AuthenticatedNetworkController</span> <span class="token keyword">func</span> <span class="token function">loadUser</span> <span class="token punctuation">(</span> withID id <span class="token punctuation">:</span> <span class="token builtin">User</span> <span class="token punctuation">.</span> <span class="token constant">ID</span> <span class="token punctuation">,</span> then handler <span class="token punctuation">:</span> @escaping <span class="token builtin">NetworkResultHandler</span> <span class="token operator"><</span> <span class="token builtin">User</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> networkController <span class="token punctuation">.</span> <span class="token function">request</span> <span class="token punctuation">(</span> <span class="token punctuation">.</span> <span class="token function">user</span> <span class="token punctuation">(</span> withID <span class="token punctuation">:</span> id <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> result <span class="token keyword">in</span> <span class="token comment">// Decode the network result into a User instance,</span> <span class="token comment">// then call the passed handler with the end result.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- By splitting our
UserManager
into smaller blocks we can allow many of our functions to be implemented as non-state
structures because thosetype
will simply perform tasks instead of arguments. another object (like our previousNetworkRequestPerformer
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="token keyword">struct</span> <span class="token builtin">LoginPerformer</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> networking <span class="token punctuation">:</span> <span class="token builtin">NonAuthenticatedNetworkController</span> <span class="token keyword">func</span> <span class="token function">login</span> <span class="token punctuation">(</span> using credentials <span class="token punctuation">:</span> <span class="token builtin">LoginCredentials</span> <span class="token punctuation">,</span> then handler <span class="token punctuation">:</span> @escaping <span class="token builtin">NetworkResultHandler</span> <span class="token operator"><</span> <span class="token builtin">NetworkTokens</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Send the passed credentials to our server's login</span> <span class="token comment">// endpoint, and then call the passed completion handler</span> <span class="token comment">// with the tokens that were returned.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- The advantage of the above code is that we can now use one of our two new
type
whenever we need to perform specific tasks of thattype
instead of always having to use the sameUserManager
regardless We log in, log out or simply update the currentUser
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <span class="token keyword">class</span> <span class="token class-name">UserManager</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token punctuation">(</span> <span class="token keyword">set</span> <span class="token punctuation">)</span> <span class="token keyword">var</span> user <span class="token punctuation">:</span> <span class="token builtin">User</span> <span class="token keyword">private</span> <span class="token keyword">let</span> loader <span class="token punctuation">:</span> <span class="token builtin">UserLoader</span> <span class="token keyword">init</span> <span class="token punctuation">(</span> user <span class="token punctuation">:</span> <span class="token builtin">User</span> <span class="token punctuation">,</span> loader <span class="token punctuation">:</span> <span class="token builtin">UserLoader</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span> <span class="token punctuation">.</span> user <span class="token operator">=</span> user <span class="token keyword">self</span> <span class="token punctuation">.</span> loader <span class="token operator">=</span> loader <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">sync</span> <span class="token punctuation">(</span> then handler <span class="token punctuation">:</span> @escaping <span class="token builtin">NetworkResultHandler</span> <span class="token operator"><</span> <span class="token builtin">User</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> loader <span class="token punctuation">.</span> <span class="token function">loadUser</span> <span class="token punctuation">(</span> withID <span class="token punctuation">:</span> user <span class="token punctuation">.</span> id <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span> <span class="token keyword">weak</span> <span class="token keyword">self</span> <span class="token punctuation">]</span> result <span class="token keyword">in</span> <span class="token keyword">if</span> <span class="token keyword">let</span> user <span class="token operator">=</span> <span class="token keyword">try</span> <span class="token operator">?</span> result <span class="token punctuation">.</span> <span class="token keyword">get</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span> <span class="token operator">?</span> <span class="token punctuation">.</span> user <span class="token operator">=</span> user <span class="token punctuation">}</span> <span class="token function">handler</span> <span class="token punctuation">(</span> result <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- The refactoring tool above not only allows us to remove an unnecessary option, but also allows us to remove a lot of ambiguous statements if and protects from any code that depends on the
User
Logging provides us with a greater degree of flexibility because we can now choose the level of abstraction related to theUser
we want to work with in each new feature we will build.