Populating objects from cloud records (or another external source) using a generic function - swift

I'm building a generic API for my Swift applications. I use CoreData for local storage and CloudKit for cloud synchronization.
in order to be able to work with my data objects in generic functions I have organized them as follows (brief summary):
Objects that go in the CoreData Database are NSManagedObject instances that conform to a protocol called ManagedObjectProtocol, which enables conversion to DataObject instances
NSManagedObjects that need to be cloud synced conform to a protocol called CloudObject which allows populating objects from records and vice-versa
Objects I use in the graphic layer of my apps are NSObject classes that conform to the DataObject protocol which allows for conversion to NSManagedObject instances
an object of a specific class. What I would like this code to look like is this:
for record in records {
let context = self.persistentContainer.newBackgroundContext()
//classForEntityName is a function in a custom extension that returns an NSManagedObject for the entityName provided.
//I assume here that recordType == entityName
if let managed = self.persistentContainer.classForEntityName(record!.recordType) {
if let cloud = managed as? CloudObject {
cloud.populateManagedObject(from: record!, in: context)
}
}
}
However, this gives me several errors:
Protocol 'CloudObject' can only be used as a generic constraint because it has Self or associated type requirements
Member 'populateManagedObject' cannot be used on value of protocol type 'CloudObject'; use a generic constraint instead
The CloudObject protocol looks as follows:
protocol CloudObject {
associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol
var recordID: CKRecordID? { get }
var recordType: String { get }
func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudManagedObject>
func populateCKRecord() -> CKRecord
}
Somehow I need to find a way that allows me to get the specific class conforming to CloudObject based on the recordType I receive. How would I Best go about this?
Any help would be much appreciated!

As the data formats of CoreData and CloudKit are not related you need a way to efficiently identify CoreData objects from a CloudKit record and vice versa.
My suggestion is to use the same name for CloudKit record type and CoreData entity and to use a custom record name (string) with format <Entity>.<identifer>. Entity is the record type / class name and identifier is a CoreData attribute with unique values. For example if there are two entities named Person and Event the record name is "Person.JohnDoe" or "Event.E71F87E3-E381-409E-9732-7E670D2DC11C". If there are CoreData relationships add more dot separated components to identify those
For convenience you could use a helper enum Entity to create the appropriate entity from a record
enum Entity : String {
case person = "Person"
case event = "Event"
init?(record : CKRecord) {
let components = record.recordID.recordName.components(separatedBy: ".")
self.init(rawValue: components.first!)
}
}
and an extension of CKRecord to create a record for specific record type from a Entity (in my example CloudManager is a singleton to manage the CloudKit stuff e.g. the zones)
extension CKRecord {
convenience init(entity : Entity) {
self.init(recordType: entity.rawValue, zoneID: CloudManager.shared.zoneID)
}
convenience init(entity : Entity, recordID : CKRecordID) {
self.init(recordType: entity.rawValue, recordID: recordID)
}
}
When you receive Cloud records extract the entity and the unique identifier. Then try to fetch the corresponding CoreData object. If the object exists update it, if not create a new one. On the other hand create a new record from a CoreData object with the unique record name. Your CloudObject protocol widely fits this pattern, the associated type is not needed (by the way deleting it gets rid of the error) but add a requirement recordName
var recordName : String { get set }
and an extension to get the recordID from the record name
extension CloudObject where Self : NSManagedObject {
var recordID : CKRecordID {
return CKRecordID(recordName: self.recordName, zoneID: CloudManager.shared.zoneID)
}
}

Swift is not Java, Swift is like C++, associatedType is a way of writing a generic protocol, and generics in Swift means C++ template.
In Java, ArrayList<String> is the same type as ArrayList<Integer>!!
In Swift (and C++) , Array<String> is NOT the same type as Array<Int>
So, you can't take an array of Arrays for example, you MUST make it an array of Array<SpecificType>
What did Apple do to make you able to make a "type-erased" array for example?
They made Array<T> extend Array<Any>.
If you want to immitate this in your code, how?
protocol CloudObject {
// Omitted the associatedtype (like you already done as in the replies)
//associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol
var recordID: CKRecordID? { get }
var recordType: String { get }
func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<NSManagedObject & ManagedObjectProtocol>
func populateCKRecord() -> CKRecord
}
Then make the "generic protocol", this would be useful in safely and performance programming when the resolving of protocol is known at compile time
protocol CloudObjectGeneric: CloudObject {
// Generify it
associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol
// You don't need to redefine those, those are not changed in generic form
//var recordID: CKRecordID? { get }
//var recordType: String { get }
//func populateCKRecord() -> CKRecord
// You need a new function, which is the generic one
func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudObject>
}
Then make the generic protocol conform to the non-generic one, not to need writing 2 populateManagedObject functions in each implementation
extension CloudObjectGeneric {
// Function like this if the generic was a parameter, would be
// straightforward, just pass it with a cast to indicate you
// are NOT CALLING THE SAME FUNCTION, you are calling it from
// the generic one, but here the generic is in the return, so
// you will need a cast in the result.
func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudObject> {
let generic = populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext)
return generic as! Promise<CloudObject> // In Promises I think this
// will NOT work, and you need .map({$0 as! CloudObject})
}
}

Related

Storing a generic subclass in Realm

I have been trying to abstract a little bit my Realm models, so I wanted to create a superclass model with a generic type. Then, create subclasses for the specific type.
I know Realm does not support storing a generic type. However, my question is:
Is it possible to store a specific defined subclass?
So the idea is to have a superclass:
class SuperClass<T: RealmCollectionValue> : Object {
#objc dynamic var pk: String = ""
let listProp = List<T>()
convenience init(pk: String){
self.init()
self.pk = pk
}
override static func primaryKey() -> String? {
return "pk"
}
}
Then implement a specific subclass:
class StringSubClass : SuperClass<String> {}
Is then the possibility to save in Realm instances of StringSubClass (as in fact, it is not a generic)?
From what I have found in this already answered question (which stands it is possible with an accepted answer): Store concrete generic subclass in Realm
You can specify the Realm to ignore the SuperClass by defining it to only handle the StringSubClass.
However, when trying it in Realm 3.13.1 with:
let realm = try! Realm(configuration: Realm.Configuration(objectTypes: [StringSubClass.self]))
try! realm.write {
realm.add(StringSubClass(pk: "mypk"))
}
The following exception is thrown when storing instances of StringSubClass
Terminating app due to uncaught exception 'RLMException', reason: 'Object type 'StringSubClass' is not managed by the Realm. If using a custom `objectClasses` / `objectTypes` array in your configuration, add `StringSubClass` to the list of `objectClasses` / `objectTypes`.'

Detect if a Realm object comforms to a specific protocol

I made a protocol called Synchronizable and I have some of my Realm objects conformed to this protocol.
Such as :
class Comment : Object, Synchronizable {
// ...
}
I want to query all of local Realm objects, and retrieve all objects that are conformed to this protocol.
So I did something like :
func getObjectsToSynchronize() -> [Object]{
Array(realm.objects(Object.self)).filter({
if let $0 = $0 as? Synchronizable {
return true
}
return false
})
}
With Object the Realm Object type, but this type object by default doesn't implements my protocol, so I can't detect if the object is conform to the protocol Synchronizable, it says :
Expression pattern of type 'Object' cannot match values of type 'Synchronizable?'
Is there a way to do this ? I think I'm on the wrong way by querying in the Realm object.
Wrong way?
Maybe I have to create another Realm class object called SynchronizableObjectthat inherits from Object realm class.
This class will have a relationship to Realm objects that I want to be Synchronizable, such as :
class SynchronizableObject : Object {
// MARK: - Realm Relationships
dynamic var synchronizables : List<Object>()
}
And then I should query this class with the Realm object
#AnthonyR you can't query/filter directly RealmSwift.Object, you must inherit your model from RealmSwift.Object as you'v done
class Comment : Object, Synchronizable {
// ...
}
Then you can query/filter all objects of inherited type:
func getObjectsToSynchronize() -> Results<Comment> {
return realm.objects(Comment.self)
}
If you explain a little more what do you want to do may be I can help you

Creating generic Repository class for Core Data with swift

I'm trying to create a generic single repository class that can both save and fetch whatever entity you ask it to, likely inferred by object type. I'm using this question as a base.
I was trying to bypass having to pass an NSManagedObjectContext in to an entity, and have the Repository do this instead. Is this possible?
The ideal method signature to save an entity would be the following, where entity is a subclass of NSManagedObject with a few properties:
Repository(persistentContainer).save(entity)
NSManagedObject Extension
extension NSManagedObject
{
class func createInContext<T>(context:NSManagedObjectContext, type : T.Type) -> T {
return unsafeDowncast(NSEntityDescription.insertNewObject(forEntityName: entityName(), into: context), to: self) as! T
}
class func entityName() -> String {
let classString = NSStringFromClass(self)
return classString.components(separatedBy: ".").last ?? classString
}
}
Repository Save Method
public func save<T>(entity: T) -> Void where T: NSManagedObject
{
self.container.performBackgroundTask { (context) in
let object = T.self.createInContext(context: context, type: T.self)
// Error checking removed for brevity
try! context.save()
}
}
The error that I get at runtime is:
Failed to call designated initializer on NSManagedObject class 'UUID'
and below it...
[AppName.Uuid setUuid:]: unrecognized selector sent to instance
Which one is it then? Is it possible to bypass having to pass the context in every time with this method or not? Is there an alternative solution to achieve what I am after?

Pass only specific types as argument to function

I have a function which is being repeated about 8-9 times and I'm trying to cut down on redundancy.Is it possible to create a function which take spefic types and returns an array of the initlizated type using the json object being sent.
Current function
static func initArray(json: JSON)-> [Event]{
var array = [Event]()
json.forEach(){
array.append(Event.init(json: $0.1))
}
return array
}
Desired function
static func initArray<T> (type: T, json: JSON)-> [T]{
var array = [T]()
//I get stuck here im not too sure how to initlize the type
//Thats why im wondering if it's possible to pass speific types
//to the function
return array
}
You initialize an instance of T just like an instance of any known class, with whatever initializers are available to it. To know what your options are, you generally need to constraint T in some form. The way I typically go about this is to define a protocol that all the types I care about passing to the function adopt. In your case you would put specific initializers in your protocol. Then constraint T to be of that type:
protocol SomeProtocol {
init(json: JSON)
}
class someClass {
static func initArray<T:SomeProtocol>(type: T, json: JSON) -> [T] {
// Create your objects, I'll create one as an example
let instance = T.init(json: json)
return [instance]
}
}
Constraining generic types can be more complex than what I've shown, so I'd recommend checking out the Type Constraints section of the Generics chapter of The Swift Programming Language for more information.

NSUserDefaults in Swift - implementing type safety

One of the things that bugs me about Swift and Cocoa together is working with NSUserDefaults, because there is no type information and it is always necessary to cast the result of objectForKey to what you are expecting to get. It is unsafe and impractical. I decided to tackle this problem, making NSUserDefaults more practical in Swift-land, and hopefully learning something along the way. Here were my goals in the beginning:
Complete type safety: each key has one type associated with it. When setting a value, only a value of that type should be accepted and when getting a value the result should come out with the correct type
Global list of keys which are clear in meaning and content. The list should be easy to create, modify and extend
Clean syntax, using subscripts if possible. For example, this would
be perfect:
3.1. set: UserDefaults[.MyKey] = value
3.2. get: let value = UserDefaults[.MyKey]
Support for classes that conform to the NSCoding protocol by
automatically [un]archiving them
Support for all property list types accepted by NSUserDefaults
I started by creating this generic struct:
struct UDKey <T> {
init(_ n: String) { name = n }
let name: String
}
Then I created this other struct that serves as a container for all the keys in an application:
struct UDKeys {}
This can then be extended to add keys wherever needed:
extension UDKeys {
static let MyKey1 = UDKey<Int>("MyKey1")
static let MyKey2 = UDKey<[String]>("MyKey2")
}
Note how each key has a type associated with it. It represents the type of the information to be saved. Also, the name property is the string that is to be used as a key for NSUserDefaults.
The keys can be listed all in one constants file, or added using extensions on a per-file basis close to where they are being used for storing data.
Then I created an "UserDefaults" class responsible for handling the getting/setting of information:
class UserDefaultsClass {
let storage = NSUserDefaults.standardUserDefaults()
init(storage: NSUserDefaults) { self.storage = storage }
init() {}
// ...
}
let UserDefaults = UserDefaultsClass() // or UserDefaultsClass(storage: ...) for further customisation
The idea is that one instance for a particular domain is created and then every method is accessed in this way:
let value = UserDefaults.myMethod(...)
I prefer this approach to things like UserDefaults.sharedInstance.myMethod(...) (too long!) or using class methods for everything. Also, this allows interacting with various domains at the same time by using more than one UserDefaultsClass with different storage values.
So far, items 1 and 2 have been taken care of, but now the difficult part is starting: how to actually design the methods on UserDefaultsClass in order to comply with the rest.
For example, let's start with item 4. First I tried this (this code is inside UserDefaultsClass):
subscript<T: NSCoding>(key: UDKey<T>) -> T? {
set { storage.setObject(NSKeyedArchiver.archivedDataWithRootObject(newValue), forKey: key.name) }
get {
if let data = storage.objectForKey(key.name) as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? T
} else { return nil }
}
}
But then I find out that Swift doesn't allow generic subscripts!! Alright, then I guess I'll have to use functions then. There goes half of item 3...
func set <T: NSCoding>(key: UDKey<T>, _ value: T) {
storage.setObject(NSKeyedArchiver.archivedDataWithRootObject(value), forKey: key.name)
}
func get <T: NSCoding>(key: UDKey<T>) -> T? {
if let data = storage.objectForKey(key.name) as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? T
} else { return nil }
}
And that works just fine:
extension UDKeys { static let MyKey = UDKey<NSNotification>("MyKey") }
UserDefaults.set(UDKeys.MyKey, NSNotification(name: "Hello!", object: nil))
let n = UserDefaults.get(UDKeys.MyKey)
Note how I can't call UserDefaults.get(.MyKey). I have to use UDKeys.MyKey. And I can't do that because it's not yet possible to have static variables on a generic struct!!
Next, let's try number 5. Now that has been an headache and that's where I need lots of help.
Property list types are, as per the docs:
A default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary.
That in Swift means Int, [Int], [[String:Bool]], [[String:[Double]]], etc are all property list types. At first I thought that I could just write this and trust whoever is using this code to remember that only plist types are allowed:
func set <T: AnyObject>(key: UDKey<T>, _ value: T) {
storage.setObject(value, forKey: key.name)
}
func get <T: AnyObject>(key: UDKey<T>) -> T? {
return storage.objectForKey(key.name) as? T
}
But as you'll notice, while this works fine:
extension UDKeys { static let MyKey = UDKey<NSData>("MyKey") }
UserDefaults.set(UDKeys.MyKey, NSData())
let d = UserDefaults.get(UDKeys.MyKey)
This doesn't:
extension UDKeys { static let MyKey = UDKey<[NSData]>("MyKey") }
UserDefaults.set(UDKeys.MyKey, [NSData()])
And this doesn't either:
extension UDKeys { static let MyKey = UDKey<[Int]>("MyKey") }
UserDefaults.set(UDKeys.MyKey, [0])
Not even this:
extension UDKeys { static let MyKey = UDKey<Int>("MyKey") }
UserDefaults.set(UDKeys.MyKey, 1)
The problem is that they are all valid property list types yet Swift obviously interprets arrays and ints as structs, not as their Objective-C class counterparts. However:
func set <T: Any>(key: UDKey<T>, _ value: T)
won't work either, because then any value type, not just the ones that have a class cousin courtesy of Obj-C, is accepted, and storage.setObject(value, forKey: key.name) is no longer valid because value has to be a reference type.
If a protocol existed in Swift that accepted any reference type and any value type that can be converted to a reference type in objective-c (like [Int] and the other examples I mention) this problem would be solved:
func set <T: AnyObjectiveCObject>(key: UDKey<T>, _ value: T) {
storage.setObject(value, forKey: key.name)
}
func get <T: AnyObjectiveCObject>(key: UDKey<T>) -> T? {
return storage.objectForKey(key.name) as? T
}
AnyObjectiveCObject would accept any swift classes and swift arrays, dictionaries, numbers (ints, floats, bools, etc that convert to NSNumber), strings...
Unfortunately, AFAIK this doesn't exist.
Question:
How can I have write a generic function (or collection of overloaded generic functions) whose generic type T can be any reference type or any value type that Swift can convert to a reference type in Objective-C?
Solved: With the help of the answers I got, I arrived at what I wanted. In case anyone wants to take a look at my solution, here it is.
I don't mean to brag but ... oh who am I kidding, I totally do!
Preferences.set([NSData()], forKey: "MyKey1")
Preferences.get("MyKey1", type: type([NSData]))
Preferences.get("MyKey1") as [NSData]?
func crunch1(value: [NSData])
{
println("Om nom 1!")
}
crunch1(Preferences.get("MyKey1")!)
Preferences.set(NSArray(object: NSData()), forKey: "MyKey2")
Preferences.get("MyKey2", type: type(NSArray))
Preferences.get("MyKey2") as NSArray?
func crunch2(value: NSArray)
{
println("Om nom 2!")
}
crunch2(Preferences.get("MyKey2")!)
Preferences.set([[String:[Int]]](), forKey: "MyKey3")
Preferences.get("MyKey3", type: type([[String:[Int]]]))
Preferences.get("MyKey3") as [[String:[Int]]]?
func crunch3(value: [[String:[Int]]])
{
println("Om nom 3!")
}
crunch3(Preferences.get("MyKey3")!)
I'd like to introduce my idea. (Sorry for my poor English in advance.)
let plainKey = UDKey("Message", string)
let mixedKey
= UDKey("Mixed"
, array(dictionary(
string, tuple(
array(integer),
optional(date)))))
let ud = UserDefaults(NSUserDefaults.standardUserDefaults())
ud.set(plainKey, "Hello")
ud.set(plainKey, 2525) // <-- compile error
ud.set(mixedKey, [ [ "(^_^;)": ([1, 2, 3], .Some(NSDate()))] ])
ud.set(mixedKey, [ [ "(^_^;)": ([1, 2, 3], .Some(NSData()))] ]) // <-- compile error
The only difference is that UDKey() now requires #2 argument, a value of BiMap class. I've uncoupled the work originally of UDKey into BiMap which converts a value of a type to/from a value of another type.
public class BiMap<A, B> {
public func AtoB(a: A) -> B?
public func BtoA(b: B) -> A?
}
Consequently, types that set/get can accepts are conducted by BiMap, and no longer limited to types as can automatically cast
from/to AnyObject (more specifically, types NSUserDefaults can accepts.).
Because BiMap is a generic class, you can easily create subtypes of that, interchanging arbitrary two types you want.
Here is full source code. (But there are bugs yet to be fixed..)
https://gist.github.com/hisui/47f170a9e193168dc946