My application code gets JSON data from a server, converts it to a Dictionary and then uses that to hydrate and attempt to save a RealmSwift object with a matching schema.
I ran into a crash caused by a Float value coming into a field that was declared as Int in the model. RLMException was thrown.
As suggested in this thread, one doesn't simply try to catch RLMException.
My question is, is that answer correct? And, if so, what is the correct way to prevent a crash when an unexpected value is found? Isn't there some sort of validation mechanism that I can run on all of the values before attempting to set them?
You can use third party frameworks for mapping JSON to Realm objects, like ObjectMapper and use it's failable initializer to validate your JSON.
This failable initializer can be used for JSON validation prior to object serialization. Returning nil within the function will prevent the mapping from occuring. You can inspect the JSON stored within the Map object to do your validation.
You can use this to see if everything is ok with json parameters
static func createFromJSONDictionary(json: JSONDictionary) throws -> MyObject {
guard let
property1 = json["property1"] as? Int,
property2 = json["property1"] as? String else {
// Throw error.... or create with default values
throw NSError(domain: "", code: 0, userInfo: nil)
}
// Everything is fine, create object and return it
let object = MyObject()
object.something = property1
return object
}
I ended up writing an extension to handle this. At the moment I'm only validating numbers and dates, but the switch statement could check every single property type easily.
extension Object {
public convenience init( withDictionary rawData: [String:AnyObject] ) {
var sanitizedData = rawData
let dynamicSelf = self.dynamicType
let schema = dynamicSelf.sharedSchema()
for property in schema.properties {
guard let value = sanitizedData[property.name] else { continue }
var isValid = true
switch property.type {
case .Double:
let val = Double(String(value))
if val == nil || val!.isNaN {
isValid = false
}
case .Float:
let val = Float(String(value))
if val == nil || val!.isNaN {
isValid = false
}
case .Int:
if Int(String(value)) == nil {
isValid = false
}
case .Date:
if Int64(String(value)) == nil {
isValid = false
} else {
sanitizedData[property.name] = NSDate(timeIntervalSince1970: value.doubleValue / 1000)
}
default:
break
}
if !isValid {
log.error( "Found invalid value: \(value) for property \(property.name) of \(dynamicSelf)" )
sanitizedData.removeValueForKey(property.name)
}
}
self.init( value: sanitizedData )
}
}
Related
I wonder if I should create my own additional layer when updating Realm objects to avoid redundant database writing operations or is it done automatically on a lower level?
Let's take an example:
class SomeEntity: Object {
#Persisted(primaryKey: true) var id = 0
#Persisted var aaa: String?
#Persisted var bbb: Float?
#Persisted var ccc: Int?
}
when doing some batch update:
newDownloadedData.forEach { entry in
guard let id = entry["id"].int else {
return
}
try? localRealm.write {
let entity = existingLocalEntities.first { $0.id == id } ?? SomeEntity(id: id)
localRealm.add(entity, update: .modified) //this makes an 'upsertion' which is automatically an update or insert
if entity.aaa != entry["aaa"].string {
entity.aaa = movieInfo["aaa"].string
}
if entity.bbb != entry["bbb"].float {
entity.bbb = movieInfo["bbb"].float
}
if entity.ccc != entry["ccc"].int {
entity.ccc = movieInfo["ccc"].int
}
}
}
I wonder if these checks necessary or can I just go with:
entity.aaa = movieInfo["aaa"].string
entity.bbb = movieInfo["bbb"].float
entity.ccc = movieInfo["ccc"].int
and not worry that values will be updated and written even if downloaded values are the same as existing local ones?
Your observers will be notified if you update a property on a realm object with the same value. Realm does not care if you use a different value or not.
I'm not sure what your use case is, but it may be a pain in the butt to check every value manually.
You can do something like this though:
protocol UniqueUpdating { }
extension UniqueUpdating where Self: AnyObject {
#discardableResult
func update<Value: Equatable>(
_ keyPath: ReferenceWritableKeyPath<Self, Value>,
to value: Value
) -> Bool {
guard self[keyPath: keyPath] != value else { return false }
self[keyPath: keyPath] = value
return true
}
}
extension Object: UniqueUpdating {}
class Person: Object {
#Persisted(primaryKey: true) var id: Int = 0
#Persisted var name: String = ""
}
Usage would be like this:
let realm = try! Realm()
try! realm.write {
person.update(\.name, to: "BOB")
}
EDIT: things described below do not work like expected.
eg. if name = "Tom" and later the same value is assigned ( self.name = "Tom") it will be treated as modification.
It turns out that YES, Realm can be smart about it!
the clue sits in update parameter in .add function.
using .modified will result in smart data write.
Excerpt from documentation:
/**
What to do when an object being added to or created in a Realm has a primary key that already exists.
*/
#frozen public enum UpdatePolicy: Int {
/**
Throw an exception. This is the default when no policy is specified for `add()` or `create()`.
This behavior is the same as passing `update: false` to `add()` or `create()`.
*/
case error = 1
/**
Overwrite only properties in the existing object which are different from the new values. This results
in change notifications reporting only the properties which changed, and influences the sync merge logic.
If few or no of the properties are changing this will be faster than .all and reduce how much data has
to be written to the Realm file. If all of the properties are changing, it may be slower than .all (but
will never result in *more* data being written).
*/
case modified = 3
/**
Overwrite all properties in the existing object with the new values, even if they have not changed. This
results in change notifications reporting all properties as changed, and influences the sync merge logic.
This behavior is the same as passing `update: true` to `add()` or `create()`.
*/
case all = 2
}
I am writing a generic base class for all of my persistence storage classes. Each child class will work with one specific entity/table in a persistent database using Core Data. The threading appears to be working correctly and I can get the count of items in the table correctly. The problem is that if the entity name in the fetch request is wrong, I do not get an exception, I get a crash. Since this is a string and is typed in somewhere in the code by a programmer, I want to detect the error in some better way so that the programmer is alerted to having used an invalid entity name.
Here's my code:
class Store<EntityType:NSFetchRequestResult> : NSObject {
private var entityName : String = ""
init( entityName : String ) {
self.entityName = entityName
}
public var Count : Int
{
get {
var fetchResults : Int = 0
objc_sync_enter( self )
do {
var privateContext : NSManagedObjectContext? = nil
DispatchQueue.main.sync {
let deleg = UIApplication.shared.delegate as! AppDelegate
privateContext = deleg.privateManagedObjectContext
}
if privateContext == nil
{ return 0 }
privateContext!.performAndWait {
do
{
let request = NSFetchRequest<EntityType>( entityName: self.entityName )
fetchResults = try privateContext!.count( for: request )
} catch
{
print("Unexpected error: \(error).")
}
}
}
objc_sync_exit( self )
return fetchResults
}
}
...
With the wrong entity name, the count() function on the MOC causes a SIGABRT and does not throw any exception.
How do I catch the error somehow?
I am also open to comments about the threading and using this in a background thread. It works now but since the internet and Apple all say vague stuff about how to use Core Data in a background thread, help is appreciated.
I also tried this just now:
let request = NSFetchRequest<EntityType>( entityName: String(reflecting: EntityType.self) )
The name is in the form of "app name.entityname" so it might be usable. But since the editor lets the programmer enter a different name for the entity and for the class, this is not at all safe. If I can somehow check at runtime that the name is valid, I'll use this method. But without solving the crash problem, I'm reluctant to change anything right now.
It's possible to get the list of entity names that exist in the model for the context.
With that, you can check that the entity name supplied by the programmer is valid before executing the fetch request.
//get the context and make sure it's not nil
guard let privateContext = privateContext
else {
print("Unexpected error: context is nil.")
return 0
}
//get the names of entities in the model associated with this context
// credit: Snowman, https://stackoverflow.com/questions/5997586/core-data-list-entity-names
guard let names = privateContext.persistentStoreCoordinator?.managedObjectModel.entities.map({ (entity) -> String? in
return entity.name
})
else {
print("Unexpected error: Could not get entity names from model.")
return 0
}
//make sure the name specified by the programmer exists in the model
guard names.contains(where: { (name) -> Bool in
return name == self.entityName
})
else {
print("Unexpected error: \(self.entityName) does not exist in the model.")
return 0
}
privateContext.performAndWait {
do
{
let request = NSFetchRequest<EntityType>( entityName: self.entityName )
fetchResults = try privateContext.count( for: request )
} catch
{
print("Unexpected error: \(error).")
}
}
If you're wondering about performance: in my testing, retrieving the list of entity names 500 times for a model with 20 entities took 20 ms. Nothing to worry about.
I have a setup where I fetch some json data from a server to populate a table. There is a chance that the data has changed, so I fetch all the data each time. I map that data into a Realm object and persist it to the database. A primary ID is used to prevent duplication.
I use Realm notifications to keep the tableview/collection view in sync with the datasource. When an server request completes, objects are updated or added to the database, and the views automatically reload.
The problem is that all of the cells reload because all of the objects in the database get updated, even if they aren't actually any different because I'm just blindly using realm.add(object, update:true). Is there is a good way to prevent updating objects that haven't actually changed so that cell's aren't needlessly reloaded?
The solution I tried was to write an extension to Realm's Object class including a function that checks if any objects with the same primary ID exist, compare them, and add/update the Object iff they don't match. However, I have many classes of objects, and I couldn't find a way to get the objects type from the object itself without knowing its class to start with.
// All subclasses of ServerObject have id as their primaryKey
let object = database.objectForPrimaryKey(type:???, key: self.id)
I do not want to copy the same hunk of check-before-add code to every one of my classes because that's asking for trouble, so I need something that can go in a protocol or extension, or just a completely different way to go about handling the server's response.
It sounds like you want something along the lines of:
extension Object {
public func hasChanges(realm: Realm) -> Bool {
guard let obj = realm.objectForPrimaryKey(self.dynamicType, key: self["id"])
else { return true }
for property in obj.objectSchema.properties {
let oldValue = obj[property.name]
let newValue = self[property.name]
if let newValue = newValue {
if oldValue == nil || !newValue.isEqual(oldValue) {
return true
}
} else if oldValue != nil {
return true
}
}
return false
}
}
This would be used as:
let obj = MyObjectType()
obj.id = ...;
obj.field = ...;
if obj.hasChanges(realm) {
realm.add(obj, update: true)
}
For object with nested objects (lists), this modified solution seems to work well.
// This function is general and can be used in any app
private func hasChanges(realm: Realm) -> Bool {
guard let obj = realm.objectForPrimaryKey(self.dynamicType, key: self["id"])
else { return true }
for property in obj.objectSchema.properties {
// For lists and arrays, we need to ensure all the entries don't have changes
if property.type == .Array {
let list = self.dynamicList(property.name)
for newEntry in list {
if newEntry.hasChanges(realm) {
return true
}
}
// For all properties that are values and not lists or arrays we can just compare values
} else {
let oldValue = obj[property.name]
let newValue = self[property.name]
if let newValue = newValue {
if oldValue == nil || !newValue.isEqual(oldValue) {
return true
}
} else if oldValue != nil {
return true
}
}
}
return false
}
I have a declaration..
var cabResults = Dictionary<CabType, [CabResult]>()
Now I want to check if the object is present in the dictionary for a particular key.. which can be don by
if self.cabResults[currentCabType] != nil
Now I also want to check if the object returned by self.cabResults[currentCabType] is of type [CabResult]
How can I get this..?
I would use if let ... as? ...:
if let cabs = self.cabResults[currentCabType] as? [CabResult] {
// yep
} else {
// nope
}
You don't need to check is the object is will and do another check is that of any particular type, you can just do:
if let myObject = self.cabResults[currentCabType] as? [CabResult] {
// myObject is not till and is of type myObject
} else {
// the object is nil or it's not of type myObject
}
I have a function that gets and parses data from Firebase. I have a validation "parseUserModel) that returns "Bool" type if the model meets the requirements to pass. I set this value to validUser which is of Bool type. The model I'm pulling actually passes and returns a boolean of true.
Here's where the issue happens.
If(validUser) { ... code ... } fails even though the validUser is "true".
I've played around with the actual return type and I've tried these things:
validUser.boolValue and setting this inside the if(validUser.boolValue)
this fails
setting the if(validUser) { ... code ... }
this fails
setting the if(validUser == true) {..code..}
this fails
setting the if(validUser.boolValue == true) {code}
this fails
I'm all out of ideas as to why exactly this is failing. I'm comparing a primitive type (I'm assuming) to another primitive type.
My Questions
is the 'true' keyword in Swift not actually a Bool object type?
why is this failing when I'm explicitly asking for the boolValue?
is their a Swift bug I should know about?
is the if statement not doing what I think it should be doing?
Retrieve Data Function
public static func retrieveUserData(user:UserModel, completion:(success:Bool)->Void){
if(user.username != nil){
let fbRef = Firebase(url: GlobalVariables().FB_BASE_URL+"users/\(user.username)")
fbRef.observeSingleEventOfType(FEventType.Value, withBlock: { (snapshot) -> Void in
if(snapshot.value != nil){
let validUser:Bool = UserModel.sharedInstance.parseUserData(JSON(snapshot.value))
println(validUser)
println(validUser.boolValue)
if(validUser.boolValue){
completion(success: true)
} else {
completion(success: false)
}
}
})
}
}
Parse Model and Validate
public func parseUserData(data:JSON) -> Bool {
var uName:String!
var uFullName:String!
if let username = data["username"].string {
UserModel.sharedInstance.username = username
}
if let fullName = data["fullName"].string {
UserModel.sharedInstance.fullName = fullName
} else {
return false
}
if let biography = data["biography"].string {
UserModel.sharedInstance.biography = biography
}
if let email = data["email"].string {
UserModel.sharedInstance.email = email
}
if let plistID = data["playlistCollectionID"].string {
UserModel.sharedInstance.playlistCollectionID = plistID
} else {
//generate a playlistCollectionID because the user doesn't have one
UserModel.sharedInstance.playlistCollectionID = NSUUID().UUIDString
}
if(UserModel.validateUserObject(UserModel.sharedInstance)) {
return true
} else {
return false
}
}
Println() log from Xcode
true
true
the above two true are coming from the retrieval code
println(validUser) and println(validUser.boolValue)
The Bool type in Swift conforms to BooleanType, which has the member:
var boolValue: Bool { get }
The value stored in validUser is definitely a Bool, because otherwise you wouldn't be able to put it in a variable of the Bool type.
The most likely situation here is that there is a bug in your code. Go over it again and make sure that it's actually entering the else block instead of the if block.
As a side note, the Swift formatting convention for if statements looks like this:
if condition {
...
} else {
...
}
as opposed to if(condition){...
Due to my own idiocy I was sending the shared user instance UserModel into itself.
private static func validateUserObject(object:UserModel) -> Bool {
println(object.username)
println(object.fullName)
if(object.username != nil && object.fullName != nil){
return true
} else {
return false
}
}
So I would call the method like this...
UserModel.sharedInstance.validateUserObject(self)
This would cause the Function specialization explosion. This would not crash the app (which is the strange part). It would continue and return the true boolean (because the model was valid).