How to Mirror The Design of The Codable/Codablekeys Protocols

How can you mirror the design of the Codable/CodableKeys protocols?

You can do this easily using Swift's CaseIterable protocol.

protocol CommandId: CaseIterable {
func handle()
}

protocol CommandHandler {
associatedtype CommandIds: CommandId, RawRepresentable
}

class HandlerA: CommandHandler {
enum CommandIds: String, CommandId {
case commandA1
case commandA2

func handle() {
print("\(rawValue) is handled")
}
}
}

class HandlerB: CommandHandler {
enum CommandIds: String, CommandId {
case commandB1
case commandB2
case commandB3

func handle() {
print("\(rawValue) is handled")
}
}
}

func processHandler<T: CommandHandler>(_ handler: T) {
// Logic to iterate over CommandIds. <-- This is where I get stumped
T.CommandIds.allCases.forEach({ $0.handle() })
}

let handlerA = HandlerA()
processHandler(handlerA)

Is it possible to get the containing type from an instance of a nested type?

I figured out how to achieve what I'm after. In short, I now have solution approach #2 working in a purely-Swift way.

Here's the link to the answer on my other question.

Implementing Codable and NSManagedObject simultaneously in Swift

In the end, it sounds like there is no "good" solution when migrating from an API dynamic app without caching to a cached app.

I decided to just bite the bullet and try the method in this Question: How to use swift 4 Codable in Core Data?

EDIT:

I couldn't figure out how to make that work so I used the following solution:

import Foundation
import CoreData

/*
SomeItemData vs SomeItem:
The object with 'Data' appended to the name will always be the codable struct. The other will be the NSManagedObject class.
*/

struct OrderData: Codable, CodingKeyed, PropertyLoopable
{
typealias CodingKeys = CodableKeys.OrderData

let writer: String,
userID: String,
orderType: String,
shipping: ShippingAddressData
var items: [OrderedProductData]
let totals: PaymentTotalData,
discount: Float

init(json:[String:Any])
{
writer = json[CodingKeys.writer.rawValue] as! String
userID = json[CodingKeys.userID.rawValue] as! String
orderType = json[CodingKeys.orderType.rawValue] as! String
shipping = json[CodingKeys.shipping.rawValue] as! ShippingAddressData
items = json[CodingKeys.items.rawValue] as! [OrderedProductData]
totals = json[CodingKeys.totals.rawValue] as! PaymentTotalData
discount = json[CodingKeys.discount.rawValue] as! Float
}
}

extension Order: PropertyLoopable //this is the NSManagedObject. PropertyLoopable has a default implementation that uses Mirror to convert all the properties into a dictionary I can iterate through, which I can then pass directly to the JSON constructor above
{
convenience init(from codableObject: OrderData)
{
self.init(context: PersistenceManager.shared.context)

writer = codableObject.writer
userID = codableObject.userID
orderType = codableObject.orderType
shipping = ShippingAddress(from: codableObject.shipping)
items = []
for item in codableObject.items
{
self.addToItems(OrderedProduct(from: item))
}
totals = PaymentTotal(from: codableObject.totals)
discount = codableObject.discount
}
}

When exactly do I need indirect with writing recursive enums?

I think part of the confusion stems from this assumption:

I thought arrays and tuples have the same memory layout, and that is why you can convert arrays to tuples using withUnsafeBytes and then binding the memory...

Arrays and tuples don't have the same memory layout:

  • Array<T> is a fixed-size struct with a pointer to a buffer which holds the array elements contiguously* in memory
    • Contiguity is promised only in the case of native Swift arrays [not bridged from Objective-C]. NSArray instances do not guarantee that their underlying storage is contiguous, but in the end this does not have an effect on the code below.
  • Tuples are fixed-size buffers of elements held contiguously in memory

The key thing is that the size of an Array<T> does not change with the number of elements held (its size is simply the size of a pointer to the buffer), while a tuple does. The tuple is more equivalent to the buffer the array holds, and not the array itself.

Array<T>.withUnsafeBytes calls Array<T>.withUnsafeBufferPointer, which returns the pointer to the buffer, not to the array itself. *(In the case of a non-contiguous bridged NSArray, _ArrayBuffer.withUnsafeBufferPointer has to create a temporary contiguous copy of its contents in order to return a valid buffer pointer to you.)


When laying out memory for types, the compiler needs to know how large the type is. Given the above, an Array<Foo> is statically known to be fixed in size: the size of one pointer (to a buffer elsewhere in memory).

Given

enum Foo {
case one((Foo, Foo))
}

in order to lay out the size of Foo, you need to figure out the maximum size of all of its cases. It has only the single case, so it would be the size of that case.

Figuring out the size of one requires figuring out the size of its associated value, and the size of a tuple of elements is the sum of the size of the elements themselves (taking into account padding and alignment, but we don't really care about that here).

Thus, the size of Foo is the size of one, which is the size of (Foo, Foo) laid out in memory. So, what is the size of (Foo, Foo)? Well, it's the size of Foo + the size of Foo... each of which is the size of Foo + the size of Foo... each of which is the size of Foo + the size of Foo...

Where Array<Foo> had a way out (Array<T> is the same size regardless of T), we're stuck in an infinite loop with no base case.

indirect is the keyword required to break out of the recursion and give this infinite reference a base case. It inserts an implicit pointer by making a given case the fixed size of a pointer, regardless of what it contains or points to. That makes the size of one fixed, which allows Foo to have a fixed size.

indirect is less about Foo referring to Foo in any way, and more about allowing an enum case to potentially contain itself indirectly (because direct containment would lead to an infinite loop).


As an aside, this is also why a struct cannot contain a direct instance of itself:

struct Foo {
let foo: Foo // error: Value type 'Foo' cannot have a stored property that recursively contains it
}

would lead to infinite recursion, while

struct Foo {
let foo: UnsafePointer<Foo>
}

is fine.

structs don't support the indirect keyword (at least in a struct, you have more direct control over storage and layout), but there have been pitches for adding support for this on the Swift forums.

How to exclude properties from Swift Codable?

The list of keys to encode/decode is controlled by a type called CodingKeys (note the s at the end). The compiler can synthesize this for you but can always override that.

Let's say you want to exclude the property nickname from both encoding and decoding:

struct Person: Codable {
var firstName: String
var lastName: String
var nickname: String?

private enum CodingKeys: String, CodingKey {
case firstName, lastName
}
}

If you want it to be asymmetric (i.e. encode but not decode or vice versa), you have to provide your own implementations of encode(with encoder: ) and init(from decoder: ):

struct Person: Codable {
var firstName: String
var lastName: String

// Since fullName is a computed property, it's excluded by default
var fullName: String {
return firstName + " " + lastName
}

private enum CodingKeys: String, CodingKey {
case firstName, lastName, fullName
}

// We don't want to decode `fullName` from the JSON
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
firstName = try container.decode(String.self, forKey: .firstName)
lastName = try container.decode(String.self, forKey: .lastName)
}

// But we want to store `fullName` in the JSON anyhow
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstName, forKey: .firstName)
try container.encode(lastName, forKey: .lastName)
try container.encode(fullName, forKey: .fullName)
}
}


Related Topics



Leave a reply



Submit