Last active
March 9, 2021 13:30
-
-
Save AvdLee/06c661861b9a9079cfaa55557f92d4cc to your computer and use it in GitHub Desktop.
Mapping from Core Data to a class and reversed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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