Defining swift extensions with generic functions for realm and object mapper - swift

I am working with Realm and ObjectMapper and would like to create some extensions to make my life easier when backing up some data to JSON. I have the following extensions defined:
extension Mappable where Self:Object {
func getCompleteJSONDictionary() throws -> [String: Any]? {
var returnValue: [String: Any]?
if self.isManaged, let realm = self.realm, !realm.isInWriteTransaction {
try realm.write {
var data = self.toJSON()
data["id"] = self.getPrimaryKeyValue()
returnValue = data
}
} else {
var data = self.toJSON()
data["id"] = self.getPrimaryKeyValue()
returnValue = data
}
return returnValue
}
}
extension Results where Element:Object, Element:Mappable {
func getAllCompleteJSONDictionaries() throws -> Array<[String:Any]>? {
var array: Array<[String:Any]> = Array()
for element in self {
if let dictionary = try? element.getCompleteJSONDictionary(), let data = dictionary {
array.append(data)
}
}
if array.count > 0 {
return array
} else {
return nil
}
}
}
extension Realm {
func getJSONBackupData<T>(forTypes types: [T.Type]) throws -> [String: Any] where T:Object, T:Mappable {
var data: [String: Any] = [:]
try self.write {
for type in types {
let entities = self.objects(type)
if let entityJsonData = try entities.getAllCompleteJSONDictionaries() {
data[String(describing: type)] = entityJsonData
}
}
}
return data
}
}
The first two extensions work fine, but as soon as I try to use the last, the country class conforms to both Object and Mappable:
var finalData = realm.getJSONBackupData(forTypes:[Country.self])
I get an error that T cannot be inferred. I still get myself hopelessly muddled when it comes to generics in Swift so am guessing i am just not understanding the problem correctly. Is there an easy fix here or have I asked too much of the compiler?

The problem is that the Realm Objects does not conform to Mappable Protocol. So the Country Object is not Mappable and thus the complier is saying that Type Country.self cannot be inferred as Object and Mappable.

Related

Swift - Return type of struct parameters without default values

I'm very new in Swift so i might be missing some basics.
I have struct:
struct MyStruct {
var a: Int
var b: String
var c: Bool
init() {
a: Int = 1,
b: String? = "",
c: Bool? = false
}
}
and function, that should iterate through given struct properties and return their types in json:
func structProps(){
let elm = MyStruct()
let mirror = Mirror(reflecting: elm)
var exampleDict: [String: String] = [:]
for child in mirror.children {
exampleDict[child.label!] = String(describing:type(of: child.value)) as String
}
if let theJSONData = try? JSONSerialization.data(
withJSONObject: exampleDict,
options: []) {
let theJSONText = String(data: theJSONData, encoding: .ascii)
}
}
it kinda return what i need:
JSON string = {"a":"Int","b":"String","c":"Bool"}
Because i'm having a lot of structs and i want to export json from all of them, i'm wondering if there is a way to have generic initializer. Without passing default values.
It means without
init() {
a: Int = 1,
b: String? = "",
c: Bool? = false
}
If I understand correctly , you can create a base protocol and add as an extension to your structures like
protocol BaseFunction {
func getElements() -> [String : String]
func getDict() -> String
}
extension BaseFunction {
func getElements() -> [String : String] {
let mirror = Mirror(reflecting: self)
let propertiesRemoveNil = mirror.children.filter({!(($0.value as AnyObject) is NSNull)})
let properties = propertiesRemoveNil.compactMap({$0.label})
var types = [String]()
_ = mirror.children.forEach({
types.append(String(describing:type(of: $0.value)))
})
return Dictionary(uniqueKeysWithValues: zip(properties, types))
}
func getDict() -> String{
if let theJSONData = try? JSONSerialization.data(
withJSONObject: getElements(),
options: []) {
let theJSONText = String(data: theJSONData, encoding: .ascii)
return theJSONText ?? ""
}
return ""
}
}
And usage :
func structProps(){
let elm = MyStruct()
print(elm.getDict())
}
OUTPUT :
{"a":"Int","b":"String","c":"Bool"}
I was going to write a comment about using a protocol but I thought it would be easier to understand as an answer with some code.
To make the usage more generic so you don't need specific code for each type of struct you should use a protocol. Instead of having an init that might clash with already existing init in the struct I prefer a static method that returns a new object, also known as a factory method
protocol PropertyExtract {
static func createEmpty() -> PropertyExtract
}
Then we can make use of the default init for the struct or any supplied to create an object with some initial values by letting the struct conform to the protocol in an extension
extension MyStruct: PropertyExtract {
static func createEmpty() -> PropertyExtract {
MyStruct(a: 0, b: "", c: false)
}
}
And instead of hardcoding or passing a specific type of object to the encoding function we pass the type of it
func structProps(for structType: PropertyExtract.Type)
and use the protocol method to get an instance of the type
let object = structType.createEmpty()
The whole function (with some additional changes)
func structProps(for structType: PropertyExtract.Type) -> String? {
let object = structType.createEmpty()
let mirror = Mirror(reflecting: object)
let exampleDict = mirror.children.reduce(into: [String:String]()) {
guard let label = $1.label else { return }
$0[label] = String(describing:type(of: $1.value))
}
if let data = try? JSONEncoder().encode(exampleDict) {
return String(data: data, encoding: .utf8)
}
return nil
}
And this is then called with the type of the struct
let jsonString = structProps(for: MyStruct.self)
According to Creating a Swift Runtime Library, there is a way to access meta data types without initializer.
Solution for what i was asking for is possible with Runtime library.
Mirror(reflecting:) expects an instance of a type and not a type itself, ref.
One idea is to have a generic function that works with all types conforming to a protocol that provides an empty init. Something like:
protocol EmptyInitializable {
init()
}
struct StructOne {
let a: Bool
let b: String
}
struct StructTwo {
let c: Int
let d: Float
}
extension StructOne: EmptyInitializable {
init() {
a = false
b = ""
}
}
extension StructTwo: EmptyInitializable {
init() {
c = 1
d = 1.0
}
}
func print(subject: EmptyInitializable) -> String? {
let dictionary = Dictionary(uniqueKeysWithValues:
Mirror(reflecting: subject).children.map {
($0.label!, String(describing: type(of: $0.value)))
}
)
return (try? JSONSerialization.data(withJSONObject: dictionary)).flatMap {
String(data: $0, encoding: .utf8)
}
}
print(subject: StructOne()) // "{"a":"Bool","b":"String"}"
print(subject: StructTwo()) // "{"d":"Float","c":"Int"}"
You still have to implement the empty init and assign some values and I'm afraid there's no way currently to avoid that.

How can I get all writable keypaths from a Swift struct programatically?

I'm trying to convert a `struct1 to Realm objects right now.
Realm object has same keypath with original struct.
so If I can get all writable keypaths from original struct, it is possible to convert with general method.
public protocol KeyPathListable {
var allKeyPaths:[WritableKeyPath<Self, Any>] { get }
}
extension KeyPathListable {
private subscript(checkedMirrorDescendant key: String) -> Any {
return Mirror(reflecting: self).descendant(key)!
}
var allKeyPaths:[WritableKeyPath<Self, Any>] {
var membersTokeyPaths = [WritableKeyPath<Self,Any>]()
let mirror = Mirror(reflecting: self)
for case (let key?, _) in mirror.children {
if let keyPath = \Self.[checkedMirrorDescendant: key] as? WritableKeyPath<Self, Any> {
membersTokeyPaths.append(keyPath)
}
}
return membersTokeyPaths
}
}
Just found the code snippet above but it returns KeyPath(not WritableKeyPath). I tried to typecast in this case, but it returns nil. Maybe mirror function has problem. Is there any solution for that?

Dynamic protocol conformance in Swift

Hi I am struggle to solve the problem dynamic protocol conformance in swift language. Please see code.
Protocol:
protocol Object {
init(by object: [String: Any])
}
Custom structs with protocol object conformance:
struct Tree: Object {
let treeName: String
init(by object: [String: Any]) {
self.treeName = object["tree"] as? String ?? "Notree"
}
}
struct Plant: Object {
let plantName: String
init(by object: [String : Any]) {
self.plantName = object["tree"] as? String ?? ""
}
}
The above code just fine until the object is [String: Any]. I can't use [[String: Any]] like below.
let coconut = ["tree":"Coconut"] // => This fine
let allTrees = [["tree":"Apple"],["tree":"Orange"],["tree":"Jakfruit"]] //=> Here is the problem
let aTree = Tree(by: coconut)
let bTree = Tree(by: ["data":allTrees])
let cTree = Plant(by: ["data":allTrees])
I can't use array of objects. So, I used to store objects in to key "data". Now I used extension: Array confirm protocol object.
extension Array: Object where Element == Object{
init(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.map({ (object) -> Object in
// return Plant.init(by: object) // => Works, But I need dynamic confirmance
// return Tree.init(by: object) // => Works, But I need dynamic confirmance
return Object.init(by: object) //=> How can I do?
})
}else{
self = []
}
}
}
The return Object shows error Protocol type 'Object' cannot be instantiated. I tried lot to solve but not able.
Can someone suggest better idea or solution for this problem? Thank you in advance...
First, you should not use the constraint == Object. You want to say that not only [Object] is an Object, but also [Plant] and [Tree] are Objects too, right? For that, you should use the : Object constraint. Second, you can use Element.init to initialise a new Element of the array. Because of the constraint Element : Object, we know that a init(by:) initialiser exists:
extension Array: Object where Element: Object{
init(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.map({ (object) in
return Element.init(by: object)
})
}else{
self = []
}
}
}
Usage:
let trees = [Tree](by: ["data": allTrees])
Here's what I think a more Swifty version of your code, making use of failable initialisers - initialisers that return nil when they fail to initialise the object:
protocol Object {
init?(by object: [String: Any])
}
struct Tree: Object {
let treeName: String
init?(by object: [String: Any]) {
if let treeName = object["tree"] as? String {
self.treeName = treeName
} else {
return nil
}
}
}
struct Plant: Object {
let plantName: String
init?(by object: [String : Any]) {
if let plantName = object["tree"] as? String {
self.plantName = plantName
} else {
return nil
}
}
}
extension Array: Object where Element: Object{
init?(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.compactMap(Element.init)
}else{
return nil
}
}
}

Swift Get all the properties in generic class

I'm trying to get all the members of a generic class T, I can get the properties based on a specific class.
But, how I can do it using Mirror ?
let mirrored_object = Mirror(reflecting: user)
for (index, attr) in mirrored_object.children.enumerated() {
if let propertyName = attr.label as String! {
print("Attr \(index): \(propertyName) = \(attr.value)")
}
}
I added this as extension
extension NSObject {
public func GetAsJson() -> [[String:Any?]] {
var result:[[String: Any?]] = [[String: Any?]]()
for item in self {
var dict: [String: Any?] = [:]
for property in Mirror(reflecting: self).children {
dict[property.label!] = property.value
}
result.append(dict)
}
return result
}
}

Get Core Data Entity relatives with a generic function

I'm designing a data manager for my Core Data model and I'd like to create a generic function to fetch relatives of a class.
I’ve created a protocol allowing to build managers for each data type. In this protocol I already defined two associated types T and K and several simple functions. Now I’m stuck with a class relatives fetching method — I need to indicate somehow that T has K relatives. I’ve tried in vain to create some protocol indicating this relationship thru mutual properties, so both classes could conform to this protocol. Any idea, is it even possible?
import Foundation
import CoreData
protocol DataManager {
associatedtype T: NSManagedObject, NSFetchRequestResult
associatedtype K: NSManagedObject, NSFetchRequestResult // Relative
static var sharedInstance: Self { get }
static func getAll(sorted: [NSSortDescriptor]?, context: NSManagedObjectContext) -> [T]?
static func insert(item: T)
static func update(item: T)
static func clean()
static func deleteById(id: String)
// Relatives
static func getRelatives(by: T) -> [K]?
static func get(byRelative: K) -> [T]?
}
extension DataManager {
static func getAll(sorted: [NSSortDescriptor]?, context: NSManagedObjectContext) -> [T]? {
guard let fetchRequest: NSFetchRequest<T> = T.fetchRequest() as? NSFetchRequest<T> else { return nil }
fetchRequest.sortDescriptors = sorted
var results: [T]? = nil
do {
results = try context.fetch(fetchRequest)
} catch {
assert(false, error.localizedDescription)
} //TODO: Handle Errors
return results
}
}
protocol Identifiable {
typealias Identity = String
var id: Identity? { get }
}
extension DataManager where Self.T: Identifiable {
static func get(by id: T.Identity, context: NSManagedObjectContext) -> T? {
guard let fetchRequest: NSFetchRequest<T> = T.fetchRequest() as? NSFetchRequest<T> else { return nil }
fetchRequest.predicate = NSPredicate(format: "%K == %#", "id", id)
var rawResults: [T]? = nil
do {
rawResults = try context.fetch(fetchRequest)
} catch {
assert(false, error.localizedDescription)
} //TODO: Handle Errors
if let result = rawResults?.first {
return result }
else { return nil }
}
}
Well, I've created one solution.
We can identify all relations with a particular class:
let relationships = T.entity().relationships(forDestination: K.entity())
It allows us to find all IDs of an item for each relationship (we can have many relationships for the same relative Entity):
let relativesIDs = item.objectIDs(forRelationshipNamed: relationship.name)
So, we can use these IDs to fetch records from another class.
static func getRelatives(of item: T, context:NSManagedObjectContext) -> [K]? {
guard let fetchRequest: NSFetchRequest<K> = K.fetchRequest() as? NSFetchRequest<K> else { return nil }
fetchRequest.fetchBatchSize = 100
var results: [K]? = nil
var resultSet: Set<K> = [] // doesn't allow duplicates
let relationships = T.entity().relationships(forDestination: K.entity())
for relationship in relationships {
let relativesIDs = item.objectIDs(forRelationshipNamed: relationship.name)
let predicate = NSPredicate(format: "self IN %#", relativesIDs)
fetchRequest.predicate = predicate
var batchResults: [K] = []
do {
batchResults = try context.fetch(fetchRequest)
} catch {
assert(false, error.localizedDescription)
} //TODO: Handle Errors
if batchResults.count > 0 { resultSet = resultSet.union(Set(batchResults)) }
}
if resultSet.count > 0 { results = Array(resultSet) }
return results
}
I'm not sure that this is the most elegant solution, but it works :-)