I have an app where when a user clicks on a UIButton,
1. I update a child ( say tapCount) in Firebase
Load a ViewController with an image.
I am using a transactionBlock to increment this count as multiple users could be incrementing this count at the same time. What I notice is because there is a slight delay in completing this Firebase transaction, the app waits to complete it and then loads the View Controller when an image is clicked. It almost makes the app a little sluggish. So what's the recommended way to do this without causing any UI issues ? Is there a way to run this in a background thread ? Thanks
func incrementTapCount() {
_tapCount += 1
// update the link
ref.runTransactionBlock { (currentData:MutableData) -> TransactionResult in
currentData.childData(byAppendingPath: "tapCount").value = self._tapCount
return TransactionResult.success(withValue: currentData)
}
}
func moreCommentAction(_ sender: AnyObject) {
let postCategory = self.globalPost.postCat
if Auth.auth().currentUser?.uid != nil {
//user is logged in
// this increments the Like count when a button is tapped.
self.globalPost.incrementTapCount()
// let likeCount = self.globalPost.favoriteBoost + self.globalPost.favoriteDict.count + self.globalPost.tapCount
// likeButton.setTitle(" \(likeCount)", for: UIControlState())
}
else {
//user is not logged in, so skip incrementing the tapCount.
}
let moreCommentVC = sender.storyboard?!.instantiateViewController(withIdentifier: "MoreCommentViewController") as! MoreCommentViewController
moreCommentVC.globalPost = self.globalPost
sender.present(moreCommentVC, animated: true, completion: nil)
}
I can see the same issue with obj-c. runTransactionBlock() returns immediately but before the andCompletionBlock() is called, the main thread is blocked for 1-2 secs.
Did not happen before, started to see it after update to newer firebase version. In my case I managed to make this lag shorter by decreasing amount of data to update. It seems firebase is doing something like local cache update on the main thread (just a theory).
Related
I'm making a Watch app that will record user acceleration. I've used CMSensorRecorder from the CoreMotion Framework to do this.
The flow of the program right now is that the user presses a button on the watch, which triggers acceleration to be recorded for 30 seconds. After this, there is a 6-minute delay (referring to answer here :watchOS2 - CMSensorRecorder, a delay is needed to read the data), and the acceleration and timestamp data is printed to the console.
Right now I'm getting a "response invalid" and "Error occurred" when running the app. I've added a motion usage description to the info.plist file.
I'm fairly new to Swift and app development, and I fear something's wrong with the way I'm trying to access the data. I've attached the console logs and code below.
Can anybody provide some insight into the messages and how to resolve this? I've searched around but haven't found any cases of this issue before. Cheers.
func recordAcceleration(){
if CMSensorRecorder.isAccelerometerRecordingAvailable(){
print("recorder started")
recorder.recordAccelerometer(forDuration: 30) //forDuration controls how many seconds data is recorded for.
print("recording done")
}
}
func getData(){
if let list = recorder.accelerometerData(from: Date(timeIntervalSinceNow: -400), to: Date()){
print("listing data")
for data in list{
if let accData = data as? CMRecordedAccelerometerData{
let accX = accData.acceleration.x
let timestamp = accData.startDate
//Do something here.
print(accX)
print(timestamp)
}
}
}
}
//Send data to iphone after time period.
func sendData(dataBlock:CMSensorDataList){
WCSession.default.transferUserInfo(["Data" : dataBlock])
}
//UI Elements
#IBAction func recordButtonPressed() {
print("button pressed")
recordAcceleration()
//A delay is needed to read the data properly.
print("delaying 6 mins")
perform(#selector(callback), with: nil, afterDelay: 6*60)
}
#objc func callback(){
getData()
}
extension CMSensorDataList: Sequence {
public func makeIterator() -> NSFastEnumerationIterator {
return NSFastEnumerationIterator(self)
}
Console output:
button pressed
recorder started
2019-03-12 12:12:12.568962+1100 app_name WatchKit Extension[233:5614] [Motion] Warning - invoking recordDataType:forDuration: on main may lead to deadlock.
2019-03-12 12:12:13.102712+1100 app_name WatchKit Extension[233:5614] [SensorRecorder] Response invalid.
recording done
delaying 6 mins
2019-03-12 12:18:13.115955+1100 app_name WatchKit Extension[233:5614] [Motion] Warning - invoking sensorDataFromDate:toDate:forType: on main may lead to deadlock.
2019-03-12 12:18:13.162476+1100 app_name WatchKit Extension[233:5753] [SensorRecorder] Error occurred while trying to retrieve accelerometer records!
I ran your code and did not get the "Response invalid" or "Error occurred". I did get the main thread warnings. So I changed to a background thread and it works fine.
Also, I don't think you need to wait six minutes. I changed it to one minute.
I hope this helps.
let recorder = CMSensorRecorder()
#IBAction func recordAcceleration() {
if CMSensorRecorder.isAccelerometerRecordingAvailable() {
print("recorder started")
DispatchQueue.global(qos: .background).async {
self.recorder.recordAccelerometer(forDuration: 30)
}
perform(#selector(callback), with: nil, afterDelay: 1 * 60)
}
}
#objc func callback(){
DispatchQueue.global(qos: .background).async { self.getData() }
}
func getData(){
print("getData started")
if let list = recorder.accelerometerData(from: Date(timeIntervalSinceNow: -60), to: Date()) {
print("listing data")
for data in list{
if let accData = data as? CMRecordedAccelerometerData{
let accX = accData.acceleration.x
let timestamp = accData.startDate
//Do something here.
print(accX)
print(timestamp)
}
}
}
}
How can I design the following code better? I have the feeling that the code can lead to a callback hell. Every function depends on completion of the previous one.
Current Solution (bad):
#objc func restoreDocuments(UID: UID) {
DispatchQueue.global(qos: .background).async {
//1. Load user details from RemoteServer#1
UserManager.RemoteServer.loadUserFromRemoteServer(userUID: UID) { (userDict) in
//2. After user is loaded save user to local database
UserManager.LocalDB.saveUser(userData: userDict, completion: {
//After User is restored, restore his documents from RemoteServer#2 (IDs provided in userDetails)
let userDocumentsArray = getDocumentIDsFromUser(userUID: UID)
//Loop through array to get every ID
for ID in userDocumentsArray{
//load each document by ID
loadDocumentsRemote(documentID: ID) { (document) in
//Save loaded document
saveDocumentsLocal(document, completion: {
//At the end populate the UI with the restored documents
DispatchQueue.main.async {
populateUI()
}
})
})
}
})
}
}
I would imagine something like the following code. But I don't know how to communicate the different steps among each other. So that task 2 does not start before task 1 is finished.
What I imagine (simplified):
#objc func restoreDocuments(UID: UID) {
//1. Restore User
UserManager.RemoteServer.loadUser(UID){ (user) in }
UserManager.LocalDB.saveUser(user)
// -> WHEN FINISH PROCCED TO STEP 2 🚨
//2. Load Documents
UserManager.LocalDB.getDocumentIDsFromUser( { (IdArray) in
for ID in IdArray {
RemoteServer.DocManager.loadDocument(ID) { (retrievedDocument) in
LocalDB.DocManager.saveDocument(retrievedDocument)
}
}
}
// -> WHEN FINISH PROCCED TO STEP 3 🚨
//3. Finish
DispatchQueue.main.async {
populateUI()
}
}
But how do I do that? And is that a good approach at all 🤔?
Take a look at futures and promises, two related design patterns that address this issue very well. My company uses BrightFutures, a third party library that offers a decent implementation of both.
You can start by extracting the closures into variables:
let onDocumentsSaved: () -> Void = {
DispatchQueue.main.async {
populateUI()
}
}
let onDocumentsLoaded: (Document) -> Void { document in
saveDocumentsLocal(document, completion: onDocumentsSaved)
}
// continue with other closures, in reverse order of execution
That will clear up your indentation and the steps will be clearly visible. If you want to wait for multiple steps (e.g. multiple documents) in one step, you can use DispatchGroup.
Every step can be also easily extracted into a function, or, you can make your class to work as a state machine.
Also, it's a good idea to group connected methods into utility methods, e.g. your load and save can be grouped to one method with a completion handler, for example:
func loadUserAndSave(userId, completion)
func loadDocumentsAndSave(userId, completion)
then your method could be simplified to (pseudocode):
loadUserAndSave(userId) {
loadDocumentsAndSave {
DispatchQueue.main.async {
populateUI()
}
}
}
Which again would be much simpler .
I set breakpoints all throughout this query to Firebase, and all that's happening is the breakpoint for the following line gets hit (which is the very first line), and then no others.
_CHAT_REF.observeEventType(.Value, withBlock: { snapshot in
Any idea why this would be happening? Even if there is no data, the breakpoints inside the query block should be still be getting hit, but they aren't. What I've done: I've uninstalled and reinstalled Firebase at least 10 times, using both CocoaPods and not CocoaPods, following directions to the T. I'm not getting any kind of compile error, and I'm running FIRApp.configure() in my app delegate.
Full Code (breakpoints on each line, none called only _CHAT_REF.observe line):
private var _CHAT_REF = FIRDatabase.database().reference().child("chats")
_CHAT_REF.observeEventType(.Value, withBlock: { snapshot in
self.individualMessages = []
if snapshot.hasChildren() {
// Found chats
for snap in snapshot.children {
let theChat = Chat(snapshot: snap as! FIRDataSnapshot)
// The current user belongs to this chat, so add it to individual messages.
if theChat.sender_id == GlobalEnv.currentUser.id || theChat.receiver_id == GlobalEnv.currentUser.id {
self.individualMessages.append(theChat)
}
}
} else {
// No Children
print("No children found.")
}
self.tvContacts.reloadData()
})
DB Structure:
DB Structure on Firebase
I ran into a similar problem. It turned out that I wasn't able to read/write the database from behind my organization's proxy server. I built to a device using open wifi, and it worked.
Try this and let me know if it makes a difference. It's very simplified but the variable assignments are handled differently.
assume you are in a ViewController class...
class ViewController: UIViewController {
var ref: FIRDatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
}
func clickAction() { //just tied to an action in the UI
let chatsRef = ref.child("chats")
chatsRef.observeEventType(.Value, withBlock: { snapshot in
print("Hello, World")
})
}
}
I've looked over hours of code and notes and I'm struggling to find any documentation that would help me with upvoting and downvoting an object in a swift app with firebase.
I have a gallery of photos and I'm looking to add an instagram style upvote to images. The user has already logged with firebase auth so I have their user ID.
I'm just struggling to figure the method and what rules need to be set in firebase.
Any help would be awesome.
I will describe how I implemented such a feature in social networking app Impether using Swift and Firebase.
Since upvoting and downvoting is analogous, I will describe upvoting only.
The general idea is to store a upvotes counter directly in the node corresponding to an image data the counter is related to and update the counter value using transactional writes in order to avoid inconsistencies in the data.
For example, let's assume that you store a single image data at path /images/$imageId/, where $imageId is an unique id used to identify a particular image - it can be generated for example by a function childByAutoId included in Firebase for iOS. Then an object corresponding to a single photo at that node looks like:
$imageId: {
'url': 'http://static.example.com/images/$imageId.jpg',
'caption': 'Some caption',
'author_username': 'foobarbaz'
}
What we want to do is to add an upvote counter to this node, so it becomes:
$imageId: {
'url': 'http://static.example.com/images/$imageId.jpg',
'caption': 'Some caption',
'author_username': 'foobarbaz',
'upvotes': 12,
}
When you are creating a new image (probably when an user uploads it), then you may want to initialize the upvote counter value with 0 or some other constant depending on what are you want to achieve.
When it comes to updating a particular upvotes counter, you want to use transactions in order to avoid inconsistencies in its value (this can occur when multiple clients want to update a counter at the same time).
Fortunately, handling transactional writes in Firebase and Swift is super easy:
func upvote(imageId: String,
success successBlock: (Int) -> Void,
error errorBlock: () -> Void) {
let ref = Firebase(url: "https://YOUR-FIREBASE-URL.firebaseio.com/images")
.childByAppendingPath(imageId)
.childByAppendingPath("upvotes")
ref.runTransactionBlock({
(currentData: FMutableData!) in
//value of the counter before an update
var value = currentData.value as? Int
//checking for nil data is very important when using
//transactional writes
if value == nil {
value = 0
}
//actual update
currentData.value = value! + 1
return FTransactionResult.successWithValue(currentData)
}, andCompletionBlock: {
error, commited, snap in
//if the transaction was commited, i.e. the data
//under snap variable has the value of the counter after
//updates are done
if commited {
let upvotes = snap.value as! Int
//call success callback function if you want
successBlock(upvotes)
} else {
//call error callback function if you want
errorBlock()
}
})
}
The above snipped is actually almost exactly the code we use in production. I hope it helps you :)
I was very surprised, but this code from original docs works like a charm. There is one disadvantage with it: the json grows pretty big if there are a lot of likes.
FirebaseService.shared.databaseReference
.child("items")
.child(itemID!)
.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
if var item = currentData.value as? [String : AnyObject] {
let uid = SharedUser.current!.id
var usersLikedIdsArray = item["liked_who"] as? [String : Bool] ?? [:]
var likesCount = item["likes"] as? Int ?? 0
if usersLikedIdsArray[uid] == nil {
likesCount += 1
usersLikedIdsArray[uid] = true
self.setImage(self.activeImage!, for: .normal)
self.updateClosure?(true)
} else {
likesCount -= 1
usersLikedIdsArray.removeValue(forKey: uid)
self.setImage(self.unactiveImage!, for: .normal)
self.updateClosure?(false)
}
item["liked_who"] = usersLikedIdsArray as AnyObject?
item["likes"] = likesCount as AnyObject?
currentData.value = item
return TransactionResult.success(withValue: currentData)
}
return TransactionResult.success(withValue: currentData)
}) { (error, committed, snapshot) in
if let error = error {
self.owner?.show(error: error)
}
}
Not a Swift fella myself (pun!) but I think this stackoverflow question has most of your answers.
Then you would simply use a couple of if statements to return the correct value from the transaction based on whether you want to up vote or down vote.
In my code I have a simple for loop that loops 100 times with nested for loops to create a delay. After the delay, I am updating a progress view element in the UI through a dispatch_async. However, I cannot get the UI to update. Does anyone know why the UI is not updating? Note: The print statement below is used to verify that the for loop is looping correctly.
for i in 0..<100 {
//Used to create a delay
for var x = 0; x<100000; x++ {
for var z = 0; z<1000; z++ {
}
}
println(i)
dispatch_async(dispatch_get_main_queue()) {
// update some UI
self.progressView.setProgress(Float(i), animated: true)
}
}
Three observations, two basic, one a little more advanced:
Your loop will not be able to update the UI in that main thread unless the loop itself is running on another thread. So, you can dispatch it to some background queue. In Swift 3:
DispatchQueue.global(qos: .utility).async {
for i in 0 ..< kNumberOfIterations {
// do something time consuming here
DispatchQueue.main.async {
// now update UI on main thread
self.progressView.setProgress(Float(i) / Float(kNumberOfIterations), animated: true)
}
}
}
In Swift 2:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
for i in 0 ..< kNumberOfIterations {
// do something time consuming here
dispatch_async(dispatch_get_main_queue()) {
// now update UI on main thread
self.progressView.setProgress(Float(i) / Float(kNumberOfIterations), animated: true)
}
}
}
Also note that the progress is a number from 0.0 to 1.0, so you presumably want to divide by the maximum number of iterations for the loop.
If UI updates come more quickly from the background thread than the UI can handle them, the main thread can get backlogged with update requests (making it look much slower than it really is). To address this, one might consider using dispatch source to decouple the "update UI" task from the actual background updating process.
One can use a DispatchSourceUserDataAdd (in Swift 2, it's a dispatch_source_t of DISPATCH_SOURCE_TYPE_DATA_ADD), post add calls (dispatch_source_merge_data in Swift 2) from the background thread as frequently as desired, and the UI will process them as quickly as it can, but will coalesce them together when it calls data (dispatch_source_get_data in Swift 2) if the background updates come in more quickly than the UI can otherwise process them. This achieves maximum background performance with optimal UI updates, but more importantly, this ensures the UI won't become a bottleneck.
So, first declare some variable to keep track of the progress:
var progressCounter: UInt = 0
And now your loop can create a source, define what to do when the source is updated, and then launch the asynchronous loop which updates the source. In Swift 3 that is:
progressCounter = 0
// create dispatch source that will handle events on main queue
let source = DispatchSource.makeUserDataAddSource(queue: .main)
// tell it what to do when source events take place
source.setEventHandler() { [unowned self] in
self.progressCounter += source.data
self.progressView.setProgress(Float(self.progressCounter) / Float(kNumberOfIterations), animated: true)
}
// start the source
source.resume()
// now start loop in the background
DispatchQueue.global(qos: .utility).async {
for i in 0 ..< kNumberOfIterations {
// do something time consuming here
// now update the dispatch source
source.add(data: 1)
}
}
In Swift 2:
progressCounter = 0
// create dispatch source that will handle events on main queue
let source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
// tell it what to do when source events take place
dispatch_source_set_event_handler(source) { [unowned self] in
self.progressCounter += dispatch_source_get_data(source)
self.progressView.setProgress(Float(self.progressCounter) / Float(kNumberOfIterations), animated: true)
}
// start the source
dispatch_resume(source)
// now start loop in the background
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
for i in 0 ..< kNumberOfIterations {
// do something time consuming here
// now update the dispatch source
dispatch_source_merge_data(source, 1);
}
}