Skip to content

Instantly share code, notes, and snippets.

@AvdLee
Last active March 9, 2021 13:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AvdLee/06c661861b9a9079cfaa55557f92d4cc to your computer and use it in GitHub Desktop.
Save AvdLee/06c661861b9a9079cfaa55557f92d4cc to your computer and use it in GitHub Desktop.
Mapping from Core Data to a class and reversed
import Cocoa
open class Content: Mappable {
var name: String = ""
init(name: String = "") {
self.name = name
}
func mapValues(using mapper: Mapping) throws {
let mapper = try mapper.mapper(for: self, destination: CKContent.self)
mapper.map(\.name, \.name)
}
}
/// This will be a Core Data `NSManagedObject` class.
final class AudioContent: Content, CustomDebugStringConvertible {
var artist: String = ""
var debugDescription: String {
"name: \(name) artist: \(artist)"
}
init(name: String = "", artist: String = "") {
super.init(name: name)
self.artist = artist
}
override func mapValues(using mapper: Mapping) throws {
try super.mapValues(using: mapper)
let mapper = try mapper.mapper(for: self, destination: CKAudioContent.self)
mapper.map(\.artist, \.artist)
}
}
open class CKContent {
var name: String = ""
init(name: String = "") {
self.name = name
}
}
final class CKAudioContent: CKContent, CustomDebugStringConvertible {
var artist: String = ""
var debugDescription: String {
"name: \(name) artist: \(artist)"
}
init(name: String = "", artist: String = "") {
super.init(name: name)
self.artist = artist
}
}
struct Mapper<Source, Destination>: ConcreteMapping {
enum Error: Swift.Error {
case invalidDestinationType
}
let source: Source
let destination: Destination
let isReversed: Bool
func map<Value>(_ sourceKeyPath: ReferenceWritableKeyPath<Source, Value>, _ destinationKeyPath: ReferenceWritableKeyPath<Destination, Value>) {
if isReversed {
source[keyPath: sourceKeyPath] = destination[keyPath: destinationKeyPath]
} else {
destination[keyPath: destinationKeyPath] = source[keyPath: sourceKeyPath]
}
}
}
// MARK: - Protocol definitions
protocol Mappable {
func mapValues(using mapper: Mapping) throws
}
extension Mappable {
func mapValues<Destination>(to destination: Destination, isReversed: Bool = false) throws {
let mapper = Mapper(source: self, destination: destination, isReversed: isReversed)
try mapValues(using: mapper)
}
}
// MARK: - Mappable Protocol conformance
protocol Mapping {
func mapper<Source, Destination>(for source: Source, destination: Destination.Type) throws -> Mapper<Source, Destination>
}
extension Mapping where Self: ConcreteMapping {
func mapper<Source, Destination>(for source: Source, destination: Destination.Type) throws -> Mapper<Source, Destination> {
guard let destination = self.destination as? Destination else {
throw Mapper<Source, Destination>.Error.invalidDestinationType
}
return Mapper(source: source, destination: destination, isReversed: isReversed)
}
}
protocol ConcreteMapping: Mapping {
associatedtype Source
associatedtype Destination
var source: Source { get }
var destination: Destination { get }
var isReversed: Bool { get }
init(source: Source, destination: Destination, isReversed: Bool)
}
let audioContent = AudioContent(name: "title.mp3", artist: "Ry X")
print("AudioContent: \(audioContent)")
let ckAudioContent = CKAudioContent()
print("CKAudioContent: \(ckAudioContent)")
do {
try audioContent.mapValues(to: ckAudioContent)
print("AudioContent: \(audioContent)")
print("CKAudioContent: \(ckAudioContent)")
} catch {
print("Mapping failed: \(error)")
}
let content = Content(name: "Jaap")
let ckContent = CKContent()
do {
try content.mapValues(to: ckContent)
print("AudioContent: \(content.name)")
print("CKAudioContent: \(ckContent.name)")
} catch {
print("Mapping failed: \(error)")
}
// MARK: - ReversedMappable Protocol Definition
protocol ReversedMappable {
func mapValues<Destination: Mappable>(to destination: Destination) throws
}
// MARK: - ReversedMappable Protocol conformance
extension CKContent: ReversedMappable { }
extension ReversedMappable where Self: CKContent {
func mapValues<Destination: Mappable>(to destination: Destination) throws {
try destination.mapValues(to: self, isReversed: true)
}
}
do {
ckAudioContent.name = "Another name"
ckAudioContent.artist = "Another artist"
try ckAudioContent.mapValues(to: audioContent)
print("AudioContent: \(audioContent)")
print("CKAudioContent: \(ckAudioContent)")
} catch {
print("Mapping failed: \(error)")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment