Hello, community!
I'm using some generic code to wrap my data model, like:
struct ItemEnvelope<Element> {
let items: [Element]
}
Then I want to make it Codable when the Element is Codable and adding a CodingKeys:
extension ItemEnvelope: Codable where Element: Codable {
enum CodingKeys: String, CodingKey {
case items = "list"
}
}
When using this generic on different Element, like:
let fooEnvelope = ItemEnvelope(items: [Foo()])
let barEnvelope = ItemEnvelope(items: [Bar()])
let encoder = JSONEncoder()
let encodedHello = try! encoder.encode(fooEnvelope)
let encodedHi = try! encoder.encode(barEnvelope)
A log appear on the console:
objc[41865]: Class _TtGCs27_KeyedEncodingContainerBaseGOE13PlaygroundiOSVS0_12ItemEnvelope10CodingKeys__ is implemented in both /Users/kukushi/Library/Developer/CoreSimulator/Devices/1BC741CF-7384-4E6D-B0B8-AEFB79401A55/data/Containers/Bundle/Application/6F18FDD6-2A26-48B0-AB5A-D81DCDF97307/PlaygroundiOS.app/Frameworks/libswiftCore.dylib (0x102cd02e0) and /Users/kukushi/Library/Developer/CoreSimulator/Devices/1BC741CF-7384-4E6D-B0B8-AEFB79401A55/data/Containers/Bundle/Application/6F18FDD6-2A26-48B0-AB5A-D81DCDF97307/PlaygroundiOS.app/Frameworks/libswiftCore.dylib (0x102cd1c78). One of the two will be used. Which one is undefined.
objc[41865]: Class _TtGCs26_KeyedEncodingContainerBoxGV10FoundationP12_0x10317007827_JSONKeyedEncodingContainerGOE13PlaygroundiOSVS2_12ItemEnvelope10CodingKeys___ is implemented in both /Users/kukushi/Library/Developer/CoreSimulator/Devices/1BC741CF-7384-4E6D-B0B8-AEFB79401A55/data/Containers/Bundle/Application/6F18FDD6-2A26-48B0-AB5A-D81DCDF97307/PlaygroundiOS.app/Frameworks/libswiftCore.dylib (0x102cd0038) and /Users/kukushi/Library/Developer/CoreSimulator/Devices/1BC741CF-7384-4E6D-B0B8-AEFB79401A55/data/Containers/Bundle/Application/6F18FDD6-2A26-48B0-AB5A-D81DCDF97307/PlaygroundiOS.app/Frameworks/libswiftCore.dylib (0x102cd19d0). One of the two will be used. Which one is undefined.
It seems that Swift has been defining a separate CodingKeys for every concrete ItemEnvelope but don't know which one to pick?
It's not that the CodingKeys has been redefined multiple times in source (since this would lead to compilation errors elsewhere) — it's that the actual KeyedEncodingContainerBase and KeyedEncodingContainerBox classes have been instantiated multiple times for the same CodingKeys in a conflicting way:
$ swift-demangle _TtGCs27_KeyedEncodingContainerBaseGOE13PlaygroundiOSVS0_12ItemEnvelope10CodingKeys__
_TtGCs27_KeyedEncodingContainerBaseGOE13PlaygroundiOSVS0_12ItemEnvelope10CodingKeys__ ---> Swift._KeyedEncodingContainerBase<(extension in PlaygroundiOS):PlaygroundiOS.ItemEnvelope.CodingKeys>
$ swift-demangle _TtGCs26_KeyedEncodingContainerBoxGV10FoundationP12_0x10317007827_JSONKeyedEncodingContainerGOE13PlaygroundiOSVS2_12ItemEnvelope10CodingKeys___
_TtGCs26_KeyedEncodingContainerBoxGV10FoundationP12_0x10317007827_JSONKeyedEncodingContainerGOE13PlaygroundiOSVS2_12ItemEnvelope10CodingKeys___ ---> Swift._KeyedEncodingContainerBox<Foundation.(_JSONKeyedEncodingContainer in _0x103170078)<(extension in PlaygroundiOS):PlaygroundiOS.ItemEnvelope.CodingKeys>>
Actually, a refinement to my statement above upon re-reading your assertion more closely — you are correct that different instantiations of the generic ItemEnvelope type should each get their own CodingKeys type. (The repetition is not happening in source, but in the actual type instantiation.)
@jrose can further correct me if I'm wrong, but what appears to be happening here is that due to the fact that the CodingKeys type you declare is implicitly internal (and not private or fileprivate, which have different name mangling rules), these differing instantiations actually get the same name. [This is not an issue AFAIK, since the type is an enum (i.e. not dynamic), and all instantiations have the same layout.] When the keyed container classes are instantiated themselves, however, they end up colliding with the same names, hence the error. [This is an issue because they are classes, specifically.]
The following expansion of your code reproduces the issue trivially for me:
import Foundation
struct ItemEnvelope<Element> { let items: [Element] }
extension ItemEnvelope: Codable where Element: Codable {
enum CodingKeys : String, CodingKey { case items = "list" }
}
struct Foo : Codable {}
struct Bar : Codable {}
let fooEnvelope = ItemEnvelope(items: [Foo()])
let barEnvelope = ItemEnvelope(items: [Bar()])
let encoder = JSONEncoder()
let encodedHello = try! encoder.encode(fooEnvelope)
let encodedHi = try! encoder.encode(barEnvelope)
If you comment out either encodedHello or encodedHi, the issue goes away of course, as the instantiation of the keyed container for the repeated type goes away. If you declare CodingKeys to be private or fileprivate, though, the issue similarly goes away.
I think this is surprising, and I would expect the instantiation of the keyed container types to be distinguished automatically on your behalf.