And what can I make a custom Encoder do?
This article is part of a series.
- Part 1: What if I just copy-paste from the web?
- Part 2: How do you get messages to Swift directly?
- Part 3: Okay, but how about all the way up to the View?
- Part 4: How to do some basic file handling?
- Part 5: How do custom Encoder's work?
- Part 6: This Article
- Part 7: Wait, how do I scan text again?
- Part 8: Date Parsing. Nose wrinkle.
- Part 9: What would be a very simple working Decoder?
Relevant repo: https://github.com/carlynorama/CoderExplorer/
After last post’s extensive review of options, I decided to write an Encoder along lines of the StackOverflow example in that
- every item would be a
String
- the full codingPath would be in the key.
The implementation will be different.
The first test needed to pass?
struct TestStruct:Codable {
let number:Int
let text:String
let sub:TestSubStruct
}
struct TestSubStruct:Codable {
let numeral:Int
let string:String
}
func testExample() throws {
let encoder = SimpleCoder()
let sub = TestSubStruct(numeral: 34, string: "world")
let testItem = TestStruct(number: 12, text: "hello", sub: sub)
let expected = "number:12/sub.numeral:34/sub.string:world/text:hello"
XCTAssertEqual(expected, try encoder.encode(testItem))
}
- “:” The delimiter between key and value
- “/” The delimiter between items
- “.” The delimiter between keys in nested values
Getting Started with a Keyed Container
Some initial decisions
- Since not a lot of fancy is going to be happening, all of the smarts should live in the
:Encoder
so I don’t have to go chasing around the whole code base to find it. - I’ll avoid doing the nested-push-pop custom codingPath data type thing for now, I’ll just make new encoders. This should cause zero problems for this
Encoder
because every single key has its full path. We can just keep passing in the same data storage reference with no collisions or confusions.
Super basic public face of the Encoder system:
struct SimpleCoder {
public func encode<E:Encodable>(_ value: E) throws -> String {
let encoder = _SimpleEncoder()
try value.encode(to: encoder)
return encoder.value
}
}
A Dumb-As-Rocks reference type for the data, a la the objc.io example
final class SimpleCoderData<Value> {
var storage:Value
init(_ value: Value) {
self.storage = value
}
}
The type that will eventually conform to Encoder
. It will know how to turn the SimpleCoderData
type into a String
.
struct _SimpleEncoder {
var data:SimpleCoderData<[String: String]>
var codingPath: [CodingKey] = []
//note: having lots of complicated work in a var
//isn't the best. I'm following the API of an :Encoder
//that didn't need to do any work to get its .value.
var value:String {
var lines = data.storage.map { key, value in
if key.isEmpty || key.contains("keyless"){
return "\(value)"
} else {
return "\(key):\(value)"
}
}
//To get a consistent order out since this isn't a
//sorted dictionary. Better for testing.
lines.sort()
return lines.joined(separator: "/")
}
}
extension _SimpleEncoder {
init() {
self.data = SimpleCoderData([:])
}
}
- SIDEBAR: discussion on Codable and value order re: OrderedDictionary
The _SimpleEncoder
will also own adding values to the data var. EVERYTHING will need to have a key in this example so we only need one function that takes a non-optional CodingKey
.
extension _SimpleEncoder {
func encodeKey(key:CodingKey) -> String {
(codingPath + [key]).map { $0.stringValue }.joined(separator: ".")
}
//to be called from containers
func encode(_ value: String, forKey key:CodingKey) {
data.storage[encodeKey(key: key)] = value
}
}
Also in _SimpleEncoder
, the converters for the basic types that the Encoding Containers will use. I’m going to follow the model of them all being called the same thing, but taking different value types in as parameters.
extension _SimpleEncoder {
//called from the containers
@inline(__always)
func convert(_ value: some BinaryFloatingPoint) -> String {
Double(value).description
}
@inline(__always)
func convert(_ value: some FixedWidthInteger) throws -> String {
guard let validatedValue = Int(exactly: value) else {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "Integer out of range."))
}
return validatedValue.description
}
}
Now for the actual conformance - time to make up some names for the encoding containers.
extension _SimpleEncoder:Encoder {
var userInfo: [CodingUserInfoKey : Any] { [:] }
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
KeyedEncodingContainer(SimpleEncoderKEC<Key>(encoder: self))
}
func unkeyedContainer() -> UnkeyedEncodingContainer {
fatalError()
//SimpleCoderUEC(encoder: self)
}
func singleValueContainer() -> SingleValueEncodingContainer {
fatalError()
//SimpleCoderSVEC(encoder: self)
}
}
The KeyedEncodingContainer
I followed the pattern from the URIEncoder
for my encoding containers, with the wrinkle that everything has to get a key. That’s not hard to envision with the KeyedContainer. We’ll get the first test case to pass by just implementing this one.
The init
struct SimpleEncoderKEC<Key: CodingKey> {
let encoder: _SimpleEncoder
}
The custom functions
extension SimpleEncoderKEC {
private func _insertValue(_ converted:String, atKey key: Key) throws {
try encoder.encode(converted, forKey: key)
}
private func _insertBinaryFloatingPoint(_ value: some BinaryFloatingPoint, atKey key: Key) throws {
try _insertValue(encoder.convert(value), atKey: key)
}
private func _insertFixedWidthInteger(_ value: some FixedWidthInteger, atKey key: Key) throws {
try _insertValue(encoder.convert(value), atKey: key)
}
}
The conformance
Full disclosure, I did not write tests for all of the func encode()
functions that I yanked from the URIEncoder
.
extension SimpleEncoderKEC:KeyedEncodingContainerProtocol {
var codingPath: [CodingKey] {
encoder.codingPath
}
mutating func encodeNil(forKey key: Key) throws {
//TODO: Decide about nils
fatalError()
}
mutating func encode(_ value: Bool, forKey key: Key) throws { try _insertValue("\(value)", atKey: key) }
mutating func encode(_ value: String, forKey key: Key) throws { try _insertValue(value, atKey: key) }
mutating func encode(_ value: Double, forKey key: Key) throws { try _insertBinaryFloatingPoint(value, atKey: key) }
mutating func encode(_ value: Float, forKey key: Key) throws { try _insertBinaryFloatingPoint(value, atKey: key) }
mutating func encode(_ value: Int, forKey key: Key) throws { try _insertFixedWidthInteger(value, atKey: key) }
mutating func encode(_ value: Int8, forKey key: Key) throws { try _insertFixedWidthInteger(value, atKey: key) }
mutating func encode(_ value: Int16, forKey key: Key) throws { try _insertFixedWidthInteger(value, atKey: key) }
mutating func encode(_ value: Int32, forKey key: Key) throws { try _insertFixedWidthInteger(value, atKey: key) }
mutating func encode(_ value: Int64, forKey key: Key) throws { try _insertFixedWidthInteger(value, atKey: key) }
mutating func encode(_ value: UInt, forKey key: Key) throws { try _insertFixedWidthInteger(value, atKey: key) }
mutating func encode(_ value: UInt8, forKey key: Key) throws { try _insertFixedWidthInteger(value, atKey: key) }
mutating func encode(_ value: UInt16, forKey key: Key) throws { try _insertFixedWidthInteger(value, atKey: key) }
mutating func encode(_ value: UInt32, forKey key: Key) throws { try _insertFixedWidthInteger(value, atKey: key) }
mutating func encode(_ value: UInt64, forKey key: Key) throws { try _insertFixedWidthInteger(value, atKey: key) }
mutating func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
//notice the reference to the existing data!
//not using push and pop style like URIEncoder.
var tmpEncoder = _SimpleEncoder(data: encoder.data)
tmpEncoder.codingPath.append(key)
try value.encode(to: tmpEncoder)
}
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key)
-> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey
{ fatalError() }
mutating func nestedUnkeyedContainer(forKey key: Key) -> any UnkeyedEncodingContainer { fatalError() }
mutating func superEncoder() -> any Encoder { fatalError() }
mutating func superEncoder(forKey key: Key) -> any Encoder { fatalError() }
}
I’m super generous with the fatal errors at this point because I’ll go back in and fix them as they fire.
And this passes the first test!
UnkeyedContainer
The test
Let’s try this test that has Arrays in it.
struct MoreItems:Codable {
let myDouble:Double
let myFloat:Float
let myArray:[InsideStruct]
}
struct InsideStruct:Codable {
let insideDouble:Double
let insideFloat:Float
//let insideArray:[TestSubStruct]
}
func testArrayExample() throws {
let encoder = SimpleCoder()
let subItem1 = InsideStruct(insideDouble: 0.234, insideFloat: 2144.421)
let subItem2 = InsideStruct(insideDouble: 5.926, insideFloat: 0.00132)
let subItem3 = InsideStruct(insideDouble: 312421.4124214, insideFloat: 421421.223)
let testItem = MoreItems(myDouble: 8921.41421, myFloat: 1182.12, myArray: [subItem1, subItem2, subItem3])
let expected = "myArray.0.insideDouble:0.234/" +
"myArray.0.insideFloat:2144.4208984375/" +
"myArray.1.insideDouble:5.926/" +
"myArray.1.insideFloat:0.0013200000394135714/" +
"myArray.2.insideDouble:312421.4124214/" +
"myArray.2.insideFloat:421421.21875/" +
"myDouble:8921.41421/" +
"myFloat:1182.1199951171875"
XCTAssertEqual(expected, try encoder.encode(testItem))
}
The init
Start off with the basic needs
struct SimpleCoderUEC {
/// The associated encoder.
let encoder: _SimpleEncoder
private(set) var count: Int = 0
}
The custom functions
extension SimpleCoderUEC {
private mutating func _appendValue(_ converted:String) throws {
try encoder.encode(converted, forKey: nextIndexedKey())
}
private mutating func _appendBinaryFloatingPoint(_ value: some BinaryFloatingPoint) throws {
try _appendValue(encoder.convert(value))
}
private mutating func _appendFixedWidthInteger(_ value: some FixedWidthInteger) throws {
try _appendValue(encoder.convert(value))
}
}
Key creation
Add what’s needed for index based keys.
extension SimpleCoderUEC {
//Require a key
private mutating func nextIndexedKey() -> CodingKey {
let nextCodingKey = IndexedCodingKey(intValue: count)!
count += 1
return nextCodingKey
}
private struct IndexedCodingKey: CodingKey {
let intValue: Int?
let stringValue: String
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = intValue.description
}
init?(stringValue: String) {
return nil
}
}
}
The conformance
Heavy on the fatal errors again.
extension SimpleCoderUEC: UnkeyedEncodingContainer {
var codingPath: [any CodingKey] { encoder.codingPath }
func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer { fatalError() }
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey>
where NestedKey: CodingKey { fatalError() }
mutating func superEncoder() -> any Encoder { fatalError() }
mutating func encodeNil() throws { fatalError() }
mutating func encode(_ value: Bool) throws { try _appendValue("\(value)") }
mutating func encode(_ value: String) throws { try _appendValue(value) }
mutating func encode(_ value: Double) throws { try _appendBinaryFloatingPoint(value) }
mutating func encode(_ value: Float) throws { try _appendBinaryFloatingPoint(value) }
mutating func encode(_ value: Int) throws { try _appendFixedWidthInteger(value) }
mutating func encode(_ value: Int8) throws { try _appendFixedWidthInteger(value) }
mutating func encode(_ value: Int16) throws { try _appendFixedWidthInteger(value) }
mutating func encode(_ value: Int32) throws { try _appendFixedWidthInteger(value) }
mutating func encode(_ value: Int64) throws { try _appendFixedWidthInteger(value) }
mutating func encode(_ value: UInt) throws { try _appendFixedWidthInteger(value) }
mutating func encode(_ value: UInt8) throws { try _appendFixedWidthInteger(value) }
mutating func encode(_ value: UInt16) throws { try _appendFixedWidthInteger(value) }
mutating func encode(_ value: UInt32) throws { try _appendFixedWidthInteger(value) }
mutating func encode(_ value: UInt64) throws { try _appendFixedWidthInteger(value) }
mutating func encode<T>(_ value: T) throws where T: Encodable {
//points to same data reference!
var tmpEncoder = _SimpleEncoder(data:encoder.data)
tmpEncoder.codingPath = encoder.codingPath
tmpEncoder.codingPath.append(nextIndexedKey())
try value.encode(to: tmpEncoder)
}
}
And that passes the test.
SingleValues
It may seem little funny to do the “simplest” case, single values, last. In this case we need to do some thinking about how to force them to have keys when they wouldn’t normally. I’m being a bit lazy and giving everything the phrase “keyless(UUID())” as a key. Using a UUID
instead of the value itself means not worrying about duplicates or if the value contains the key delimiter.
The test
func testSingleValues() async throws {
let encoder = SimpleCoder()
let toEncodeInt = Int.random(in: Int.min...Int.max)
let toEncodeText = "hello" //TODO: explore strings more
let toEncodeBool = Bool.random()
let toEncodeDouble = Double.random(in: Double.leastNonzeroMagnitude...Double.greatestFiniteMagnitude)
let toEncodeFloat = Float.random(in: Float.leastNonzeroMagnitude...Float.greatestFiniteMagnitude)
let toEncodeInt32 = Int32.random(in: Int32.min...Int32.max)
let encodedInt = try encoder.encode(toEncodeInt)
let encodedText = try encoder.encode(toEncodeText)
let encodedBool = try encoder.encode(toEncodeBool)
let encodedDouble = try encoder.encode(toEncodeDouble)
let encodedFloat = try encoder.encode(toEncodeFloat)
let encodedInt32 = try encoder.encode(toEncodeInt32)
XCTAssertEqual(toEncodeInt.description, encodedInt)
XCTAssertEqual(toEncodeText.description, encodedText)
XCTAssertEqual(toEncodeBool.description, encodedBool)
XCTAssertEqual(toEncodeDouble.description, encodedDouble)
XCTAssertEqual(Double(toEncodeFloat).description, encodedFloat)
XCTAssertEqual(toEncodeInt32.description, encodedInt32)
}
The init
struct SimpleCoderSVEC {
let encoder: _SimpleEncoder
}
The custom functions
extension SimpleCoderSVEC {
private func _setValue(_ converted:String) throws {
try encoder.encode(converted, forKey: SVECCodingKey(converted))
}
private func _setBinaryFloatingPoint(_ value: some BinaryFloatingPoint) throws {
try _setValue(encoder.convert(value))
}
private func _setFixedWidthInteger(_ value: some FixedWidthInteger) throws {
try _setValue(encoder.convert(value))
}
}
Key creation
extension SimpleCoderSVEC {
private struct SVECCodingKey: CodingKey {
let intValue: Int?
let stringValue: String
init?(intValue: Int) {
return nil
}
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init(_ forValue:String) {
self.stringValue = "keyless\(UUID())"
self.intValue = nil
}
}
}
The conformance
extension SimpleCoderSVEC: SingleValueEncodingContainer {
var codingPath: [any CodingKey] { encoder.codingPath }
func encodeNil() throws {
fatalError()
}
func encode(_ value: Bool) throws { try _setValue("\(value)") }
func encode(_ value: String) throws { try _setValue(value) }
func encode(_ value: Double) throws { try _setBinaryFloatingPoint(value) }
func encode(_ value: Float) throws { try _setBinaryFloatingPoint(value) }
func encode(_ value: Int) throws { try _setFixedWidthInteger(value) }
func encode(_ value: Int8) throws { try _setFixedWidthInteger(value) }
func encode(_ value: Int16) throws { try _setFixedWidthInteger(value) }
func encode(_ value: Int32) throws { try _setFixedWidthInteger(value) }
func encode(_ value: Int64) throws { try _setFixedWidthInteger(value) }
func encode(_ value: UInt) throws { try _setFixedWidthInteger(value) }
func encode(_ value: UInt8) throws { try _setFixedWidthInteger(value) }
func encode(_ value: UInt16) throws { try _setFixedWidthInteger(value) }
func encode(_ value: UInt32) throws { try _setFixedWidthInteger(value) }
func encode(_ value: UInt64) throws { try _setFixedWidthInteger(value) }
func encode<T>(_ value: T) throws where T: Encodable {
try value.encode(to: encoder)
}
}
And again the test passes!
Fixing the Dates
Before going on to tackle more complicated data structures I want to sort out a few value types that don’t get encoded the way I want by default. Date
, URL
and Data
. Starting with Date, this is the test to pass.
func testDate() throws {
let encoder = SimpleCoder()
let date = Date()
let dateString = date.ISO8601Format(.iso8601)
let encodedDate = try encoder.encode(date)
XCTAssertEqual(dateString, encodedDate)
}
It doesn’t out of the gate. It fails with the error:
XCTAssertEqual failed: ("2024-03-07T14:55:56Z") is not equal to ("731516156.037366")
The one below does though, which shows us that the default Encoding
for Date
’s Codable
implementation (core | FoundationEssential) is the timeIntervalSinceReferenceDate
.
func testDate() throws {
let encoder = SimpleCoder()
let date = Date()
let dateString = "\(date.timeIntervalSinceReferenceDate)"
let encodedDate = try encoder.encode(date)
XCTAssertEqual(dateString, encodedDate)
}
How to fix it? First off, the encoder has to catch that a Date
has come into play. We’ll catch it in the func encode<T>(_ value: T)
functions in all three encoding containers by adding a case statement to each.
mutating func encode<T>(_ value: T,
forKey key: Key
) throws where T: Encodable {
switch value {
case let value as Date: fatalError()
default:
//points to same data reference!
var tmpEncoder = _SimpleEncoder(data:encoder.data)
tmpEncoder.codingPath.append(key)
try value.encode(to: tmpEncoder)
}
}
If we run the test again the Date
still doesn’t get snagged by the error. It will be easier to hit inside the Keyed and Unkeyed containers first.
I got the Keyed and Unkeyed encoding containers working by adding the relevant functions, starting with a convert function like there is for Float
and Int
in the :Encoder
.
//---- In :Encoder
func convert(_ value:Date) -> String {
return value.ISO8601Format()
}
Then adding the wrapper functions to the encoding containers and updating the case statements
//---- In :KeyedEncodingContainerProtocol
private func _insertDate(_ value: Date, atKey key:Key) throws {
try _insertValue(encoder.convert(value), atKey: key)
}
mutating func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
switch value {
//...
case let value as Date: try _insertDate(value, atKey: key)
//...
}
}
//---- In :UnkeyedEncodingContainer
private mutating func _appendDate(_ value:Date) throws {
try _appendValue(encoder.convert(value))
}
mutating func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
switch value {
//...
case let value as Date: try _appendDate(value)
//...
}
}
Add Keyed and Unkeyed containers to the test:
func testDate() throws {
let encoder = SimpleCoder()
let date = Date()
//--------- SingleValue (STILL FAILS)
let dateString = date.ISO8601Format(.iso8601)
let encodedDate = try encoder.encode(date)
XCTAssertEqual(dateString, encodedDate)
//--------- Keyed (PASSES)
struct MiniWithDate:Encodable {
let date:Date = Date()
}
let miniToTest = MiniWithDate()
let structExpected = "date:\(miniToTest.date.ISO8601Format())"
let encodedStruct = try encoder.encode(miniToTest)
XCTAssertEqual(structExpected, encodedStruct)
//--------- Unkeyed (PASSES)
let dateArray = [Date(), Date(), Date(), Date()]
let arrayExpected = dateArray.enumerated().map ({ index, value in
"\(index):\(value.ISO8601Format())"
}).joined(separator: "/")
let encodedArray = try encoder.encode(dateArray)
XCTAssertEqual(arrayExpected, encodedArray)
}
The Keyed and Unkeyed containers work and the SingleValue encoding container doesn’t hit the error! What gives?
The Date
keeps prioritizing its defaults (core | FoundationEssential). I mentioned date handling in the last post, that the URIEncoder & the JSONEncoders and even the XML encoder all have to catch a stand alone Date
value in their encodeValue/wrapGeneric/box functions. It was good to see the need for that in action.
Instead of a routing function inside the _SimpleCoder:Encoder
, I’ll put an array of types that I’ve made special handlers for up in the public SimpleCoder
. If my public function gets passed a value whose type is in that array, that’s when it will go to the special handler function inside the :Encoder
. Since SimpleCoder
is a learning exercise I want to contrast right up top why one might see the basic value.encode(to: encoder)
pattern (no overrides) vs. something more like encoder.encode(for: value) -> some Output
(overrides inside).
//---- Updated
struct SimpleCoder {
let flaggedTypes:[Encodable.Type] = [Date.self]
public func encode<E:Encodable>(_ value: E) throws -> String {
let encoder = _SimpleEncoder()
if flaggedTypes.contains(where: { $0 == E.self }) {
try encoder.specialEncoder(for: value)
} else {
try value.encode(to: encoder)
}
return encoder.value
}
}
//---- Added
extension _SimpleEncoder {
func specialEncoder(for value: some Encodable) throws {
var container = singleValueContainer()
try container.encode(value)
}
}
//---- In the :SingleValueEncodingContainer
private func _setDate(_ value:Date) throws {
try _setValue(encoder.convert(value))
}
mutating func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
switch value {
//...
case let value as Date: try _setDate(value)
//...
}
}
The Date
tests now all pass.
Fixing URLs
I’m also going to need URL
s for the Lines
data and URL
s have an excessively complete default format for my needs. What do I mean? They come out looking like:
//singleValue
"relative:http://www.example.com"
//part of Struct
"url.relative:http://www.example.com"
//part of Array
"0.relative:http://www.example.com/1.relative:http://www.example.com?testQuery=42/2.relative:http://www.example.com/3.relative:http://www.example.com"
When a URL
gets created from any of the following possible init paths, it actually creates a key:value pair of the String
“relative” and the URL
’s relativeString
. See a discussion
URL(string:"http://www.example.com")
FileManager.default.homeDirectoryForCurrentUser
var components = URLComponents(); components.scheme = "http"; components.host = "www.example.com"; components.queryItems = [URLQueryItem(name: "testQuery", value: "42")]
URL(dataRepresentation: "http://www.example.com".data(using: .utf8)!, relativeTo: nil, isAbsolute: true)
Why is that? Well, lets look at the Codable implementation for URL:
extension URL : Codable {
private enum CodingKeys : Int, CodingKey {
case base
case relative
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let relative = try container.decode(String.self, forKey: .relative)
let base = try container.decodeIfPresent(URL.self, forKey: .base)
guard let url = URL(string: relative, relativeTo: base) else {
throw DecodingError.dataCorrupted(
DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "Invalid URL string.")
)
}
self = url
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.relativeString, forKey: .relative)
if let base = self.baseURL {
try container.encode(base, forKey: .base)
}
}
}
A complete URL
actually has two pieces of information. Its path and whether it’s relative to a different URL
. If one declares a URL
like:
let relativeURL = URL(fileURLWithPath: "hello.jpg", relativeTo: URL(string:"http://www.example.com"))
It does in fact produce the complete set when encoded:
"relative:hello.jpg/base.relative:http://www.example.com"
For saving URLs to the Lines
data I just want the URL
’s absoluteString
, so I’ll need another convert
function.
struct SimpleCoder {
let flaggedTypes:[Encodable.Type] = [Date.self, URL.self]
public func encode<E:Encodable>(_ value: E) throws -> String {
let encoder = _SimpleEncoder()
if flaggedTypes.contains(where: { $0 == E.self}) {
try encoder.specialEncoder(for: value)
} else {
try value.encode(to: encoder)
}
return encoder.value
}
}
//---- In :Encoder
func convert(_ value:URL) -> String {
return value.absoluteString()
}
//---- Also in all 3 encoding containers... Keyed example.
private func _insertURL(_ value: URL, atKey key:Key) throws {
try _insertValue(encoder.convert(value), atKey: key)
}
mutating func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
switch value {
//...
case let value as URL: try _insertValue(value, forKey:Key)
//...
}
}
And again the tests work!
func testURL() throws {
let encoder = SimpleCoder()
//------------------ mostly HTTP Schema
let http_url = URL(string: "http://www.example.com")!
var components = URLComponents()
components.scheme = "http"
components.host = "www.example.com"
components.queryItems = [URLQueryItem(name: "testQuery", value: "42")]
let file_url = FileManager.default.homeDirectoryForCurrentUser
let absoluteURL = URL(dataRepresentation: "http://www.example.com".data(using: .utf8)!,
relativeTo: nil, isAbsolute: true)!
let relativeURL = URL(fileURLWithPath: "hello.jpg",
relativeTo: URL(string:"http://www.example.com"))
//does not even touch my encoder
let httpUrlString = http_url.relativeString //.absoluteString
let httpEncodedURL = try encoder.encode(http_url)
XCTAssertEqual(httpUrlString, httpEncodedURL)
//--------- Keyed
struct MiniWithHttpURL:Encodable {
let url:URL = URL(string: "http://www.example.com")!
}
let httpMiniToTest = MiniWithHttpURL()
let httpStructExpected = "url:\(httpMiniToTest.url.absoluteString)"
let httpEncodedStruct = try encoder.encode(httpMiniToTest)
XCTAssertEqual(httpStructExpected, httpEncodedStruct)
//--------- Unkeyed
let urlArray = [http_url, components.url!, absoluteURL, relativeURL, file_url]
let httpArrayExpected = urlArray.enumerated().map ({ index, value in
"\(index):\(value.absoluteString)"
}).joined(separator: "/")
let encodedURLArray = try encoder.encode(urlArray)
XCTAssertEqual(httpArrayExpected, encodedURLArray)
}
Fixing Data
Failing initial test:
//FAILS
//XCTAssertEqual failed: ("Cg8U") is not equal to ("0:10/1:15/2:20")
func testData() throws {
let encoder = SimpleCoder()
let data = Data([10, 15, 20])
let dataString = data.base64EncodedString()
let encodedData = try encoder.encode(data)
XCTAssertEqual(dataString, encodedData)
}
New convert:
@inline(__always)
func convert(_ value:Data) throws -> String {
return value.base64EncodedString()
}
- Do the same song and dance in the three encoding containers done for
URL
andDate
- add
Data.self
toflaggedTypes
And again passing complete test.
func testData() throws {
let encoder = SimpleCoder()
//------------------ SingleValue
let data = Data([10, 15, 20])
let dataString = data.base64EncodedString()
let encodedData = try encoder.encode(data)
XCTAssertEqual(dataString, encodedData)
//--------- Keyed
struct MiniWithData:Encodable {
let myData:Data = Data([10, 15, 20, 127, 0])
}
let miniToTest = MiniWithData()
let structExpected = "myData:Cg8UfwA="
let encodedStruct = try encoder.encode(miniToTest)
XCTAssertEqual(structExpected, encodedStruct)
//--------- Unkeyed
let dataArray = [data, data, data, data]
let structArray = [miniToTest, miniToTest]
let arrayExpected = dataArray.enumerated().map ({ index, value in
"\(index):\(value.base64EncodedString())"
}).joined(separator: "/")
let encodedArray = try encoder.encode(dataArray)
XCTAssertEqual(arrayExpected, encodedArray)
let structArrayExpected = "0.myData:Cg8UfwA=/1.myData:Cg8UfwA="
let encodedStructArray = try encoder.encode(structArray)
XCTAssertEqual(structArrayExpected, encodedStructArray)
}
Not hard to pull off, but one can see that having to go in to every encoding container to add every little new custom override would get annoying so when I do the LineCoder
I’ll handle it a little differently.
What to do about Optionals?
There are three ways to handle Optionals in an Encoder
- add a specific marker for null values (put something in
encodeNil(forKey key: Key)
func) - silently ignore (put nothing in the
encodeNil(forKey key: Key)
func) - add
encodeIfPresent()
versions of the functions- see DateComponents’ codable conformance for an encoder using them.
- more info since I don’t go this route: https://forums.swift.org/search?q=encodeIfPresent
And one can choose differently per container. Maybe a place holder makes sense for SingleValue situations, but Keyed null values can just be ignored. That’s the route I picked for SimpleCoder. It causes some interesting trouble for enums with optional associated values in arrays, but that’s for fixing another day.
//In KeyedEncodingContainer
mutating func encodeNil(forKey key: Key) throws {
//do nothing.
//try _insertValue("NULL", atKey: key)
}
//In SingleValueEncodingContainer
func encodeNil() throws { try _setValue("NULL") }
//In UnkeyedEncodingContainer
//That's right. NOTHING in the test calls this.
//Not even the nested Optional arrays.
mutating func encodeNil() throws {
fatalError()
//try _appendValue("NULL")
}
func testBasicNil() throws {
let encoder = SimpleCoder()
//------------------ SingleValue
let optionalValue:Int? = nil
let optionalString = "NULL"
let encodedOptional = try encoder.encode(optionalValue)
XCTAssertEqual(optionalString, encodedOptional)
//--------- Keyed
struct MiniStruct:Encodable {
let noneInt:Int? = nil
let someInt:Int? = 12
}
let miniToTest = MiniStruct()
let structExpected = "someInt:12"
let encodedStruct = try encoder.encode(miniToTest)
XCTAssertEqual(structExpected, encodedStruct)
func renderArray(_ array:[Int?], prepend:String = "") -> String {
array.enumerated().map ({ index, value in
let valueString = value != nil ? "\(value!)" : "NULL"
return "\(prepend)\(index):\(valueString)"
}).joined(separator: "/")
}
//--------- Unkeyed
let array = [10, nil, 20, nil, 0]
let arrayExpected = renderArray(array)
let encodedArray = try encoder.encode(array)
XCTAssertEqual(arrayExpected, encodedArray)
let subArray:[[Int?]?] = [array, nil, array]
let subArrayExpected = subArray.enumerated().map({
if let notNil = $1 {
return renderArray(notNil, prepend: "\($0).")
} else {
return "\($0):NULL"
}
}).joined(separator: "/")
let encodedSubArray = try encoder.encode(subArray)
XCTAssertEqual(subArrayExpected, encodedSubArray)
struct MiniArrayStruct:Encodable {
let myIntArray:[Int?] = [10, nil, 20, nil, 0]
}
let miniArratStructToTest = MiniArrayStruct()
let structArray = [miniArratStructToTest, miniArratStructToTest]
let structArrayExpected = structArray.enumerated().map ({ index, value in
renderArray(value.myIntArray, prepend: "\(index).myIntArray.")
}).joined(separator: "/")
let encodedStructArray = try encoder.encode(structArray)
XCTAssertEqual(structArrayExpected, encodedStructArray)
}
Adding enums
RawRepresentable
When getting started with enum
’s it’s very useful to dive into the actual original swift-evolution proposal for what was called “swift-archival-serialization” and jump to the place where it shows what the synthesized conformance for enums would be.
public func encode(to encoder: Encoder) throws {
// Encode as a single value; no keys.
try encoder.singleValueContainer().encode(self.rawValue)
}
An enum always gets pushed through to a singleValueContainer.
And if we test this with a RawRepresentable
conforming enum with a RawValue
of String
we see it works perfectly:
func testEnum() throws {
let encoder = SimpleCoder()
enum Greeting:String,Codable {
case hello, howdy, hola, hi, hiya
}
//------------------ SingleValue
let enumValue:Greeting = .hiya
let expectedEnumString = "hiya"
let encodedEnum = try encoder.encode(enumValue)
XCTAssertEqual(expectedEnumString, encodedEnum)
}
but add an enum to a struct or array…
struct MiniWithEnum:Encodable {
let otherValue:Double = 42
let myGreeting:Greeting = .hello
let otherStringValue = "world"
}
let miniToTest = MiniWithEnum()
let structExpected = "myGreeting:\(miniToTest.myGreeting)/otherStringValue:world/otherValue:42.0"
let encodedStruct = try encoder.encode(miniToTest)
XCTAssertEqual(structExpected, encodedStruct)
//--------- Unkeyed
let enumArray:[Greeting] = [.hello, .howdy, .hiya, .hola]
let arrayExpected = enumArray.enumerated().map ({ index, value in
"\(index):\(value)"
}).joined(separator: "/")
let encodedArray = try encoder.encode(enumArray)
XCTAssertEqual(arrayExpected, encodedArray)
And we get failures like:
XCTAssertEqual failed: ("myGreeting:hello/otherStringValue:world/otherValue:42.0") is not equal to ("hello/otherStringValue:world/otherValue:42.0")
What happened? Well, even though I’m in a keyed container, my default behavior for anything my encoder doesn’t recognize is to let it do its own thing. For an enum that means it’s going through a SingleValueEncodingContainer
(See this discussion). In the data store it does have a key, but it’s myGreeting.keyless(some UUID)
which gets stripped out in our serialization step because it contains the text “keyless”.
I have some choices:
- figure out a way to intercept all enums
- write custom codable conformance for any enums to use with this encoder
- Fix the
Dictionary -> String
step
I’m going to update the Dictionary -> String
creation code because it will be easier than rerouting all enums or guaranteeing custom Codable implementations. Also my data store really relies on those unique keys so I can’t change the key-encoding function either.
var value:String {
var lines = data.storage.map { key, value in
var mKey = key
if mKey.contains("keyless") {
mKey = cleanKey(mKey)
}
if mKey.isEmpty {
return "\(value)"
} else {
return "\(mKey):\(value)"
}
}
lines.sort()
return lines.joined(separator: "/")
}
func cleanKey(_ key:String, keyDelimiter:String.Element = ".") -> String {
//potential alternative approach:
//just trim from the end until the last delimiter.
//TBD if there is a case where that would make a difference since the SVEC
//create a nested container.
let split = key.split(whereSeparator: { $0 == keyDelimiter }).filter({ !$0.contains("keyless")})
return split.joined(separator: String(keyDelimiter))
}
And now it all works.
Unspecified Enum
What happens when the enum isn’t RawRepresentable
with a RawValue
of String
or Int
?
func testPlainEnum() throws {
let encoder = SimpleCoder()
enum FruitChoice:Codable {
case strawberry, pineapple, dragon, kiwi, kumquat
}
//------------------ SingleValue
let enumFruitValue:FruitChoice = .strawberry
let expectedEnumFruitString = "strawberry"
let encodedFruitEnum = try encoder.encode(enumFruitValue)
XCTAssertEqual(expectedEnumFruitString, encodedFruitEnum)
}
Whelp. It catches one of the remaining fatalErrors()
in the SimpleEncoderKEC at
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key)
-> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey
{
fatalError()
}
replacing that with
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key)
-> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey
{
//fatalError()
print("nestedKey:\(key.stringValue)")
return encoder.container(keyedBy: NestedKey.self)
}
One gets the TestFailure, AND one gets to see the reason for it; the enum values are coming through as the keys this time.
Test Case
'-[CoderExplorerTests.CodeableStringExample testPlainEnum]' started.
nestedKey:strawberry
CoderExplorer/Tests/CoderExplorerTests/SimpleCoderTests.swift:251:
error: -[CoderExplorerTests.CodableStringExample testPlainEnum] :
XCTAssertEqual failed: ("strawberry") is not equal to ("")
If I sent this enum to the JSONEncoder, it could handle it. The value would be an empty object.
enum FruitChoice:Encodable {
case strawberry, pineapple, dragon, kiwi, kumquat
}
let JSONencoder = JSONEncoder()
let encodedData = try JSONencoder.encode(FruitChoice.strawberry)
let string = String(bytes: encodedData, encoding: .utf8) ?? ""
//string == {"strawberry":{}}
It would actually take some significant doing to catch an empty object in this particular encoder, so I’m going to show “fixing” the enum instead. These enums will no longer call the nestedContainer<NestedKey>(keyedBy:forKey:)
function because the the custom encode(to:)
function (as opposed to the synthesized one) knows the enum is flat and has no associated values.
Option 1: Stripping the key entirely
The right thing to do would be to make it RawRepresentable
as a String
as seen above, but if for some reason you can’t or want to have more control over the returned string:
enum FruitChoice:Encodable {
case strawberry, pineapple, dragon, kiwi, kumquat
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode("\(self)")
}
}
Option 2: Forcing a key:value pair
Maybe instead the enum name would be useful to have in the data for some reason. The enum can return a dictionary.
enum FruitChoice:Encodable {
case strawberry, pineapple, dragon, kiwi, kumquat
public func encode(to encoder: Encoder) throws {
//really a dictionary, but JSON terminology is leaking in
let asObject = ["FruitChoice":"\(self)"]
var container = encoder.singleValueContainer()
let container =
try container.encode(asObject)
}
}
Option 3: updating the CodingKeys (the “right way”)
Strictly more correct than the above method?
enum CodingKeys:CodingKey {
case fruitChoice
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: Self.CodingKeys.self)
try container.encode("\(self)", forKey: .fruitChoice)
}
Associated Values
Newish to Codable is “Codable Synthesis for Enums with Associated Values”
Adding a mix of associated values to FruitChoice, what one will get is position based synthesized keys for the sub-object Swift thinks the associated value is part of.
enum FruitChoice:Encodable {
case strawberry(String, Int),
case pineapple(Int),
case dragon(Double),
case kiwi(UInt8),
case kumquat(Date)
}
let JSONencoder = JSONEncoder()
let encodedData = try JSONencoder.encode(FruitChoice.strawberry("Everbearing", 5))
let string = String(bytes: encodedData, encoding: .utf8) ?? ""
//"{"strawberry":{"_0":"Everbearing","_1":5}}
One can name them inline:
enum FruitChoice:Encodable {
case strawberry(name:String, cropCount:Int),
case pineapple(yearsToFruit:Int),
case dragon(metersTall:Double),
case kiwi(seedsIn1mmSlice:UInt8),
case kumquat(whenAquired:Date)
}
Or override the whole synthesized Codable conformance:
enum FruitChoice:Encodable {
case strawberry(String, Int), pineapple(Double), kumquat(Date)
enum CodingKeys:CodingKey {
case strawberry
case pineapple
case kumquat
}
enum StrawberryCodingKeys: CodingKey {
case name
case cropCount
}
enum PineappleCodingKeys: CodingKey {
case yearsToFruit
}
enum KumquatCodingKeys: CodingKey {
case dateAquired
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .strawberry(let string, let int):
var nestedContainer = container.nestedContainer(
keyedBy: StrawberryCodingKeys.self,
forKey: .strawberry)
try nestedContainer.encode(string, forKey: .name)
try nestedContainer.encode(int, forKey: .cropCount)
case .pineapple(let double):
var nestedContainer = container.nestedContainer(
keyedBy: PineappleCodingKeys.self,
forKey: .pineapple)
try nestedContainer.encode(double, forKey: .yearsToFruit)
case .kumquat(let date):
var nestedContainer = container.nestedContainer(
keyedBy: KumquatCodingKeys.self,
forKey: .kumquat)
try nestedContainer.encode(date, forKey: .dateAquired)
}
}
}
I include that mostly to show how the nestedContainer call comes into play. Now that I’ll be needing that function again I have to fix it to work correctly.
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key)
-> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey
{
//have to reversibly push the key onto the stack.
var tmpEncoder = _SimpleEncoder(data:encoder.data)
tmpEncoder.codingPath = encoder.codingPath
tmpEncoder.codingPath.append(key)
return tmpEncoder.container(keyedBy: NestedKey.self)
}
I’ll add that to all the nestedContainer(keyType:forKey:)
calls. I’m creating a new _SimpleCoder
in at least 5 places and counting now so I’ll make a function to do it:
// in _SimpleCoder
typealias DataStore = SimpleCoderData<[String: String]>
func getEncoder(forKey key:CodingKey?, withData passedInData:DataStore) -> Self {
var tmp = _SimpleEncoder(data:passedInData)
tmp.codingPath = self.codingPath
if let key {
tmp.codingPath.append(key)
}
return tmp
}
In _SimpleCoder
the data storage never gets wiped clean so I could just use self.data
in the function without passing it through, but I’d like to make it clear at the call location that the data matters for my own future reference.
For more about nested containers see also
The tests pass!
func testAssociateValueEnum() throws {
enum FruitChoice:Encodable {
case strawberry(name:String, cropCount:Int),
case pineapple(yearsToFruit:Double),
case kumquat(dateAcquired:Date)
}
//------------------ SingleValue
let enumFruitValue:FruitChoice = .strawberry(name:"Everbearing", cropCount:6)
let expectedEnumFruitString = "strawberry.cropCount:6/strawberry.name:Everbearing"
let encodedFruitEnum = try encoder.encode(enumFruitValue)
XCTAssertEqual(expectedEnumFruitString, encodedFruitEnum)
//--------- Keyed
struct MiniWithEnum:Encodable {
let otherValue:Double = 42
let myFruit:FruitChoice = .pineapple(yearsToFruit:3.12)
let otherStringValue = "world"
}
let miniToTest = MiniWithEnum()
let structExpected = "myFruit.pineapple.yearsToFruit:3.12/otherStringValue:world/otherValue:42.0"
let encodedStruct = try encoder.encode(miniToTest)
XCTAssertEqual(structExpected, encodedStruct)
//--------- Unkeyed
let enumArray:[FruitChoice] = [.strawberry(name:"Everbearing", cropCount:6),
.pineapple(yearsToFruit:1.23343),
.kumquat(dateAcquired:try Date("2024-03-10T21:36:24Z",
strategy: .iso8601))]
// let arrayExpected = enumArray.enumerated().map ({ index, value in
// "\(index).fruitChoice:\(value)"
// }).joined(separator: "/")
let arrayExpected = "0.strawberry.cropCount:6/"+
"0.strawberry.name:Everbearing/"+
"1.pineapple.yearsToFruit:1.23343/"+
"2.kumquat.dateAquired:2024-03-10T21:36:24Z"
let encodedArray = try encoder.encode(enumArray)
XCTAssertEqual(arrayExpected, encodedArray)
}
Adding a Dictionary
Using dictionaries that have an Int
or String
as a key and some Encodable
as their value works like a charm. I’m not going to spend any time on them except to mention, yes, I wrote a test.
One can get other types to work as codable dictionary keys by conforming them to CodingKeyRepresentable
- https://github.com/apple/swift-evolution/blob/main/proposals/0320-codingkeyrepresentable.md
- https://forums.swift.org/t/pitch-allow-coding-of-non-string-int-keyed-dictionary-into-a-keyedcontainer/44593
Many of the fixed-width int types conform automatically, but not all of them do or should
A passing test
func testSomeEncodableKeyDictionary() throws {
enum FruitKey:String, Codable, CodingKeyRepresentable {
case strawberry, banana, kiwi
}
let dictionaryA:Dictionary<FruitKey,UInt32> = [
.strawberry:1,
.banana:2,
.kiwi: 3
]
struct FlowerCodingKey: CodingKey {
let stringValue: String
var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
init(_ flower:Flower) {
self.stringValue = flower.name
}
}
//TODO: Codable shouldn't be necessary but my Encoder requested it?
struct Flower:Hashable, Codable, CodingKeyRepresentable {
let name:String
init(_ name:String) {
self.name = name
}
var codingKey: any CodingKey {
FlowerCodingKey.init(self)
}
init?<T>(codingKey: T) where T : CodingKey {
self.name = codingKey.stringValue
}
}
let dictionaryB:Dictionary<Flower,Int> = [
Flower("anemone"):7,
Flower("freesia"):6,
Flower("periwinkle"):5
]
//------------------ SingleValue
let expectedA = "banana:2/kiwi:3/strawberry:1"
let expectedB = "anemone:7/freesia:6/periwinkle:5"
let encodedA = try encoder.encode(dictionaryA)
let encodedB = try encoder.encode(dictionaryB)
XCTAssertEqual(expectedA, encodedA)
XCTAssertEqual(expectedB, encodedB)
//--------- Keyed
struct MiniWithDict:Encodable {
let first:Dictionary<FruitKey,UInt32>
let second:Dictionary<Flower,Int>
}
let miniToTest = MiniWithDict(first: dictionaryA, second: dictionaryB)
let structExpected = "first.banana:2/first.kiwi:3/first.strawberry:1/second.anemone:7/second.freesia:6/second.periwinkle:5"
let encodedStruct = try encoder.encode(miniToTest)
XCTAssertEqual(structExpected, encodedStruct)
//--------- Unkeyed
let dictArray = [dictionaryB, dictionaryB]
let arrayExpected = "0.anemone:7/0.freesia:6/0.periwinkle:5/1.anemone:7/1.freesia:6/1.periwinkle:5"
let encodedArray = try encoder.encode(dictArray)
XCTAssertEqual(arrayExpected, encodedArray)
}
If one DOESN’T make the key CodingKeyRepresentable
, one ends up with a interlaced output that the decoder will have to know to stitch back together.
func testUnofficialKeyDictionary() throws {
struct MyKey:Hashable, Codable {
var id:UInt8
}
let dictionaryA:Dictionary<MyKey, Int> = [
MyKey(id: 127):1,
MyKey(id: 126):2,
MyKey(id: 125):3
]
//NOTE: This test will only intermittently pass
//because dictionary order is arbitrary.
//will be easier to test when can round trip with
//decoder.
let expectedA = "0.id:125/" +
"1:3/" +
"2.id:126/" +
"3:2/" +
"4.id:127/" +
"5:1"
let encodedA = try encoder.encode(dictionaryA)
//.sorted(by: { $0.key < $1.key }) not Encodable
XCTAssertEqual(expectedA, encodedA)
}
What’s the deal with superEncoder()?
.superEncoder()
gets called when a subclass wants to use it’s parent class’ encoder.
I did a couple of tests using my own class/subclass and one with AttributedString which uses a lot of .base64EncodedString()
data. They’re not exactly super rigorous (why I’ve only added links), but it got me nominal coverage for:
-
from Mine: KeyedEncodingContainer.superEncoder()
-
from AttributedString: KeyedEncodingContainer.superEncoder(forKey key: Key)
-
from AttributedString: UnkeyedEncodingContainer.superEncoder()
-
Note about super and keys: https://forums.swift.org/t/codable-with-references/13885/20
The remaining fatalError()s
Nesting and unkeyed containers didn’t happen in either direction in my tests. Even where I expected them to. I could write my own custom objects that call them just for testing, but I’d rather wait until I understand who/what uses them in the wild for what.
- KeyedEncodingContainerProtocol.nestedUnkeyedContainer(forKey key: Key)
- UnkeyedEncodingContainer.nestedUnkeyedContainer()
- UnkeyedEncodingContainer.nestedContainer
(keyedBy keyType: NestedKey.Type) - UnkeyedEncodingContainer.encodeNil
More research:
- https://forums.swift.org/search?q=UnkeyedEncodingContainer
- https://github.com/search?q=encoder.unkeyedContainer%28%29&type=code
- confirmation they aren’t common: https://forums.swift.org/t/codable-with-references/13885/17
A note about userInfo
Most encoder implementations I’ve seen essentially ignore the protocol defined userInfo
dictionary, but the idea behind it is to pass down Encoder settings:
- https://github.com/apple/swift-evolution/blob/1b0b339bc3072a83b5a6a529ae405a0f076c7d5d/proposals/0166-swift-archival-serialization.md?plain=1#L831
- https://forums.swift.org/t/proper-way-to-structure-containers-in-new-coders/11712/5
I’ve found at least one thing that uses it that way though:
More research:
I’m curious if it gets used with CodableWithConfiguration
implementations?
CodableWithConfiguration
I have not implemented any custom handlers for things that are CodableWithConfiguration
Summary
Encoders. Not so fiddly if one can be ruthless in vetting the input. Interminable to write if trying to be general purpose.
Next post will be the Decoder
for this SimpleCoder
which may cause it to be entirely rewritten!
This article is part of a series.
- Part 1: What if I just copy-paste from the web?
- Part 2: How do you get messages to Swift directly?
- Part 3: Okay, but how about all the way up to the View?
- Part 4: How to do some basic file handling?
- Part 5: How do custom Encoder's work?
- Part 6: This Article
- Part 7: Wait, how do I scan text again?
- Part 8: Date Parsing. Nose wrinkle.
- Part 9: What would be a very simple working Decoder?