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

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]

Related

Generic function -> Cannot convert return expression to return type

Here is a little issue I am having using a generic function. Probably a basic error due to a lack of practice with generics. Anyway, below is the code relevant to the question.
The generic function itself, not showing any error:
func setThingRevision<GenericType:Revisionable>(entity name: String) -> [(GenericType,Int)] {
var resultArray = [(GenericType,Int)]()
// ..... we do some useful magic ......
return resultArray
}
Some code using the generic function above:
func setMyRealStuffRevision(entity name: String) -> [(RealType,Int)] {
return setThingRevision(entity: name)
}
Here is the error message given by the compiler in the last function (setMyRealStuffRevision):
Cannot convert return expression of type '[(_, Int)]' to return type '[(RealType, Int)]'
Rather than being surprised by the message, I wonder what is the right syntax to use.
My RealType is compatible with GenericType, but I am not sure if I need provide some information to the setThingRevision generic function or if it can be inferred from the context.
--- Addition ---
Here is a fake setThingRevision that I created for testing purpose.
func setThingRevision<GenericType:Revisionable>(entity name: String) -> [(GenericType,Int)] {
var resultArray = [(GenericType,Int)]()
// Here name contains the name of a Core Data entity and getArrayFromEntity is
// a local function, extracting an array from the contents of the entity.
for item in getArrayFromEntity(name) as! [GenericType] {
resultArray.append((item, 99))
return resultArray
}
return resultArray
}
On type safe languages if a "induced" conversion cannot be done, the compiler will tell you that message. Somehow 'var resultArray = (GenericType,Int)' it's not interpreted as a type that can be converted to the type of the return function. Examine closely the type of resultArray assigned by the compiler. The right syntax to use would be not using 'var' to create the resultArray variable, instead, explicitly define the type.
try this
func setThingRevision<T: Revisionable>(entity name: String) -> [(T, Int)] {
var resultArray = [(T, Int)]()
// ..... we do some useful magic ......
return resultArray
}
func setMyRealStuffRevision(entity name: String) -> [(RealType, Int)] {
return setThingRevision(entity: name)
}
protocol Revisionable {
}
// edited
class RealType: NSManagedObject, Revisionable {
}

iOS Swift4 how to reconcile T.Type and type(of:) to pass dynamic class type as function parameter?

I'm trying implement generic storage of configuration parameters by using class type string as a dictionary key. The idea is that the retrieve function will return an object of proper type. Each type is unique in the storage. However, when I call the function, I'm getting a Swift compiler error and am not sure how interpret it:
Compiler Error:
Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
I checked the documentation, and it seems like the type(of:) method is supposed to return runtime class, while the compiler makes it look like it's complaining because it thinks I'm passing a type of Any
How do I pass Swift class name as a function parameter without using an instance of that class?
func retrieve<T>(type: T.Type) -> T? {
let valueKey = String(describing: type)
print("retrieving: \(valueKey)")
return dictionary[valueKey] as? T
}
func update(with value: Any) {
let valueKey = String(describing: type(of: value))
print("Updating: \(valueKey)")
dictionary[valueKey] = value
}
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
subject.update(with: testCase)
//Compiler Error: Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
let t = type(of: testCase)
let retrieved = subject.retrieve(type: t)
//check for equality
}
//this works
expect(subject.retrieve(type: Int.self)).to(equal(1))
expect(subject.retrieve(type: Double.self)).to(equal(2.0))
expect(subject.retrieve(type: String.self)).to(equal("text"))
I've done more testing, and see that it appears my array does not honor the type(of: ) documentation, and this function returns the same object as "Any":
func retrieve<T>(sample: T) -> T? {
let valueKey = String(describing: type(of: sample))
print("retrieving: \(valueKey)") //always retrieves same object "Any"
return dictionary[valueKey] as? T
}
Updated: Thank you for responses, to clarify - test cases were intended to begin with simple types, then progress to more complex classes. The actual implementation would store completely unique instances of custom types, not Strings or Ints.
let testCases: [Any] = [ConnectionConfig(...),
AccountID("testID"),
AccountName("testName")]
The tests recognize the generic nature of the retrieve function and assign appropriate types, as evidenced by code completion:
expect(subject.retrieve(type: ConnectionConfig.self)?.ip).to(equal("defaultIP"))
expect(subject.retrieve(type: AccountID.self)?.value).to(equal("testId"))
The intended end use within RxSwift context: provide the generic storage to a class and allow it to pull the appropriate values for configuration parameters. If no value exists, an error is thrown and is handled by a separate error handler:
class RxConfigConsumer: ConfigConsumer {
var connection: ConnectionConfig?
var accountID: AccountID?
init(with provider: ConfigProvider) {
connection = provider.retrieve(type: ConnectionConfig.self)
accountID = provider.retrieve(type: AccountID.self)
//etc
}
}
The combination of a generic with a metatype (.Type) is very weird and is probably what's tripping you up. If you get rid of the generic things work as you would expect:
func retrieve(_ T:Any.Type) {
print(type(of:T))
}
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
retrieve(type(of:testCase))
}
// Int.Type, Double.Type, String.Type
If you really want the generic, then get rid of the .Type and write it like this:
func retrieve<T>(_ t:T) {
print(type(of:t))
}
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
retrieve(type(of:testCase))
}
// Int.Type, Double.Type, String.Type
Even then, however, it's unclear to me what the point is of passing the metatype.
The short answer is what you're trying to do is impossible, because there is no way to type-annotate the following line of code:
let retrieved = subject.retrieve(type: t)
What is the static type, known at compile-time, of retrieved? It can't change at run-time. It certainly can't change from iteration to iteration. The compiler needs to allocate space for it. How much space does it require? Does it require space on the stack or heap? There's no way to know. The best we can say is that it's Any and put a box around it. 1 doesn't even have a proper type anyway. It's just an integer literal. It could be a Float or many other things (try let x: Float = 1 and see).
The answer is you can't build a loop like this. Your individual test cases are the right ones. Once you create an [Any], it is very difficult to get "real" types back out. Avoid it. If you have a more concrete problem beyond the example you've given, we can discuss how to deal with that, but I believe outside of a unit test, this specific problem shouldn't come up anyway.
This is an interesting question and you can run the following codes in a playground.
The first step is to solve the T.Type parameter. It's hard to put it into a function call. So to achieve your goal, we can use T but T.Type.
class MySubject {
var dictionary : [String : Any] = [:]
func retrieve<T>(type1: T) -> T? {
let valueKey = String(describing: (type(of: type1)))
print("retrieving: \(valueKey)")
return dictionary[valueKey] as? T
}
func update(with value: Any) {
let valueKey = String(describing: type(of: value))
print("Updating: \(valueKey)")
dictionary[valueKey] = value
}
}
var subject : MySubject = MySubject()
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
subject.update(with: testCase)
//Compiler Error: Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
let retrieved = subject.retrieve(type1: testCase)
//check for equality
}
The compilation is correct. But as you said, the return value is nil as a result of a generic retrieve Function. In order to achieve your goal, we may skip the generic way, use Any directly.
class MySubject {
var dictionary : [String : Any] = [:]
func retrieve(type1: Any) -> Any? {
let valueKey = String(describing: (type(of: type1)))
print("retrieving: \(valueKey)")
return dictionary[valueKey]
}
func update(with value: Any) {
let valueKey = String(describing: type(of: value))
print("Updating: \(valueKey)")
dictionary[valueKey] = value
}
}
var subject : MySubject = MySubject()
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
subject.update(with: testCase)
//Compiler Error: Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
let retrieved = subject.retrieve(type1: testCase)
//check for equality
}
Currently everything is perfect as you wish. But this brings up an interesting thought about generic. Is it O.K. or right to use generic here? As we know, there is a presumption in generic, which is represented by letters T, U, V. They have one common meaning: Type. When we try to use generic, we assume every parameter should have only one unique type. So in first case, Any is the only type and should be accepted without question in a generic call. There is no other type will be revealed during function call.
This kind of misunderstanding roots from the use of "let testCases: [Any] = [1, 2.0, "text"]. Although swift allows you to write this way, they are not a normal array. They are a list which contains different type essentially. So you can ignore fancy generic here without any regrets. Just pick the Any to solve your problem.

Instatiate Realm Object from string in swift 3

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)

Using .map to map an array of one class to an array of another

I'm new to Swift, and am trying to get my head around using .map. My understanding is that the behaviour is similar to that of Javascript, but maybe I'm not nailing the Swift syntax correctly?
I've created a public Array of my custom class CustomItem (which is a subclass of the type coming back in the response):
public var availableItems: [CustomItem] = []
public static func getAvailableItems(id: String, completion: (items: [CustomItem]) -> ()) -> Void {
DataConnector.getRelated(type: "users", id: id, relationship: "available") { (response) -> Void in
availableItems = (response.data?.map { return $0 as! CustomItem })!
completion(items: availableItems)
}
}
When I do a po response.data.map { return $0 } in the console with a breakpoint after the response is received, I get:
(lldb) po response.data.map { return $0 }
▿ Optional([<AnSDK.RemoteDataObject: 0x7f8a0b9c16b0>])
▿ Some : 1 elements
▿ [0] : < AnSDK.RemoteDataObject: 0x7f8a0b9c16b0>
So it definitely seems that part works, but when I try to cast the data object to CustomItem class, I get:
Could not cast value of type 'AnSDK.RemoteDataObject' (0x100abbda0) to 'Project.CustomItem' (0x100882c60).
Here's my CustomItem class just in case:
import AnSDK
public class CustomItem: RemoteDataObject {
var displayName: String = ""
var value: Float = 0.0
var owner: User?
}
If I don't use the ! to force downcast, I get:
RemoteDataObject is not convertible to CustomItem [...]
... in the compiler.
(I'm really just restating Ben Gottlieb's answer here, but hopefully a bit clearer since I believe some readers were confused by his attempt.)
The message seems fairly clear. You've received an array of AnSDK.RemoteDataObject. As best I can tell from your output, that is the actual class of the objects. You can't just say "it's really this subclass of RDO" unless it really is that subclass. Looking at your code, that seems unlikely. Somewhere in AnSDK it would have to construct a CustomItem and then just happen to return it as a RemoteDataObject. That doesn't appear to be what's happening inside of getRelated. Given your code, I doubt AnSDK knows anything about CustomItem, so how would it have constructed one?
There are numerous ways to fix this depending on what the types really are and how they interact. Ben's solution is one, which basically creates a copy of the object (though in that case, there's no particular reason for CustomItem to be a subclass of RDO, and probably shouldn't be.)
If you just want to add methods to RemoteDataObject, you can do that with extensions. You don't need to create a subclass.

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