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-sizestruct
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.
- Contiguity is promised only in the case of native Swift arrays [not bridged from Objective-C].
- 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.
struct
s 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
Swift Error "Domain=Nscocoaerrordomain Code=3840 "Invalid Value Around Character 1."
Uibutton Action Is Not Triggered After Constraint Layouts Changed
Why Is Inceptionv3 Machine Learning Model Not Recognized on My Project
Xcodebuild Commands Failed to Generate Ipa
Uisearchcontroller Searchbar Misaligns While Active During Rotation
How to Handle Error with Realm During Writing
Custom Vibration in iOS 8 - Swift
Fbsdksharephoto Not Sharing Link Alongside Photo Using Swift
Swift Delegate Protocol Cannot Prevent Retain Cycle Issue
Uimarkuptextprintformatter and MAC Catalyst
Table View's 'Cellforrow(At:)' Is 'Nil' in Unit Test
How to Initialize UIbezierpath to Draw a Circle in Swift
How to Byte Reverse Nsdata Output in Swift The Littleendian Way
How to Compare Two Dates (Nsdate) in Swift 3 and Get The Days Between Them
Convert Single File to Swift 3 in Xcode 8