How to create negative Firebase timestamp in swift - ios

For an iOS app I am working on, I need to fetch messages in descending order i.e the latest message comes first, followed by second newest message etc.
From looking at other SO answers and research it seems that the best approach for my situation is to create a negative timestamp and then persist that to the database as an extra property to messages.
I will then use queryOrderedByChild('negativeTimestamp') to fetch the messages in a observeSingleEvent and then have a childAdded observer to handle messages which are sent once initial calls are made.
In the firebase documentation it says I can get the server timestamp value from this snippet of code firebase.database.ServerValue.TIMESTAMP
How do I write this for Swift 3?

First, see the linked answer in the comments. That answer relies on the client to generate a timestamp that's made negative and written to Firebase.
If you want to have Firebase generate a timestamp, that can be done as well with this little snappy Firebase structure and piece of code.
First, let's take a look at the structure
root
parent_node
-Y8j8a8jsjd0adas
time_stamp: -1492030228007
timestamp: 1492030228007
Next, some code to create and work with that structure:
Define a var we can use within our class that references the Firebase time stamp
let kFirebaseServerValueTimestamp = [".sv":"timestamp"]
and a function that adds an observer to the timestamp node:
func attachObserver() {
let timestampRef = self.ref.child("timestamp")
let parentNodeRef = self.ref.child("parent_node")
var count = 0
timestampRef.observe(.value, with: { snapshot in
if snapshot.exists() {
count += 1
if count > 1 {
let ts = snapshot.value as! Int
let neg_ts = -ts
let childNodeRef = parentNodeRef.childByAutoId()
let childRef = childNodeRef.child("time_stamp")
childRef.setValue(neg_ts)
count = 0
}
}
})
And a function that writes out a timestamp, therefore causing the observer to fire which creates child nodes within the parent_node based on the Firebase time stamp
func doTimestamp() {
let timestampRef = self.ref.child("timestamp")
timestampRef.setValue(kFirebaseServerValueTimestamp)
}
Here's the rundown.
In the attachObserver function, we attach an observer to the timestamp node - that node may or may not exist but if it doesn't it will be created - read on. The code in the closure is called any time an event occurs in the timestamp node.
When the doTimestamp function is called, it creates and writes a timestamp to the timestamp node, which then fires the observer we attached in attachObserver.
The code in the observe closure does the following:
Make sure the snapshot contains something, and if it does, increment a counter (more on that in a bit). If the counter is greater than 1 get the timestamp as an integer from the snapshot. Then, create it's negative and write it back out to Firebase as a child of parent_node.
How this would apply would be anytime you want to timestamp a child node with a Firebase generated timestamp but negative value for reverse loading/sorting - which speaks to the OP question.
The gotcha here is that when this happens
timestampRef.setValue(kFirebaseServerValueTimestamp)
It actually writes twice to the node, which would cause the code in the closer to be called twice.
Maybe a Firebaser can explain that, but we need to ignore the first event and capture the second, which is the actual timestamp.
So the first event will cause the observer closer to fire, making count = 1, which will be ignored due to the if statement.
Then the second event fires, which contains the actual timestamp, and that's what we use to make negative and write out to Firebase.
Hope this helps the OP and the commenters.

Regardless whether it's for Swift or not, another conceptual solution is to rely on Firebase's server time offset value.
It's not as precise as firebase.database.ServerValue.TIMESTAMP, but the difference is usually within milliseconds. The advantage is that it lets you create a negative timestamp on the client without having to update your Firebase node twice.
You grab the server time offset value when you need it from Firebase, generate the negative timestamp on the client, and then save your object in Firebase once.
See:
https://groups.google.com/forum/#!topic/firebase-talk/EXMbZmyGWgE
https://firebase.google.com/docs/database/ios/offline-capabilities#clock-skew (for iOS).
https://firebase.google.com/docs/database/web/offline-capabilities#clock-skew (for web).
var offsetRef = firebase.database().ref(".info/serverTimeOffset");
offsetRef.on("value", function(snap) {
var offset = snap.val();
var negativeTimestamp = (new Date().getTime() + offset) * -1;
});

Related

Safety of using an empty reference instance across multiple threads

Background
I have a class Data that stores multiple input parameters and a single output value.
The output value is recalculated whenever one of the input parameters is mutated.
The calculation takes a non-trivial amount of time so it is performed asynchronously.
If one of the input parameters changes during recalculation, the current calculation is cancelled, and a new one is begun.
The cancellation logic is implemented via a serialized queue of calculation operations and a key (reference instance) (Data.key). Data.key is set to a new reference instance every time a new recalculation is added to the queue. Also only a single recalculation can occur at a time — due to the queue. Any executing recalculation constantly checks if it was the most recently initiated calculation by holding a reference to both the key that what was created with it when it was initiated and the currently existing key. If they are different, then a new recalculation has been queued since it began, and it will terminate.
This will trigger the next recalculation in the queue to begin, repeating the process.
The basis for my question
The reassignment of Data.key is done on the main thread.
The current calculation constantly checks to see if its key is the same as the current one. This means another thread is constantly accessing Data.key.
Question(s)
Is it safe for me to leave Data.key vulnerable to being read/written to at the same time?
Is it even possible for a property to be read and written to simultaneously?
Yes Data.Key vulnerable to being read/written to at the same time.
Here is example were i'm write key from main thread and read from MySerialQueue.
If you run that code, sometimes it would crash.
Crash happens because of dereference of pointer that point to memory released during writing by main queue.
Xcode have feature called ThreadSanitizer, it would help to catch such problems.
Discussion About Race condition
func experiment() {
var key = MyClass()
var key2 = MyClass()
class MyClass {}
func writer() {
for _ in 0..<1000000 {
key = MyClass()
}
}
func reader() {
for _ in 0..<1000000 {
if key === key2 {}
}
}
DispatchQueue.init(label: "MySerialQueue").async {
print("reader begin")
reader()
print("reader end")
}
DispatchQueue.main.async {
print("writer begin")
writer()
print("writer end")
}
}
Q:
Is it safe for me to leave Data.key vulnerable to being read/written to at the same time?
A:
No
Q:
Is it even possible for a property to be read and written to simultaneously?
A:
Yes, create a separate queue for the Data.Key that only through which you access it. As long as any operation (get/set) is restricted within this queue you can read or write from anywhere with thread safety.

What's the right way to do an initial load of list data in Firebase and Swift?

Every firebase client example I see in Swift seems to oversimplify properly loading data from Firebase, and I've now looked through all the docs and a ton of code. I do admit that my application may be a bit of an edge case.
I have a situation where every time a view controller is loaded, I want to auto-post a message to the room "hey im here!" and additionally load what's on the server by a typical observation call.
I would think the flow would be:
1. View controller loads
2. Auto-post to room
3. Observe childAdded
Obviously the calls are asynchronous so there's no guarantee the order of things happening. I tried to simplify things by using a complete handler to wait for the autopost to come back but that loads the auto-posted message twice into my tableview.
AutoPoster.sayHi(self.host) { (error) in
let messageQuery = self.messageRef.queryLimited(toLast:25).queryOrdered(byChild: "sentAt")
self.newMessageRefHandle = messageQuery.observe(.childAdded, with: { (snapshot) in
if let dict = snapshot.value as? [String: AnyObject] {
DispatchQueue.main.async {
let m = Message(dict, key: snapshot.key)
if m.mediaType == "text" {
self.messages.append(m)
}
self.collectionView.reloadData()
}
}
})
}
Worth noting that this seems very inefficient for an initial load. I fixed that by using a trick with a timer that will basically only allow the collection view to reload maximum every .25s and will restart the timer every time new data comes in. A bit hacky but I guess the benefits of firebase justify the hack.
I've also tried to observe the value event once for an initial load and then only after that observe childAdded but I think that has issues as well since childAdded is called regardless.
While I'm tempted to post code for all of the loading methods I have tried (and happy to update the question with it), I'd rather not debug what seems to not be working and instead have someone help outline the recommended flow for a situation like this. Again, the goal is simply to auto-post to the room that I joined in the conversation, then load the initial data (my auto-post should be the most recent message), and then listen for incoming new messages.
Instead of
self.newMessageRefHandle = messageQuery.observe(.childAdded, with: { (snapshot) in
try replacing with
let childref = FIRDatabase.database().reference().child("ChildName")
childref.queryOrdered(byChild:"subChildName").observe(.value, with: { snapshot in

FEventType.ChildAdded event only fired once

my firebase data structure looks like the following
user
|__{user_id}
|__userMatch
|__{userMatchId}
|__createdAt: <UNIX time in milliseconds>
I'm trying to listen for the child added event under userMatch since a particular given time. Here's my swift code:
func listenForNewUserMatches(since: NSDate) -> UInt? {
NSLog("listenForNewUserMatches since: \(since)")
var handle:UInt?
let userMatchRef = usersRef.childByAppendingPath("\(user.objectId!)/userMatch")
var query = userMatchRef.queryOrderedByChild("createdAt");
query = query.queryStartingAtValue(since.timeIntervalSince1970 * 1000)
handle = query.observeEventType(FEventType.ChildAdded, withBlock: { snapshot in
let userMatchId = snapshot.key
NSLog("New firebase UserMatch created \(userMatchId)")
}, withCancelBlock: { error in
NSLog("Error listening for new userMatches: \(error)")
})
return handle
}
What's happening is that the event call back is called only once. Subsequent data insertion under userMatch didn't trigger the call. Sort of behaves like observeSingleEventOfType
I have the following data inserted into firebase under user/{some-id}/userMatch:
QGgmQnDLUB
createdAt: 1448934387867
bMfJH1bzNs  
createdAt: 1448934354943
Here are the logs:
2015-11-30 17:32:38.632 listenForNewUserMatches since:2015-12-01 01:32:37 +0000
2015-11-30 17:45:55.163 New firebase UserMatch created bMfJH1bzNs
The call back was fired for bMfJH1bzNs but not for QGgmQnDLUB which was added at a later time. It's very consistent: after opening the app, it only fires for the first event. Not sure what I'm doing wrong here.
Update: Actually the behavior is not very consistent. Sometimes the call back is not fired at all, not even once. But since I persist the since time I should use when calling listenForNewUserMatches function. If I kill the app and restart the app, the callback will get fired (listenForNewUserMatches is called upon app start), for the childAdded event before I killed the app. This happens very consistently (callback always called upon kill-restart the app for events that happened prior to killing the app).
Update 2: Don't know why, but if I add queryLimitedToLast to the query, it works all the time now. I mean, by changing userMatchRef.queryOrderedByChild("createdAt") to userMatchRef.queryOrderedByChild("createdAt").queryLimitedToLast(10), it's working now. 10 is just an arbitrary number I chose.
I think the issue comes from the nature of time based data.
You created a query that says: "Get me all the matches that happened after now." This should work when the app is running and new data comes in like bMfJH1bzNs. But older data like QGgmQnDLUB won't show up.
Then when you run again, the since.timeIntervalSince1970 has changed to a later date. Now neither of the objects before will show up in your query.
When you changed your query to use queryLimitedToLast you avoided this issue because you're no longer querying based on time. Now your query says: "Get me the last ten children at this location."
As long as there is data at that location you'll always receive data in the callback.
So you either need to ensure that since.timeIntervalSince1970 is always earlier than the data you expect to come back, or use queryLimitedToLast.

how to fix firebase local cache corruption

I was trying to read the old value from firebase, update it, then save it back with the following code:
var unreadChatsRef = usersRef.childByAppendingPath("\(user.objectId!)/unreadChats/\(self.chat.objectId!)")
unreadChatsRef.observeSingleEventOfType(FEventType.Value, withBlock: { (snapshot) -> Void in
if snapshot.exists() {
var msgCnt = snapshot.value as? Int
msgCnt = (msgCnt ?? 0) + 1
unreadChatsRef.setValue(msgCnt)
}
})
Previously I made a mistake and used observeEventType instead of single event so by saving the value, it triggers itself to update the value again and again. I stopped my iOS program while seeing this in debug session. Somehow none of the subsequent updates made it to the firebase server. I had the local cache enabled by calling Firebase.defaultConfig().persistenceEnabled = true. So I end up with a cached value different from the value on the server.
What puzzles me is that even if my other iOS chat client updates that number, this iOS device (the code above) continues to see the cached value and can never fetch the correct value from the server. Am I doing something wrong? How do I tell my program to invalidate the cache and fetch a fresh copy from the server?

Save 2 different PFObjects eventually or fail both

Using Parse SDK for iOS, I have 2 tables :
Game
- UserA : Pointer <_User>
- UserB : Pointer <_User>
- Round : Number
- IsTurnOfUserA : Bool
RoundScore
- GameId : Pointer <Game>
- User : Pointer <_User>
- Score : Number
- etc
A game is done in 3 rounds between 2 users.
When a user ends a round, it toggles Game.IsTurnOfUserA and saves the score for the round to RoundScore table.
In iOS, I didn't find a way to update Game table AND save a RoundScore eventually (maybe later if there is no network).
Both must be done or none at all, but I don't want to end up with only one of the 2 query to be successful and the other one failed.
With Cloud Code, it should be easy to do so but there is no call eventually function.
Update: Maybe there is something to try with Parse's local database ? But I don't know that tool yet.
Important: RoundScore has a field that depends on Game. If Game Object is new, it doesn't have an ObjectId yet, but I still need to link it to the RoundScore Object.
Unfortunately it isn't possible with saveEventually.
What you would need to do is implement your own network checking code and call a cloud method that will save both. That would be the best option.
A hack you could try as an alternative is to save the combined data to another class and have a background job on the server turn that single temporary row into a row in each table, then remove the temporary row.
The drawbacks of this hack is that the background job can run every 15 minutes only, so there might be up-to 15 minutes delay. It also adds extra complexity and overhead to your app.
As Timothy Walters suggested, here is the hack without any background job:
I created a fake table that has the columns of both tables Game and RoundScore
GameAndRoundScore
- Game_UserA
- Game_UserB
- RoundScore_GameId
- RoundScore_Score
- etc
In Cloud Code, I added this function before save
Parse.Cloud.beforeSave("GameAndScoreRound", function(request, response) {
var Game = Parse.Object.extend("Game");
var game = new Game();
game.set("UserA", request.object.get("game_UserA"));
game.set("UserB", request.object.get("game_UserB"));
game.save.then(function(newGame) {
var RoundScore = Parse.Object.extend("RoundScore");
var roundScore = new RoundScore();
//roundScore.set(...)
return scoreRound.save();
}).then(function(newRoundScore) {
response.success();
});
});
Then for the fake table data, I can either leave it as it is or set a background job that empties it or even empty the table manually on the Parse Backend.

Resources