Instatiate Realm Object from string in swift 3 - ios

I would like to know if it is possible to instantiate a realm object based on a string that is the class name of the realm object but without knowing what that string will be until it is provided.
For example:
for(_, object) in json["AllObjects"]{
let objectType = self.getRealmObjectBasedOnString(type: className, params: object.stringValue)
self.objectList.append(objectType)
}
Here I go through a json that I get and want to create a realm object from each json object in the array. The problem is that this method will be called several times and each time the only thing that will change is the className variable. So I would like to keep this logic in only one method instead of creating several methods with same logic or a huge and complicated if else that determines the realm object to be created.
Here is getRealmObjectBasedOnString
func getRealmObjectBasedOnString(type: String, params: String) -> Object{
switch type {
case "classA":
return ClassA(JSONString: params)!
case "classB":
return ClassB(JSONString: params)!
default:
return DefaultClass(JSONString: params)!
}
}
Can someone explain why this does not work and whether it is possible to accomplish what I want?

You can use NSClassFromString to get Realm object type from string, but keep in mind that Swift uses modules for nemespacing, so you'll need to add your app's module name before your class name.
guard let objectType = NSClassFromString("YourAppModuleName.\(json["className")") else {
// handle unexpected class here
}
let objectList = realm.objects(objectType)

Related

RealmSwift Cannot cast Results<SomeOjbect> to Results<Object>

RealmSwift version: latest master branch
So I have a Realm Object like:
import RealmSwift
class SomeObject: Object
{
#objc dynamic var datetime = ""
#objc dynamic var city = 0
convenience init(city: Int, datetime: String)
{
self.init()
self.city = city
self.datetime = datetime
}
}
There is a func call like
static func createlineData(from results: Results<Object>?) -> LineChartData
Now I fetch some results and pass to createLineData:
let realm = try! Realm()
let results = realm.objects(SomeObject.self).filter("city = \(city.rawValue)")
let lineData = createlineData(from: results as? Results<Object>)
compiler warns me that the type cast will always fail:
Cast from Results<"SomeObject"> to unrelated type Results<"Object"> always fails
I am confused since SomeObject is just a subclass. How can I fix it? Thanks in advance.
UPDATE:
What I want to do is that, the param of
static func createlineData(from results: Results<Object>?) -> LineChartData
can never be changed, so I need to make a query to filt based on city which is enum, pass them into createlineData(from results: Results<Object>?), and access other properties like datetime later in createlineData, from Results<Object>
In Swift, each generic class represents its own type and even if you have a generic class where the generic type parameter is a subclass of your other generic class having the superclass as its generic parameter, the two generic classes won't be related through inheritance.
This is why you cannot cast Results<SomeObject> to Results<Object> even though SomeObject is a subclass of Object.
Here's a simple example representing the same issue with a generic class:
class A{}
class B:A{}
class GenericClass<T> {
let val:T
init(val:T) {
self.val = val
}
}
let genericA = GenericClass<A>(val: A())
let genericB = GenericClass<B>(val: B())
let upcasted = genericB as? GenericClass<A> //warning: Cast from 'GenericClass<B>' to unrelated type 'GenericClass<A>' always fails
Moreover, the Results type in Realm is a homogenous collection type, so you cannot store different subclasses of Object in the same Results object, so casting to Results<Object> wouldn't make sense anyways. If you need to store objects from different Realm model classes in the same collection, you will need to sacrifice the self-updating nature of Results and stick with storing your objects in an Array for instance.

Swift understand generics with enums

I have following code written by other person:
public enum MSEntity<T : Metable> {
case meta(MSMeta)
case entity(T)
public func value() -> T? {
guard case let MSEntity.entity(value) = self else {
return nil
}
return value
}
public func objectMeta() -> MSMeta {
switch self {
case .entity(let entity): return entity.meta
case .meta(let meta): return meta
}
}
}
I have following questions:
1) what is the point of doing case entity(T)? What is "value" of that enum case and for what it may be used for?
2) I can't understand public func value() func.
What is that guard checking? What is value? Can someone provide an example where similar code may be useful?
The enum seems to represent an object that can either only contain metadata or a entity with metadata.
It has 2 cases: meta represents an object with only metadata and that metadata is this case's associated value, entity represents an object that has a entity and metadata. This case's associated value is the object's entity, which contains metadata (so the constraint on T is Metable)
what is the point of doing case entity(T)? What is "value" of that enum case and for what it may be used for?
Well, for this you have to look at the docs or ask the person who wrote the code. As far as I'm concerned, MSEntity is an object that has 2 states:
only metadata (meta case)
entity and metadata (entity case)
So the entity case is there to represent that.
I can't understand public func value() func. What is that guard checking? What is value?
The value function seems to return the object's entity, but what if this object has no entity (i.e. self is actually in the meta case)? The author of this code decided to return nil. That is why there is a check: if self is in the meta case. The word value is part of the pattern matching. It might be clearer if I wrote it like this:
guard case let MSEntity.entity(let value) = self else {
Whatever is in the associated value of entity case is put into a variable called value, so that you can return it later.
The (MSMeta) and (T) associated values of the meta and entity cases, respectively.
The value() method (which IMO should be a computed property of type T?, rather than a function of type () -> T?) returns the associated value of the entity case, if self is entity, otherwise it returns nil (when the self is meta).

Realmswift generic function call crashes when Result<Object> is returned

I have built a generic function to get any kind of data from realm, that seems to work just fine.
func getData<T: Object>(withFilter: String) -> Results<T>? {
if !checkRealm(){return nil}
return realm!.objects(T.self).filter(withFilter)
}
I can't figure out though how to use this function to delete data.
My delete function is the following:
func removeData(withFilter: String) {
let dataToDelete: Results<Object>? = getData(withFilter: withFilter)
// *** The above line crashes the app ***
if let dataToDeleteUnwrapped = dataToDelete{
try? realm!.write {
realm!.delete(dataToDeleteUnwrapped)
}
}
}
This results to an error, attached bellow. Though Results<Object>? crashes the app, Results<MyCustomObject>? works fine, but then my remove data function is not generic anymore.
Terminating app due to uncaught exception 'RLMException', reason: 'Object type 'RealmSwiftObject' is not managed by the Realm. If using a custom `objectClasses` / `objectTypes` array in your configuration, add `RealmSwiftObject` to the list of `objectClasses` / `objectTypes`.'
I am sure there is a nice short way to solve this one, but I can't figgure it out, so any help is appreciated.
Results cannot hold instances of several classes, all of its elements must be from the same type, so Results<Object> is not a valid type for the collection.
Your current implementation of getData is wrong. You don't use the generic type T anywhere in your function. Generic types should be given to input arguments for generic functions. You need to call both getData and removeData on a specific Realm type inheriting from Object, since Result can only hold a single type and all your types have unique properties, so you can only use a predicate on a specific type.
This is the correct generic implementation of getData to query Realm for a specific class with the given predicate.
func getData<T:Object>(ofType: T.Type, withFilter: String) -> Results<T>? {
if !checkRealm(){return nil}
return realm!.objects(T.self).filter(withFilter)
}
You call it like this (the following example was tested and is working with the playground in the examples project from the Realm website):
let people = getData(ofType: Person.self, withFilter: "cars.#count > 1 && spouse != nil")
people?.first?.name //prints Jennifer when called after adding the objects to realm, but before deleting them in the example project
Seeing how a generic function can be used on Realm with type constraints, you should be able to update your deletion function as well.
use this extension to convert results to real object
extension Results {
func toArray<T>(ofType: T.Type) -> [T] {
var array = [T]()
for i in 0 ..< count {
if let result = self[i] as? T {
array.append(result)
}
}
return array
}
}
how to use in code
let orjObjs = realm.objects(UrObj.self).toArray(ofType: UrObj.self) as [UrObj]

Argument of '#selector' does not refer to an initializer or method in

I update my project from Swift2.2 to Swift3.0 But "Argument of '#selector' does not refer to an initializer or method" issue received.
Here is code :
for object in Students {
let sectionNumber = collation.section(for: object.firstName!, collationStringSelector: #selector(NSObjectProtocol.self))
sections[sections.count - 1 - sectionNumber].append(object)
}
class Person: NSObject {
#objc var name: String
init(name: String) {
self.name = name
}
}
let p = Person(name: "Alice")
let collation = UILocalizedIndexedCollation.current()
collation.section(for: p, collationStringSelector: #selector(getter: Person.name))
This is also fine since Selector is from Objective-C. Which we need to :NSObject and #objc.
As per docs
func section(for object: Any, collationStringSelector selector: selector) -> Int
Description
Returns an integer identifying the section in which a model object belongs.
The table-view controller should iterate through all model objects for the table view and call this method for each object. If the application provides a Localizable.strings file for the current language preference, the indexed-collation object localizes each string returned by the method identified by selector. It uses this localized name when collating titles. The controller should use the returned integer to identify a local “section” array in which it should insert object.
Parameters
object
A model object of the application that is part of the data model for the table view.
*selector*
A selector that identifies a method returning an identifying string for object that is used in collation. The method should take no arguments and return an NSString object. For example, this could be a name property on the object.
Returns
An integer that identifies the section in which the model object belongs. The numbers returned indicate a sequential ordering.
Solution
Change like below
collation.section(for: "test", collationStringSelector: #selector(getStr)) //here getStr is an other function returning String
func getStr()->String{
return "test"; // this should return an identifying string for object that is used in your collation
}
I implement user2215977 answer but app crash again & again. Now i just change the #selector(NSObjectProtocol.self) to "self". All error gone but just one warning received " Use of string literal for Objective-C selectors is deprecated; use #selector instead ".
If any person have idea to resolve this warning then share me.Otherwise error go now.

Insert a potentially null value into the sqlite database in iOS

I have a class called Content, whose URL property is nullable (URL: String?).
I'd like to store this URL property in my sqlite database using FMDB, but Xcode complains I need to unwrap the optional with !
but the problem is when I do content.URL! it crashes because it's nil.
success = db.executeUpdate("INSERT INTO CONTENT(ID, Icon, Title, Description, URL, IsActive) VALUES(?,?,?,?,?,?,?,?,?)", withArgumentsInArray: [content.ID, content.icon, content.title, content.description, content.URL!, content.isActive])
How can I successfully insert URL both when it has and does not have a value?
Thanks!
One approach that I use for cases like this is to create a class extension.
For example:
class func databaseSafeObject(object: AnyObject?) -> AnyObject {
if let safeObject: AnyObject = object{
return safeObject;
}
return NSNull();
}
Then you can just use:
NSObject.databaseSafeObject(content.URL);
to get something that can be directly inserted in the db.
So this ended up working for me, although it seems kinda irking that this is how it has to be:
(content.URL == nil ? NSNull() : content.URL!)
There exists Swift wrappers for SQLite that may be a better fit that fmdb which can run in Swift but does not use Swift features such as optionals (that you miss here), type safety, and error handling. See for example my GRDB.swift http://github.com/groue/GRDB.swift which was heavily influenced by ccgus/fmdb.
The AnyObject type didn't work for me when working with variables of type Int and Double, so I created a similar function to handle optional Swift variables.
private func getOptionalOrNull(_ possibleValue:Any?)->Any {
if let theValue = possibleValue {
return theValue
} else {
return NSNull()
}
}

Resources