It does not set translatesAutoresizingMaskIntoConstraints = false on a UIView being assigned to tableView.tableFooterView or else it gets the hose again. Yes, it will, Precious, won’t it? It will get the hose!
Author: Mark
Happy Friday!
Happy Friday! A client let the certificate on their QA endpoint expire overnight. Now if their Production certificate had expired then I would be in for a fun weekend! 🤠
How (not to) encode an array
Sounds simple, right? But yesterday I messed up this simple operation and was stuck for a couple hours wondering why an API request wasn’t working.
Background
A pattern I follow is to use Swift struct
‘s to represent each network request my app needs. Each struct has only let
properties and conforms to Encodable
so that it can be serialized to JSON and sent to the server by my networking class. Typically this is straightforward and the top-level JSON object is almost always a dictionary.
For example:
struct MyRequest: Encodable { let uid: String let date: Date let bar: Int? }
Thanks to the magic of the compiler, this will encode to a JSON dictionary just fine without any boiler plate code on my side.
Occasionally if the names of the properties don’t match the names of the parameters the API expects (typically because the API is not in my control and follows some naming convention I don’t like – or even no naming convention whatsoever), then I need to declare CodingKeys
to properly map the encoding.
Some times there’s no avoiding it though and I need to implement the encode
function myself. I often need this when the server expects some weird Date format or some business logic is needed for the encoding.
The Issue
Yesterday I implemented a simple network request that needed to be serialized as an array.
struct CallsRequest: Encodable { let calls: [Call] // Call conforms to Encodable }
The trouble is that this would encode to a Dictionary with one key “calls” whose value is the array. What I needed was to encode to an Array instead.
func encode(to encoder: Encoder) throws { var values = encoder.unkeyedContainer() try values.encode(calls) }
I had never used unkeyedContainer()
before. Previously I’ve always used container(keyedBy:)
, which encodes as a dictionary (hence keyed).
The output of the approach above was an object of type [[Call]]
. That is, it encodes an array of arrays instead of just an array. (Someone else spotted the extra brackets in the JSON output. Thanks Anton!)
It took me a while to realize where the extra level of Array was coming from, but then it dawned on me: the unkeyedContainer()
is an Array, just as the container(keyedBy:)
is a Dictionary.
So what I really needed was something like this:
func encode(to encoder: Encoder) throws { var values = encoder.unkeyedContainer() for call in calls { try values.encode(call) } }
This delivered the expected output of type [Call]
. Problem solved.
Anyway, I hope that someday this might save someone a few hours of frustration.
Update
Where the real JSON encoding happens is via another custom protocol that I didn’t mention. Basically I want objects to be able to convert themselves to Data
so that they could be set as the body to an HTTP PUT/POST/PATCH/DELETE request.
public protocol ParametersBuilder { func data() throws -> Data func dictionary() throws -> [String: Any] }
dictionary()
is for GET requests or for .formUrlEncoded
request bodies. But data()
is used for everything else.
The default implementation of data()
is this:
extension Encodable { public func data() throws -> Data { return try JSONEncoder().encode(self) } }
(which is why CallsRequest conforms to Encodable
and what by default would turn it into a dictionary)
A simpler solution to my problem would have been to provide a custom implementation of data()
on CallsRequest instead of conforming to Encodable
:
struct CallsRequest: ParametersBuilder { let calls: [Call] // Call conforms to Encodable public func data() throws -> Data { return try JSONEncoder().encode(calls) } }
Update 2
Rob Napier pointed out to me that I could use singleValueContainer
to skip having to loop through the array.
func encode(to encoder: Encoder) throws { var values = encoder.singleValueContainer() try values.encode(calls) }
Unsubscribe
It’s some serious next level bullshit when a company demands a phone number or another email address just so you can unsubscribe from unsolicited emails. Looking at you, Microsoft.
Adventure of Link finished
I finished Adventure of Link yesterday. I think next up I will play Link’s Awakening…
Most Powerful Magic
“I can give you most powerful magic.” I love the dialog in old NES games. Also the CRT-mimicking filter (left, obviously) is uncannily accurate.
Mostly Zelda or Mario
(And by video games I mostly mean Legend of Zelda or Mario games.)
Play more video games!
One of my New Year’s Resolutions was to play more video games, so I’ve been trying to stick to that. (I often go months without playing anything, which is lame.)
Adventure of Link
I’m playing Adventure of Link on the NES Classic and making extensive (ab)use of the save point feature. This game was hard! I had attempted a play through almost two years ago, but gave up because the combats were so difficult.
Level 8
I restarted my play through of The Adventure of Link (NES), only this time I spent a few hours xp farming in the first palace. I finished Parapa Palace with Attack, Magic, and Life fully leveled up. That should make the remainder of the game considerably easier.