- As programmers, we often work on applications and systems that consist of many components that are connected in different ways. Especially when using a highly rigorous programming language like
Swift
, it is difficult to find a way to model thedata
part that satisfies both the compiler and the easy-to-readcodebase
. - Let’s look at a case that involves customizing multiple
model data
of the samemodel data
and several different techniques and methods that allow us to handledata
safely.
1 / Mixed structures:
- Assuming that we are working on a cooking application that includes both videos and recipes in text form and the
content
we get returned from theweb service
inJSON
format is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <span class="token punctuation">{</span> <span class="token string">"items"</span> <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token string">"type"</span> <span class="token punctuation">:</span> <span class="token string">"video"</span> <span class="token punctuation">,</span> <span class="token string">"title"</span> <span class="token punctuation">:</span> <span class="token string">"Making perfect toast"</span> <span class="token punctuation">,</span> <span class="token string">"imageURL"</span> <span class="token punctuation">:</span> <span class="token string">"https://image-cdn.com/toast.png"</span> <span class="token punctuation">,</span> <span class="token string">"url"</span> <span class="token punctuation">:</span> <span class="token string">"https://videoservice.com/toast.mp4"</span> <span class="token punctuation">,</span> <span class="token string">"duration"</span> <span class="token punctuation">:</span> <span class="token string">"00:12:09"</span> <span class="token punctuation">,</span> <span class="token string">"resolution"</span> <span class="token punctuation">:</span> <span class="token string">"720p"</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token string">"type"</span> <span class="token punctuation">:</span> <span class="token string">"recipe"</span> <span class="token punctuation">,</span> <span class="token string">"title"</span> <span class="token punctuation">:</span> <span class="token string">"Tasty burritos"</span> <span class="token punctuation">,</span> <span class="token string">"imageURL"</span> <span class="token punctuation">:</span> <span class="token string">"https://image-cdn.com/burritos.png"</span> <span class="token punctuation">,</span> <span class="token string">"text"</span> <span class="token punctuation">:</span> <span class="token string">"Here's how to make the best burritos..."</span> <span class="token punctuation">,</span> <span class="token string">"ingredients"</span> <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token string">"Tortillas"</span> <span class="token punctuation">,</span> <span class="token string">"Salsa"</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> <span class="token punctuation">}</span> |
- Structured
JSON
as above is extremely popular, but creatingstruct
fit it can be quite difficult. We get anarray
ofitem
including recipes and videos. We will need to write amodel
where we candecode
both customizations simultaneously. - One way to do that is to create an
ItemType
enum
that includes instances for our two customizations as well as a unifiedmodel data
that contains all the properties we encounter and encapsulatesItemCollection
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <span class="token keyword">enum</span> <span class="token builtin">ItemType</span> <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token punctuation">,</span> <span class="token builtin">Decodable</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> video <span class="token keyword">case</span> recipe <span class="token punctuation">}</span> <span class="token keyword">struct</span> <span class="token builtin">Item</span> <span class="token punctuation">:</span> <span class="token builtin">Decodable</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> type <span class="token punctuation">:</span> <span class="token builtin">ItemType</span> <span class="token keyword">var</span> title <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token keyword">var</span> imageURL <span class="token punctuation">:</span> <span class="token constant">URL</span> <span class="token keyword">var</span> text <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token operator">?</span> <span class="token keyword">var</span> url <span class="token punctuation">:</span> <span class="token constant">URL</span> <span class="token operator">?</span> <span class="token keyword">var</span> duration <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token operator">?</span> <span class="token keyword">var</span> resolution <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token operator">?</span> <span class="token keyword">var</span> ingredients <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token builtin">String</span> <span class="token punctuation">]</span> <span class="token operator">?</span> <span class="token punctuation">}</span> <span class="token keyword">struct</span> <span class="token builtin">ItemCollection</span> <span class="token punctuation">:</span> <span class="token builtin">Decodable</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> items <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token builtin">Item</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> |
- The above approach allows us to
decode
JSON
but it is still not optimal because we are forced to implement most custom properties such as thisVideoPlayer
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">class</span> <span class="token class-name">VideoPlayer</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">playVideoItem</span> <span class="token punctuation">(</span> <span class="token number">_</span> item <span class="token punctuation">:</span> <span class="token builtin">Item</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// We can't establish a compile-time guarantee that the</span> <span class="token comment">// item passed to this method will, in fact, be a video.</span> <span class="token keyword">guard</span> <span class="token keyword">let</span> url <span class="token operator">=</span> item <span class="token punctuation">.</span> url <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token function">assertionFailure</span> <span class="token punctuation">(</span> <span class="token string">"Video item doesn't have a URL: <span class="token interpolation"><span class="token delimiter variable">(</span> item <span class="token delimiter variable">)</span></span> "</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token function">startPlayback</span> <span class="token punctuation">(</span> from <span class="token punctuation">:</span> url <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
2 / Complete polymorphism:
- We try to
model data
a collection of data into different types. We can create aprotocol
Item
contains all the attributes to be shared between two variants and twotype
separately:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <span class="token keyword">protocol</span> <span class="token builtin">Item</span> <span class="token punctuation">:</span> <span class="token builtin">Decodable</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> type <span class="token punctuation">:</span> <span class="token builtin">ItemType</span> <span class="token punctuation">{</span> <span class="token keyword">get</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> title <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token punctuation">{</span> <span class="token keyword">get</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> imageURL <span class="token punctuation">:</span> <span class="token constant">URL</span> <span class="token punctuation">{</span> <span class="token keyword">get</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">struct</span> <span class="token builtin">Video</span> <span class="token punctuation">:</span> <span class="token builtin">Item</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> type <span class="token punctuation">:</span> <span class="token builtin">ItemType</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> video <span class="token punctuation">}</span> <span class="token keyword">var</span> title <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token keyword">var</span> imageURL <span class="token punctuation">:</span> <span class="token constant">URL</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> duration <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token keyword">var</span> resolution <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token punctuation">}</span> <span class="token keyword">struct</span> <span class="token builtin">Recipe</span> <span class="token punctuation">:</span> <span class="token builtin">Item</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> type <span class="token punctuation">:</span> <span class="token builtin">ItemType</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> recipe <span class="token punctuation">}</span> <span class="token keyword">var</span> title <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token keyword">var</span> imageURL <span class="token punctuation">:</span> <span class="token constant">URL</span> <span class="token keyword">var</span> text <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token keyword">var</span> ingredients <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token builtin">String</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> |
- We also want to modify the wrapper
ItemCollection
of us to include thearray
for eachtype
eithertype
because otherwise how we will have to constantly enter values forVideo
orRecipe
:
1 2 3 4 5 | <span class="token keyword">struct</span> <span class="token builtin">ItemCollection</span> <span class="token punctuation">:</span> <span class="token builtin">Decodable</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> videos <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token builtin">Video</span> <span class="token punctuation">]</span> <span class="token keyword">var</span> recipes <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token builtin">Recipe</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> |
- To be able to reuse previous
Item
andItemCollection
implementations and rename them to fit the new purpose:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">private</span> <span class="token keyword">extension</span> <span class="token builtin">ItemCollection</span> <span class="token punctuation">{</span> <span class="token keyword">struct</span> <span class="token builtin">Encoded</span> <span class="token punctuation">:</span> <span class="token builtin">Decodable</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> items <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token builtin">EncodedItem</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token keyword">struct</span> <span class="token builtin">EncodedItem</span> <span class="token punctuation">:</span> <span class="token builtin">Decodable</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> type <span class="token punctuation">:</span> <span class="token builtin">ItemType</span> <span class="token keyword">var</span> title <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token keyword">var</span> imageURL <span class="token punctuation">:</span> <span class="token constant">URL</span> <span class="token keyword">var</span> text <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token operator">?</span> <span class="token keyword">var</span> url <span class="token punctuation">:</span> <span class="token constant">URL</span> <span class="token operator">?</span> <span class="token keyword">var</span> duration <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token operator">?</span> <span class="token keyword">var</span> resolution <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token operator">?</span> <span class="token keyword">var</span> ingredients <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token builtin">String</span> <span class="token punctuation">]</span> <span class="token operator">?</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- We are ready to
Decodable
but because we will need to add a few options when doing that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">extension</span> <span class="token builtin">ItemCollection</span> <span class="token punctuation">{</span> <span class="token keyword">struct</span> <span class="token builtin">MissingEncodedValue</span> <span class="token punctuation">:</span> <span class="token builtin">Error</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> name <span class="token punctuation">:</span> <span class="token builtin">String</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">private</span> <span class="token keyword">func</span> unwrap <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">(</span> <span class="token number">_</span> value <span class="token punctuation">:</span> T <span class="token operator">?</span> <span class="token punctuation">,</span> name <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token operator">-</span> <span class="token operator">></span> T <span class="token punctuation">{</span> <span class="token keyword">guard</span> <span class="token keyword">let</span> value <span class="token operator">=</span> value <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token function">MissingEncodedValue</span> <span class="token punctuation">(</span> name <span class="token punctuation">:</span> name <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> value <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Now let’s write the actual
decode
. We will start bydecode
a version of. We will convertItemCollection
items intoVideo
andRecipe
as follows:
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 27 28 29 | <span class="token keyword">extension</span> <span class="token builtin">ItemCollection</span> <span class="token punctuation">{</span> <span class="token keyword">init</span> <span class="token punctuation">(</span> from decoder <span class="token punctuation">:</span> <span class="token builtin">Decoder</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> container <span class="token operator">=</span> <span class="token keyword">try</span> decoder <span class="token punctuation">.</span> <span class="token function">singleValueContainer</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> collection <span class="token operator">=</span> <span class="token keyword">try</span> container <span class="token punctuation">.</span> <span class="token function">decode</span> <span class="token punctuation">(</span> <span class="token builtin">Encoded</span> <span class="token punctuation">.</span> <span class="token keyword">self</span> <span class="token punctuation">)</span> <span class="token keyword">for</span> item <span class="token keyword">in</span> collection <span class="token punctuation">.</span> items <span class="token punctuation">{</span> <span class="token keyword">switch</span> item <span class="token punctuation">.</span> type <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token punctuation">.</span> video <span class="token punctuation">:</span> <span class="token keyword">try</span> videos <span class="token punctuation">.</span> <span class="token function">append</span> <span class="token punctuation">(</span> <span class="token function">Video</span> <span class="token punctuation">(</span> type <span class="token punctuation">:</span> item <span class="token punctuation">.</span> type <span class="token punctuation">,</span> title <span class="token punctuation">:</span> item <span class="token punctuation">.</span> title <span class="token punctuation">,</span> imageURL <span class="token punctuation">:</span> item <span class="token punctuation">.</span> imageURL <span class="token punctuation">,</span> url <span class="token punctuation">:</span> <span class="token function">unwrap</span> <span class="token punctuation">(</span> item <span class="token punctuation">.</span> url <span class="token punctuation">,</span> name <span class="token punctuation">:</span> <span class="token string">"url"</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> duration <span class="token punctuation">:</span> <span class="token function">unwrap</span> <span class="token punctuation">(</span> item <span class="token punctuation">.</span> duration <span class="token punctuation">,</span> name <span class="token punctuation">:</span> <span class="token string">"duration"</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> resolution <span class="token punctuation">:</span> <span class="token function">unwrap</span> <span class="token punctuation">(</span> item <span class="token punctuation">.</span> resolution <span class="token punctuation">,</span> name <span class="token punctuation">:</span> <span class="token string">"resolution"</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token punctuation">.</span> recipe <span class="token punctuation">:</span> <span class="token keyword">try</span> recipes <span class="token punctuation">.</span> <span class="token function">append</span> <span class="token punctuation">(</span> <span class="token function">Recipe</span> <span class="token punctuation">(</span> type <span class="token punctuation">:</span> item <span class="token punctuation">.</span> type <span class="token punctuation">,</span> title <span class="token punctuation">:</span> item <span class="token punctuation">.</span> title <span class="token punctuation">,</span> imageURL <span class="token punctuation">:</span> item <span class="token punctuation">.</span> imageURL <span class="token punctuation">,</span> text <span class="token punctuation">:</span> <span class="token function">unwrap</span> <span class="token punctuation">(</span> item <span class="token punctuation">.</span> text <span class="token punctuation">,</span> name <span class="token punctuation">:</span> <span class="token string">"text"</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> ingredients <span class="token punctuation">:</span> <span class="token function">unwrap</span> <span class="token punctuation">(</span> item <span class="token punctuation">.</span> ingredients <span class="token punctuation">,</span> name <span class="token punctuation">:</span> <span class="token string">"ingredients"</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> |
- Instead of treating
instance
as separate implementations withprotocol
, treat them asinstance
of the samemodel
. That will have a pretty big impact on our final stage of the model structure. - Rename the previous
protocol
Item
toItemVariant
and remove itstype
attribute:
1 2 3 4 5 | <span class="token keyword">protocol</span> <span class="token builtin">ItemVariant</span> <span class="token punctuation">:</span> <span class="token builtin">Decodable</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> title <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token punctuation">{</span> <span class="token keyword">get</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> imageURL <span class="token punctuation">:</span> <span class="token constant">URL</span> <span class="token punctuation">{</span> <span class="token keyword">get</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- We model our actual
item
type as anenum
as follows:
1 2 3 4 5 | <span class="token keyword">enum</span> <span class="token builtin">Item</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token function">video</span> <span class="token punctuation">(</span> <span class="token builtin">Video</span> <span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token function">recipe</span> <span class="token punctuation">(</span> <span class="token builtin">Recipe</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
- We can greatly simplify the
decode
implementation that takes place entirely within the newItem
itself, and simply check eachJSON
item’s type value to determine which type ofdecode
:
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">extension</span> <span class="token builtin">Item</span> <span class="token punctuation">:</span> <span class="token builtin">Decodable</span> <span class="token punctuation">{</span> <span class="token keyword">struct</span> <span class="token builtin">InvalidTypeError</span> <span class="token punctuation">:</span> <span class="token builtin">Error</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> type <span class="token punctuation">:</span> <span class="token builtin">String</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">private</span> ` <span class="token keyword">enum</span> ` <span class="token builtin">CodingKeys</span> <span class="token punctuation">:</span> <span class="token builtin">CodingKey</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> type <span class="token punctuation">}</span> <span class="token keyword">init</span> <span class="token punctuation">(</span> from decoder <span class="token punctuation">:</span> <span class="token builtin">Decoder</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> container <span class="token operator">=</span> <span class="token keyword">try</span> decoder <span class="token punctuation">.</span> <span class="token function">container</span> <span class="token punctuation">(</span> keyedBy <span class="token punctuation">:</span> <span class="token builtin">CodingKeys</span> <span class="token punctuation">.</span> <span class="token keyword">self</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> type <span class="token operator">=</span> <span class="token keyword">try</span> container <span class="token punctuation">.</span> <span class="token function">decode</span> <span class="token punctuation">(</span> <span class="token builtin">String</span> <span class="token punctuation">.</span> <span class="token keyword">self</span> <span class="token punctuation">,</span> forKey <span class="token punctuation">:</span> <span class="token punctuation">.</span> type <span class="token punctuation">)</span> <span class="token keyword">switch</span> type <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">"video"</span> <span class="token punctuation">:</span> <span class="token keyword">self</span> <span class="token operator">=</span> <span class="token punctuation">.</span> <span class="token function">video</span> <span class="token punctuation">(</span> <span class="token keyword">try</span> <span class="token function">Video</span> <span class="token punctuation">(</span> from <span class="token punctuation">:</span> decoder <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token string">"recipe"</span> <span class="token punctuation">:</span> <span class="token keyword">self</span> <span class="token operator">=</span> <span class="token punctuation">.</span> <span class="token function">recipe</span> <span class="token punctuation">(</span> <span class="token keyword">try</span> <span class="token function">Recipe</span> <span class="token punctuation">(</span> from <span class="token punctuation">:</span> decoder <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">default</span> <span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token function">InvalidTypeError</span> <span class="token punctuation">(</span> type <span class="token punctuation">:</span> type <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Since our
Item
implementation is responsible fordecode
its owninstance
, we canItemCollection
returnItemCollection
simply as anarray
ofItem
values allowed based on the default implementation ofDecodable
:
1 2 3 4 | <span class="token keyword">struct</span> itemCollection <span class="token punctuation">:</span> <span class="token builtin">Decodable</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> items <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token builtin">Item</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> |
- The benefit is that we continue to use specialized models while keeping our
decode
simple and the overall order of theitem
remains the same, but the downside is that it requires decompressing eachitem
before using it. use:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">extension</span> <span class="token builtin">ItemCollection</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">allTitles</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token punctuation">[</span> <span class="token builtin">String</span> <span class="token punctuation">]</span> <span class="token punctuation">{</span> items <span class="token punctuation">.</span> <span class="token builtin">map</span> <span class="token punctuation">{</span> item <span class="token keyword">in</span> <span class="token keyword">switch</span> item <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token punctuation">.</span> <span class="token function">video</span> <span class="token punctuation">(</span> <span class="token keyword">let</span> video <span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">return</span> video <span class="token punctuation">.</span> title <span class="token keyword">case</span> <span class="token punctuation">.</span> <span class="token function">recipe</span> <span class="token punctuation">(</span> <span class="token keyword">let</span> recipe <span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">return</span> recipe <span class="token punctuation">.</span> title <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Although we will have to continue writing the above
code
whenever we need to access specificdata
for recipes orVideo
. We can do it to provide direct access to any of the attributes defined in theItemVariant
protocol
and useSwift
‘ssubscription
lookup feature. - Adding the
@dynamicMemberLookup
attribute to our mainitem
declaration:
1 2 3 4 5 6 | @dynamicMemberLookup <span class="token keyword">enum</span> <span class="token builtin">Item</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token function">video</span> <span class="token punctuation">(</span> <span class="token builtin">Video</span> <span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token function">recipe</span> <span class="token punctuation">(</span> <span class="token builtin">Recipe</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
- Since
Swift 5.1
we have added support forsubscription
in a completely safe and easy way as follows:
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">extension</span> <span class="token builtin">Item</span> <span class="token punctuation">{</span> <span class="token keyword">subscript</span> <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">(</span> dynamicMember keyPath <span class="token punctuation">:</span> <span class="token builtin">KeyPath</span> <span class="token operator"><</span> <span class="token builtin">ItemVariant</span> <span class="token punctuation">,</span> T <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> T <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token keyword">self</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token punctuation">.</span> <span class="token function">video</span> <span class="token punctuation">(</span> <span class="token keyword">let</span> video <span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">return</span> video <span class="token punctuation">[</span> keyPath <span class="token punctuation">:</span> keyPath <span class="token punctuation">]</span> <span class="token keyword">case</span> <span class="token punctuation">.</span> <span class="token function">recipe</span> <span class="token punctuation">(</span> <span class="token keyword">let</span> recipe <span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">return</span> recipe <span class="token punctuation">[</span> keyPath <span class="token punctuation">:</span> keyPath <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- We can now access any properties shared between
Video
andRecipe
(via theItemVariant
protocol
) as if that attribute were defined within theItem
type itself. We can convert theallTitle
method to the following:
1 2 3 4 5 6 | <span class="token keyword">extension</span> <span class="token builtin">ItemCollection</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">allTitles</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token punctuation">[</span> <span class="token builtin">String</span> <span class="token punctuation">]</span> <span class="token punctuation">{</span> items <span class="token punctuation">.</span> <span class="token function">map</span> <span class="token punctuation">(</span> <span class="token punctuation">.</span> title <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |