astoundingly, typing this error into the Stack Overflow search returns no results that actually mention this error 0.0
So I have an Event object in my Swift IOS App which is saved as a document on Firestore that looks like this
The start and end fields are Timestamps.
Over on xcode when the Event collection is queried, the results are decoded into Events with this initialiser
init(document: DocumentSnapshot) {
self.id = document.documentID
let d = document.data()
self.title = d!["title"] as? String
let stamp = d!["start"] as? Timestamp
let estamp = d!["end"] as? Timestamp
self.start = stamp?.dateValue()
self.end = estamp?.dateValue()
/*
There is a breakpoint here!
*/
self.creator = d!["user"] as? String
self.isAllDay = (d!["isAllDay"] as? Bool)!
self.isPrivate = d!["isPrivate"] as! Bool
self.count = (d!["count"] as? String)!
self.date = d?["day"] as? String
self.month = d?["month"] as? String
self.year = d?["year"] as? String
self.bridgesDays = doesEventBridgeDays()
//MARK: re-implement these functions
isInvitee()
}
I've just swapped this over from using Strings to Timestamps and i now have unexpectedly found nil errors for the start and end fields on the app.
a breakpoint shows me this:
As you can see, the start and end fields now say Failed to get the 'some' field from optional start/end (start and end are now both Date objects)
I either don't understand what i'm reading online, or there are no questions/blog posts etc about this on the internet so
what does this mean?
and how do I fix it?
happy to answer any further questions to help resolve this issue :)
Thanks :)
Extra information*
This is failing because your Event object in code is trying to store your 'start' and 'end' properties as a Date? but you're retrieving them from Firebase Firestore as a Timestamp.
You'll need to do an intermediate step to unwrap the values and then get the date object.
if let end = data["end"] as? Timestamp {
self.end = end.dateValue()
}
Also, you should really be unwrapping the document.data() safely to avoid using the d! and risking a crash.
if let data = document.data(){
// extract values
}
Finally, it may be worth reading the document on sending / retrieving custom objects with Firestore. It makes everything a lot easier.
Firestore.firestore().collection("Event").document(id).getDocument { (document, error) in
if error != nil {
print(error?.localizedDescription as Any)
}
guard let document = document else {
// failed to unwrap document
return
}
if document.exists {
let result = Result {
try document.data(as: Event.self)
}
switch result {
case .success(let event):
if let event = event {
// do something with Event object
} else {
// failed to unwrap Event
}
case .failure:
// failed to form Event
}
} else {
// document doesn't exist
}
}
Related
My app uses CoreData + CloudKit synchronization. Some CoreData entities like Item can be shared via iCloud's shared database. The app uses only 1 NSPersistentContainer, but it has 2 NSManagedContexts, the visualContext and a backgroundContext.
Thus during saving of a context, 2 types of merging conflicts can arise: 1) If both contexts try to save the same Item in different states, and 2) If my persistent container and iCloud sync try to save the same Item in different states.
Item has an attribute updatedAt, and the app requires that always the Item version updated last should be saved.
For consistency reasons, I cannot merge by property. Only complete Item objects can be stored, either one of both stored in a managed context, or either the one stored in a managed context or the one persistently stored.
But the standard merge policies cannot be used: NSRollbackMergePolicy ignores changes in a managed context, and takes the persistent copy, while NSOverwriteMergePolicy overwrites the persistent store with the object in the managed context. But I have to use the Item with the newest updatedAt. Thus I have to use a custom merge policy.
It was not easy to find any hint how to do this. I found two tutorials with demo code. The best one is the book Core Data by Florian Kugler and Daniel Eggert that has a section about Custom Merge Policies, and related code here. The other is a post by Deepika Ramesh with code. However I have to admit, I did not understand both fully. But based on their code, I tried to setup my own custom merge policy, that will be assigned to the mergePolicy property of both managed contexts. Here it is:
import CoreData
protocol UpdateTimestampable {
var updatedAt: Date? { get set }
}
class NewestItemMergePolicy: NSMergePolicy {
init() {
super.init(merge: .overwriteMergePolicyType)
}
override open func resolve(optimisticLockingConflicts list: [NSMergeConflict]) throws {
let nonItemConflicts = list.filter({ $0.sourceObject.entity.name != Item.entityName })
try super.resolve(optimisticLockingConflicts: nonItemConflicts)
let itemConflicts = list.filter({ $0.sourceObject.entity.name == Item.entityName })
itemConflicts.forEach { conflict in
guard let sourceObject = conflict.sourceObject as? UpdateTimestampable else { fatalError("must be UpdateTimestampable") }
let key = "updatedAt"
let sourceObjectDate = sourceObject.updatedAt ?? .distantPast
let objectDate = conflict.objectSnapshot?[key] as? Date ?? .distantPast
let cachedDate = conflict.cachedSnapshot?[key] as? Date ?? .distantPast
let persistedDate = conflict.persistedSnapshot?[key] as? Date ?? .distantPast
let latestUpdateAt = [sourceObjectDate, objectDate, cachedDate, persistedDate].max()
let persistedDateIsLatest = persistedDate == latestUpdateAt
let sourceObj = conflict.sourceObject
if let context = sourceObj.managedObjectContext {
context.performAndWait {
context.refresh(sourceObj, mergeChanges: !persistedDateIsLatest)
}
}
}
try super.resolve(optimisticLockingConflicts: itemConflicts)
}
}
My first question is if this code makes sense at all. I am asking this because merging conflicts are hard to test.
Specifically, I have apparently to use any of the standard merging properties in super.init(merge: .overwriteMergePolicyType), although is is apparently not important which one, since I am using custom merge conflict resolution.
The code in the question is wrong:
It filters out first conflicts for non-Item objects, and calls super for them. This is correct.
Then it loops over conflicts for Item objects to resolve them.
There, it first applies the default merge policy (super) and then refreshes the object in the context where merging is done if the persistent snapshot is newest. One reason why this is wrong is that the persistent snapshot can be nil.
A correct resolution requires:
to find first the properties of the latest updatedAt (it can be kept in the source object, the object snapshot, the cached snapshot or in the persistent snapshot),
to store these properties,
to apply the default merge policy on that the custom merge policy is based,
to set if required the objects properties to the stored newest values.
Only then is the conflict resolved.
The correct implementation that I am using now is:
override func resolve(optimisticLockingConflicts list: [NSMergeConflict]) throws {
for conflict in list {
let sourceObject = conflict.sourceObject
// Only UpdateTimestampable objects can use the custom merge policy. Other use the default merge policy.
guard sourceObject is UpdateTimestampable else {
try super.resolve(optimisticLockingConflicts: [conflict])
continue
}
let newestSnapshot = conflict.newestSnapShot
if let sourceObject = sourceObject as? Item {
let fixedAtTopAt: Date?
let howOftenBought: Int32
let lastBoughtDate: Date?
let name: String
let namesOfBuyPlaces: Set<String>?
let status: Int16
let updatedAt: Date?
let sourceObjectUpdatedAt = sourceObject.updatedAt ?? .distantPast
if sourceObjectUpdatedAt >= newestSnapshot?["updatedAt"] as? Date ?? .distantPast {
fixedAtTopAt = sourceObject.fixedAtTopAt
howOftenBought = sourceObject.howOftenBought
lastBoughtDate = sourceObject.lastBoughtDate
name = sourceObject.name
namesOfBuyPlaces = sourceObject.namesOfBuyPlaces
status = sourceObject.status
updatedAt = sourceObject.updatedAt
} else {
fixedAtTopAt = newestSnapshot?["fixedAtTopAt"] as? Date
howOftenBought = newestSnapshot?["howOftenBought"] as! Int32
lastBoughtDate = newestSnapshot?["lastBoughtDate"] as? Date
name = newestSnapshot?["name"] as! String
namesOfBuyPlaces = newestSnapshot?["namesOfBuyPlaces"] as? Set<String>
status = newestSnapshot?["status"] as! Int16
updatedAt = newestSnapshot?["updatedAt"] as? Date
}
// Here, all properties of the newest Item or Item snapshot have been stored.
// Apply now the default merging policy to this conflict.
try super.resolve(optimisticLockingConflicts: [conflict])
// Overwrite now the source object's properties where necessary
if sourceObject.fixedAtTopAt != fixedAtTopAt { sourceObject.fixedAtTopAt = fixedAtTopAt }
if sourceObject.howOftenBought != howOftenBought { sourceObject.howOftenBought = howOftenBought }
if sourceObject.lastBoughtDate != lastBoughtDate { sourceObject.lastBoughtDate = lastBoughtDate }
if sourceObject.name != name { sourceObject.name = name }
if sourceObject.namesOfBuyPlaces != namesOfBuyPlaces { sourceObject.namesOfBuyPlaces = namesOfBuyPlaces }
if sourceObject.status != status { sourceObject.status = status }
if sourceObject.updatedAt != updatedAt { sourceObject.updatedAt = updatedAt }
continue
} // source object is an Item
if let sourceObject = conflict.sourceObject as? Place {
// code for Place object …
}
}
}
Here, newestSnapShot is an NSMergeConflict extension:
extension NSMergeConflict {
var newestSnapShot: [String: Any?]? {
guard sourceObject is UpdateTimestampable else { fatalError("must be UpdateTimestampable") }
let key = Schema.UpdateTimestampable.updatedAt.rawValue
/* Find the newest snapshot.
Florian Kugler: Core Data:
Note that some of the snapshots can be nil, depending on the kind of conflict you’re dealing with.
For example, if the conflict occurs between the context and the row cache, the persisted snapshot will be nil.
If the conflict happens between the row cache and the persistent store, the object snapshot will be nil.
*/
let objectSnapshotUpdatedAt = objectSnapshot?[key] as? Date ?? .distantPast
let cachedSnapshotUpdatedAt = cachedSnapshot?[key] as? Date ?? .distantPast
let persistedSnapshotUpdatedAt = persistedSnapshot?[key] as? Date ?? .distantPast
if persistedSnapshotUpdatedAt >= objectSnapshotUpdatedAt && persistedSnapshotUpdatedAt >= cachedSnapshotUpdatedAt {
return persistedSnapshot
}
if cachedSnapshotUpdatedAt >= persistedSnapshotUpdatedAt && cachedSnapshotUpdatedAt >= objectSnapshotUpdatedAt {
return cachedSnapshot
}
if objectSnapshotUpdatedAt >= persistedSnapshotUpdatedAt && objectSnapshotUpdatedAt >= cachedSnapshotUpdatedAt {
return objectSnapshot
}
fatalError("No newest snapshot found")
}
}
I've got this issue when running my code to update a few records. I have 138 records.
I've set limit to get 1000 record per one request and then I've tried to update my new column I've created:
But I get this error when I save PFObject in background
Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.
I found this link with similar problem, but looks like that ticket is resolved and closed.
Looking in my code below I am trying to cycle over 138 records to set new key value for PFObject and then I save it. Normally I don't need such operation, maybe this happens because of lot of records were updated at once. But just wonder if this is still the case in api.
This is my code:
let salaryRepository = SalaryDataRepository(remoteDataSource: SalaryRemoteDataSource(), localDataSource: SalaryLocalDataSource())
salaryRepository.getSalaries(with: nil, payPeriod: nil, paidDateDoesNotExist: false, limit: 0, skip: 0, completion: { (result) in
switch result {
case let .success(salaries):
guard let unwrappedSalaries = salaries else {
return
}
var counter = 0
for salary in unwrappedSalaries {
var totalQuote1 = 0.0
if let pfSalary = salary.getPFSalary() {
if let subTotal = pfSalary["subTotal"] as? NSNumber, let unwrappedCustomExchangeRate = pfSalary["customExchangeRate"] as? NSNumber {
if let pfProjectEmployee = pfSalary["projectEmployee"] as? PFObject {
if let pfProjectEmployeeDetails = pfProjectEmployee["projectEmployeeDetails"] as? PFObject {
if let transactionFee = pfProjectEmployeeDetails["transactionFee"] as? NSNumber {
let subTotalQuote = NSNumber.getMultiplying(a: subTotal, b: unwrappedCustomExchangeRate)
let totalQuote = subTotalQuote.afterFee(fee: transactionFee)
pfSalary["totalQuote"] = totalQuote
totalQuote1 = totalQuote.doubleValue
print(transactionFee)
counter = counter + 1
}
}
}
pfSalary.saveInBackground { (success, error) in
if error == nil && success == true {
print("SUCCESS:")
print(totalQuote1)
} else {
print("ERROR:")
print(totalQuote1)
}
}
}
}
}
print("total updated:")
print(counter)
break
case let .failure(error):
print(error)
break
}
})
I have some code that reads data from Firebase on a custom loading screen that I only want to segue once all of the data in the collection has been read (I know beforehand that there won't be more than 10 or 15 data entries to read, and I'm checking to make sure the user has an internet connection). I have a loading animation I'd like to implement that is started by calling activityIndicatorView.startAnimating() and stopped by calling activityIndicatorView.stopAnimating(). I'm not sure where to place these or the perform segue function in relation to the data retrieval function. Any help is appreciated!
let db = Firestore.firestore()
db.collection("Packages").getDocuments{(snapshot, error) in
if error != nil{
// DB error
} else{
for doc in snapshot!.documents{
self.packageIDS.append(doc.documentID)
self.packageNames.append(doc.get("title") as! String)
self.packageIMGIDS.append(doc.get("imgID") as! String)
self.packageRadii.append(doc.get("radius") as! String)
}
}
}
You don't need to know the progress of the read as such, just when it starts and when it is complete, so that you can start and stop your activity view.
The read starts when you call getDocuments.
The read is complete after the for loop in the getDocuments completion closure.
So:
let db = Firestore.firestore()
activityIndicatorView.startAnimating()
db.collection("Packages").getDocuments{(snapshot, error) in
if error != nil{
// DB error
} else {
for doc in snapshot!.documents{
self.packageIDS.append(doc.documentID)
self.packageNames.append(doc.get("title") as! String)
self.packageIMGIDS.append(doc.get("imgID") as! String)
self.packageRadii.append(doc.get("radius") as! String)
}
}
DispatchQueue.main.async {
activityIndicatorView.stopAnimating()
}
}
As a matter of style, having multiple arrays with associate data is a bit of a code smell. Rather you should create a struct with the relevant properties and create a single array of instances of this struct.
You should also avoid force unwrapping.
struct PackageInfo {
let id: String
let name: String
let imageId: String
let radius: String
}
...
var packages:[PackageInfo] = []
...
db.collection("Packages").getDocuments{(snapshot, error) in
if error != nil{
// DB error
} else if let documents = snapshot?.documents {
self.packages = documents.compactMap { doc in
if let title = doc.get("title") as? String,
let imageId = doc.get("imgID") as? String,
let radius = doc.get("radius") as? String {
return PackageInfo(id: doc.documentID, name: title, imageId: imageId, radius: radius)
} else {
return nil
}
}
}
There is no progress reporting within a single read operation, either it's pending or it's completed.
If you want more granular reporting, you can implement pagination yourself so that you know how many items you've already read. If you want to show progress against the total, this means you will also need to track the total count yourself though.
I'm capturing Firestore data as Firebase shows us, but I don't know how to save the query I make.
In conclusion, what I want to do is bring all the documents that have the same value in your pid field, and then show in a table the product fields and start date, each document in a different cell.
collection food
document: 1
pid:john1
product:Ice
startDate:01/01/2010
document: 2
pid:john1
product:Rice
startDate:01/02/2010
I need to show in the table:
Ice was bought on 01/01/2010
Rice was bought on 01/02/2010
I have this code:
func loadFood(){
pid = "john1"
db = Firestore.firestore()
db.collection("food").whereField("pid", isEqualTo: pid)
.addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("\n--------------------------------------")
print("Error document: \(error!)")
print("--------------------------------------\n")
return
}
let startDate = documents.map { $0["startDate"]! }
let product = documents.map {$0["product"]!}
let message = ("\(product) was bought \(startDate)")
self.dataRecord.insert(message, at: 0)
DispatchQueue.main.async {
self.tvRecord.reloadData()
}
}
}
I'm showing in the table:
[Ice, Rice] was bought on [01/01/2010, 01/02/2010]
You make a couple of mistakes. First, you loop over the documents multiple times, unnecessarily; that's not very efficient. In your case, you should loop over them once and do all of your data prep in each loop iteration. Second, Firestore has a method specifically for extracting data from document fields called get() which is very easy to read and efficient.
func loadFood(){
pid = "john1"
Firestore.firestore().collection("food").whereField("pid", isEqualTo: pid).addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("\n--------------------------------------")
print("Error document: \(error!)")
print("--------------------------------------\n")
return
}
for doc in documents {
guard let startDate = doc.get("startDate") as? String,
let product = doc.get("product") as? String else {
continue // continue this loop, not "return" which will return control out of the calling function
}
let message = "\(product) was bought \(startDate)"
dataRecord.append(message)
}
DispatchQueue.main.async {
self.tvRecord.reloadData()
}
}
}
As you deal with the 2 arrays as if they are 1 string instead you need
struct Item {
let message,startDate:String
}
Then
var dataRecord = [Item]()
and finally
self.dataRecord = documents.map { Item(message:$0["product"]!,startDate:$0["startDate"]!)}
I'm having my string declared as,
var firstName = String()
and I'm assigning value from parsed JSON content like,
firstName = json["first_name"].stringValue
But sometimes, there might be empty values in the JSON response and the app is crashing, I read about guard statement and if statement to check empty values, but that requires the declaration format to be changed, couldn't find a right way to handle this error without changing the declaration format.
since I have declared all the variables in my app with similar formats, changing that requires time, I'm in the verge of uploading my app, this is my first swift app, if my declaration format is wrong please answer why it is, can someone help me out of this?
Code as of Swift 4:
Keep in mind that when you are using ".stringValue", it is almost the same as using a "!" which will force a crash on nil.
if let firstName = json["first_name"]as? String {
//do stuff like
self.firstName = firstName
}
This will unwrap it to where you can get at the value if it isn't null and can be a string.
Guard let's are really good for this though as you can account for it in the beginning and you can assume that it is not optional for the entire scope.
guard let firstName = json["first_name"]as? String else {return}
self.firstName = firstName
In addition, you could always check for nulls in one line and assign a default value if a nil value occurs.
self.firstName = (json["first_name"]as? String) ?? "Default String"
You can use next statement:
guard let firstName = json["first_name"].stringValue else { // Do sth if nil }
// Do sth if not nil
Or you could use statement, which you wrote, but you should check variable
firstName like this:
guard firstName != nil else { // Do sth if nil }
// Do sth if not nil
Or
if firstName != nil {
// Do sth if not nil
}
You can use guard statement also,
guard let firstName = json["first_name"] else {
print("FirstName is Empty")
return
}
or you can check with if also,
if let firstName = json["first_name"] {
//Your code goes here
}
You can do that the following way:
if let dictionary = json {
if let fName = dictionary["first_name"] {
firstName = fName as? String
}
}
I guest you use SwiftyJSON. The function stringValue always return String object. It's impossible to be nil. It sounds like the response data which is not valid JSON format, so it crashed.
My snippet codes.
// Alarmofire + SwiftyJSON
let request: DataRequest = ...//< Configure Alarmofire DataRequest
request.response() { (resp) in
if let error = resp.error {
// TODO: Error handle
return
}
if (response.data == nil) {
// TODO: error handle for empty data?
return
}
assert(response.data != nil, "In this place, the `data` MUST not be nil.")
let json = try? JSON(data: response.data!)
if (json == nil) {
// TODO: Error handle for invalid JSON format.
// If json is nil, then `json["first_name"]` will lead to crash.
return
}
// TODO: parse json object
let firstNameStr = json["first_name"].stringValue
}