Exception throw for null data with optional Int type in Realm - swift

I am taking my first foray into using Realm (0.98.1 via Cocoapods, Xcode 7.2) and am running into a small problem that I am not sure how to solve.
I have a model class called Airport that declares a property
let elevationFt = RealmOptional<Int>()
I am creating a set of Airport objects and persisting them in the following way
public func cacheDataToPersistanceStore(data:NSArray) -> Bool {
var success = true
autoreleasepool {
do {
let realm = try Realm()
realm.beginWrite()
for object in data {
guard let dictionaryValues = object as? Dictionary<String, AnyObject> else {
debugPrint("Unable to convert data to correct type")
success = false
return
}
if(dictionaryValues["airportID"] as! Int == 6605) {
realm.create(Airport.self, value: dictionaryValues, update: true)
}
}
try realm.commitWrite()
}
catch(let e) {
debugPrint(e)
success = false
}
}
return success
}
For the airport entry in question, the dictionary that stores the relevant data looks to have a null value for the key "elevationFt", so I assume things will be OK for the optional Int property
Here is a string version of the dictionary:
["gps_code": 01MD, "ident": 01MD, "iata_code": , "local_code": 01MD, "keywords": , "elevationFt": , "type": seaplane_base, "municipality": Annapolis, "iso_country": US, "airportID": 6605, "longitudeDeg": -76.45600128173828, "latitudeDeg": 38.99919891357422, "iso_region": US-MD, "wikipedia_link": , "name": Annapolis Seaplane Base, "scheduled_service": no, "continent": NA, "home_link": ]
However once the create function starts for this set of data, an exception is thrown:
Terminating app due to uncaught exception 'RLMException', reason: 'Invalid value '' for property 'elevationFt''
I am guessing I have something set up incorrectly, but I am not quite sure how to fix this other than to clean my source data for that particular field.

The screenshot from the debugger shows that elevationFt is an empty string, which is not a number or null, so it is not a valid value for an optional int property.

Related

Terminating app due to uncaught exception 'NSUnknownKeyException' when updating Firebase

When creating a function to add a key-value pair in Firebase I get a crash with the following report
* Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key score.'
* First throw call stack:
(0x1dbd9a3a8 0x1daf9fd00 0x1dbcb36c8 0x1dc796054 0x1dc7ed2a0 0x1dc7070e0 0x102e58cc8 0x102e50640 0x102e4ed18 0x102e4f2c8 0x103f24214 0x103f13f84 0x1daf9b760 0x20906dbf0 0x208af1f94 0x208af22c8 0x208af12dc 0x2090a6a90 0x2090a7cc8 0x209086f50 0x209152150 0x20915490c 0x209154c7c 0x20914d9c4 0x1dbd2a444 0x1dbd2a3c0 0x1dbd29c7c 0x1dbd24950 0x1dbd24254 0x1ddf63d8c 0x20906c4c0 0x102f2befc 0x1db7e0fd8)
libc++abi.dylib: terminating with uncaught exception of type NSException
Message from debugger: failed to send the k packet
The layout of my database is as such:
I believe the problem is I'm calling it to the wrong path in my function.
My functions to update the "score" is :
func updateRecentScore(chatRoomId: String, score: Int) {
let date = dateFormatter().string(from: Date())
firebase.child(kRECENT).queryOrdered(byChild: kCHATROOMID).queryEqual(toValue: chatRoomId).observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
for recent in ((snapshot.value as! NSDictionary).allValues as Array) {
updateRecentScoreItem(recent: recent as! NSDictionary, score: score)
}
}
}
}
func updateRecentScoreItem(recent: NSDictionary, score: Int) {
var score = recent[kSCORE] as! Int
let values = [kSCORE: score] as [String: Any]
firebase.child(kRECENT).child((recent[kRECENTID] as? String)!).updateChildValues(values as [NSObject : AnyObject]) {
(error, ref) -> Void in
if error != nil {
ProgressHUD.showError("Couldnt update recent: \(error!.localizedDescription)")
}
}
}
I call the function to update it here:
let score = recentsRef.child(kRECENTID).value(forKey: kSCORE) as? Int
let newScore = score! + 1
let score1 = firebase.child(kRECENT).queryOrdered(byChild: kCHATROOMID).queryEqual(toValue: chatRoomId).value(forKey: kSCORE) as? Int
let newScore1 = score1! + 1
updateRecentScore(chatRoomId: chatRoomId, score: newScore1)
All help is appreciated as I'm not sure what to do to fix this and I have tried to look everywhere for an answer. If you need any further information please ask.
I think you're looking for this:
func updateRecentScore(chatRoomId: String, score: Int) {
let date = dateFormatter().string(from: Date())
firebase.child(kRECENT).queryOrdered(byChild: kCHATROOMID).queryEqual(toValue: chatRoomId).observeSingleEvent(of: .value) { (snapshot) in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for child in snapshots {
child.ref.child(kSCORE).setValue(score+1)
}
}
}
}
The main differences:
I loop over the child nodes with the built-in children property, which means I get a Snapshot, which allows me to simply look up the ref of each child.
I call setValue() directly on the score property, since you're only updating that property. Using updateChildValues is really only useful if you want to update multiple-but-not-all properties in one go.

Ignore records in FirebaseDB with swift

Im working on a swift app that displays records from firebase to a table view controller.
I have successfully been pulling in all data, But I have since added another field to the DB called "secretKey".. This doesn't not need to be used on the table view as its used elsewhere in the app
But now each time I try to run the app I get the following errors
2018-05-07 12:24:24.616490+0100 Loop Guardian[21847:9857567] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSTaggedPointerString 0xa030818c015ea1f9> valueForUndefinedKey:]: this class is not key value coding-compliant for the key createdAt.'
*** First throw call stack:
(0x18204b164 0x181294528 0x18204ae2c 0x1829fe434 0x182944e20 0x182944874 0x18297d930 0x1030084b0 0x103008740 0x1030f2840 0x1044892cc 0x10448928c 0x10448dea0 0x181ff3344 0x181ff0f20 0x181f10c58 0x183dbcf84 0x18b6695c4 0x10301de44 0x181a3056c)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
This only happens when the secretKey is present in the DB.. If I remove it the app works fine.
I somehow need to ignore this field
Heres the code used to pull the records from the DB
func observeMessages() {
let key = UserDefaults.standard.value(forKey: "uid") as! String
DBrefs.databaseMessages.child(key).observe(.value, with: { (snapshot) in
if snapshot.exists() {
let messageData = snapshot.value as! Dictionary<String, AnyObject>
self.dataArr = []
for (key,data) in messageData {
self.dataArr.append(data)
}
//THIS IS WHERE THE ERROR IS THROWN.. THREAD 1: SIGNAL SIGABRT
self.dataArr = (self.dataArr as NSArray).sortedArray(using: [NSSortDescriptor(key: "createdAt", ascending: true)]) as [AnyObject]
self.tblMessage.reloadData()
}
else {
}
})
}
Any help is appreciated
Thanks
Oliver
for (key,data) in messageData {
if (key == "secreyKey") {
continue;
}
self.dataArr.append(data)
}
Also use Swift method to sort the Array. Don't use NSArray use Array instead.
Like below code:
self.dataArr.sorted(by: {$0["createdAt"] as? Int64 ?? 0 < $1["createdAt"] as? Int64 ?? 0})

object content different from within method

I am encountering a strange behavior with an App I am building.
I have a struct Record and I create an instance of it from a NavigationViewController's child ViewController.
We can see it like this:
NavigationController (called TaskTabsController)
|
ViewController (called TaskFromController)
If I put a breakpoint, I can inspect and check the content of the object is what it should (conform to interface input data).
But when I call a method on that instance (the very next line), the different members have different or missing values.
The object instance is created from within TaskTabsController with something like so:
if let formVC = (viewControllers?[1] as? TaskFormViewController) {
let rec: TaskRecord? = formVC.makeTaskRecord()
// here the rec data are correct when I inspect the instance
rec?.prepareData()
// from the prepareData function, the properties are different of can't be accessed...
}
Ex: From controller, I can see my instance rec having a member task instance (of Task type) with a name property.
But from within the prepareData method, that task member can't display the name attached to it. (debugger says value unreadable or something like that)
Also, I can see a list of other objects, but in the method, their count is different...
Here is how makeTaskRecord method works: (from my TaskFormViewController)
In TaskFormFiewController I have a private property like so:
private var stepsSelected: [StepRecord] = []
That property is updated whit user actions.
The StepRecord is a struct. (so should be passed by value I think)
Next is the makeTaskRecord method in the same controller (gathering details from form elements)
func makeTaskRecord() -> TaskRecord? {
guard let current_task = task
else {
print("TaskForm.makeTaskRecord(): Can't create a record without a Task")
return nil
}
// check data validity
var errors:[String] = []
// generate StepRecords >>>
if stepsSelected.count == 0 {
errors.append("Error, no step selected")
}
// <<<
// DATA OK
if errors.isEmpty {
let taskrecord = TaskRecord(
id: record?.id,
taskId: task!.id,
userCode: Config.getUser()!.code, // global object containing login state
pictures: [],
memo: memo.text,
task: current_task,
stepRecords: stepsSelected // data here is correctly set
)
return taskrecord // but here, I can't retreive the stepRecords! (1)
}
else {
// display errors in an alert box
let errorVC = MyHelpers.makeAlert("Error with data", errors.joined(separator: "\n"))
self.present(errorVC, animated: true)
return nil
}
}
Here is how the prepareData() method looks like: (method of Record class)
func prepareData() -> [String: Any]? {
// no steps in record, data is invalid
guard
let stepRecordsJson = try? JSONEncoder().encode(self.stepRecords)
// This is where I constated that stepRecords is different (set but empty)
else {
return nil
}
// other data
var params = [
"task_id": taskId,
"user_code": userCode,
"step_records": stepRecordsJson, // this is what doesn't work well, but it's certainly related to (1) above
"memo": memo ?? ""
] as [String : Any]
// when editing, we set again the record ID
if let edit_id = self.id {
params["id"] = edit_id
}
return params
}
Note: All the data structures and Codable struct
In the sample above, at the (1), I put a breakpoint on the return line.
When checking data, I see that the "sub-struct" do not have good values:
(lldb) print current_task.name
(String) $R0 = "屋根工事"
(lldb) print taskrecord.task.name
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x6f).
The process has been returned to the state before expression evaluation.
The task is a struct so there should not be problems about it, but the debugger can't read the task I assigned to my taskrecord.
Is there something I am missing? Like a pointer reference of some sort?

Workaround for EKParcipant URL accessing crash?

Some of my users have been sent me logs identifying a EXC_BREAKPOINT (SIGTRAP) Error on this line of code. I've been been trying to make it safe but all of the properties of EKParticipant are non optional so comparing to nil just gives me a warning saying it will always be true. If something is nil here how should I handle it?
Error Line
let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "")
Apple Error Description
Trace Trap [EXC_BREAKPOINT // SIGTRAP]
Similar to an Abnormal Exit, this exception is intended to give an
attached debugger the chance to interrupt the process at a specific
point in its execution. You can trigger this exception from your own
code using the __builtin_trap() function. If no debugger is attached,
the process is terminated and a crash report is generated. Lower-level
libraries (e.g, libdispatch) will trap the process upon encountering a
fatal error. Additional information about the error can be found in
the Additional Diagnostic Information section of the crash report, or
in the device's console. Swift code will terminate with this exception
type if an unexpected condition is encountered at runtime such as:
a non-optional type with a nil value
a failed forced type conversion Look at the Backtraces to determine where the unexpected condition was encountered. Additional
information may have also been logged to the device's console. You
should modify the code at the crashing location to gracefully handle
the runtime failure. For example, use Optional Binding instead of
force unwrapping an optional."
Full Method
/**
Parses participants for a given event.
Goes through the EKEvents attendees array to build Attendee objects used to model a participant.
- parameter event: The calendar event we'll be finding the participants for.
- returns: An array of Attendee objects with the participants name, email, required/optional status and whether they've accepted their invitation to the event.
*/
private static func parseParticipantsIn(event: EKEvent) -> [Attendee] {
var participants = [Attendee]()
if let attendees = event.attendees, event.attendees?.isEmpty == false {
for participant in attendees {
let participantName : String? = parse(EKParticipantName: participant)
let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "")
let isRequiredParticipant : Bool = participant.participantRole == EKParticipantRole.required
let hasAccepted : Bool = participant.participantStatus == EKParticipantStatus.accepted
guard (participantName != nil && participantEmail != nil)
else
{
log.error("Participant could not be parsed")
continue
}
let attendee = Attendee(name: participantName!, email: participantEmail!, required: isRequiredParticipant, hasAccepted: hasAccepted)
participants.append(attendee)
}
}
return participants
}
This appears to be a problem with the EKParticipant.url property. Any attempted access of EKParticipant.url causes a crash if you have a participant with the following email field within it.
"Bill Gates" <billgates#google.com>
I'd guess the quotation marks end the String prematurely. It is fine when accessed from EKParticipant.description so I intend to parse it from there.
This is a ridiculous issue, and Deco pinpointed it exactly. I used a different approach to get around it though: Since I'm already working in a mixed code base (obj-c and Swift), I created a class method on one of my obj-c classes that takes an EKParticipant and returns its URL as a string. Then, in Swift, I call that class method to get the URL instead of directly accessing the property (and crashing). It's hacky, but better than crashing and saved me from parsing the description.
This is rather old question but yet I hit this issue myself. My solution is to fallback to ObjC in order to workaround it.
Just add this ObjC functions to swift bridging header file and you are good to use them in swift.
static inline BOOL
participantHasNonNilURL (EKParticipant* _Nonnull participant) {
return participant.URL != nil;
}
static inline NSURL* _Nullable
participantURL(EKParticipant* _Nonnull participant) {
if (participant.URL != nil) {
return participant.URL;
}else {
return nil;
}
}
Example of usage:
extension EKParticipant {
var optionalURL: URL? {
return participantURL(self)
}
var hasURL: Bool {
return participantHasNonNilURL(self)
}
}
This is still an issue on macOS 11.2... I have reported it to Apple. I encourage anyone else hitting this issue to do the same.
The only Swift-only workaround that worked for me is:
extension EKParticipant {
public var safeURL: URL? {
perform(#selector(getter: EKParticipant.url))?.takeUnretainedValue() as? NSURL? as? URL
}
}
Validations are added incorrectly, please check the below response about how the guard could be used.
if let attendees = event.attendees, event.attendees?.isEmpty == false {
for participant in attendees {
guard let participantName : String? = parse(EKParticipantName: participant) else{
log.error("error in participant name")
return
}
guard let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "") else{
log.error("error in participant email")
return
}
let isRequiredParticipant : Bool = participant.participantRole == EKParticipantRole.required
let hasAccepted : Bool = participant.participantStatus == EKParticipantStatus.accepted
/* guard validation is not required here */
if (participantName != nil && participantEmail != nil){
let attendee = Attendee(name: participantName!, email: participantEmail!, required: isRequiredParticipant, hasAccepted: hasAccepted)
participants.append(attendee)
}
}
}
return participants

Updating object with primary key not working

This is the object I created with primary key username and the error when I try to run the add object code.
class Login_item: Object {
dynamic var username = String()
dynamic var password = String()
override static func primaryKey() -> String? {
return "username"
}
}
This is the code that add new object and update object.
func save_login_info() {
let new_login_entry = Login_item()
new_login_entry.username = Username.text!
new_login_entry.password = Password.text!
let realm = try! Realm()
try! realm.write {
realm.add(new_login_entry)
print("Login info save as: \(Realm.Configuration.defaultConfiguration.fileURL!)")
}
try! realm.write {
realm.add(new_login_entry, update: true)
}
}
Error I got when execute the code.
fatal error: 'try!' expression unexpectedly raised an error: Error Domain=io.realm Code=10 "Migration is required due to the following errors:
- Property 'username' has been made a primary key." UserInfo={NSLocalizedDescription=Migration is required due to the following errors:
- Property 'username' has been made a primary key., Error Code=10}: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-703.0.18.8/src/swift/stdlib/public/core/ErrorType.swift, line 54
Yeah you need to add migration instruction when you change the model, thats what the error message is describing.
let config = Realm.Configuration(schemaVersion: 1, migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion < 1 {
}
})
The 1 in the code represents the current version, and every time you make any changes to the code you need to increase the version number and do any changes to the data that you want to.
You can read more about it here