Firebase Queries and Completion - ios

I'm having some trouble writing a process to allow a user to delete their Firebase account which deletes their account and all the posts that they had in their account including images. I am querying the Users posts in Firebase Database, looping through and deleting them as they come out. In the middle of this loop, I call a function passing in the image URL to delete the image from Firebase storage. The problem I am having is that I can't get an ordered result because of asynchronous stuff. I thought having completion handlers would give it some order. Should I be looking into dispatch groups? Any help is greatly appreciated.
The start of my process with function call:
ref.deleteFirebaseDBUsersPosts(deleter) { (success) -> Void in
if success {
print("The user and their info is completely gone!")
}
}
Function that queries Firebase and loops through users posts and deletes:
func deleteFirebaseDBUsersPosts(uid: String, completion:(success: Bool) -> Void) {
let usersCurrentId = uid
ref.child(“posts”).queryOrderedByChild("uid").queryEqualToValue("\(uid)").observeEventType(.Value, withBlock: {snapshot in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot{
print("SNAP: \(snap)")
if let postedImg = snap.childSnapshotForPath("imageUrl").value {
let postImgUrl = postedImg as! String
self.deletingOldUsersImgsFromStorage(postImgUrl) { (success) -> Void in
}
}
let key = snap.key
ref.child(“posts”).child(key).removeValue()
ref.child(“users”).child(usersCurrentId).removeValue()
}
print("COMPLETING")
completion(success: true)
}
})
}
Deleting the image from Firebase Storage:
func deletingOldUsersImgsFromStorage(postImgUrl: String!, completion:(success: Bool) -> Void) {
let deleteImgRef = ref.child(“images”).child("[imageNAME]")
deleteImgRef.deleteWithCompletion({ (error) in
if (error != nil) {
print("DEVELOPER: There was an error when trying to delete the image from Firebase Storage")
} else {
print("IMAGE SUCCESSFULLY DELETED FROM FIREBASE STORAGE")
}
completion(success: true)
})
}

I dealt with a similar challenge when deleting a user and their data from Firebase. I handled it using dispatch groups to delete the user after my methods that deleted their data had completed.
func deleteUsersData(completion: (success: Bool) -> Void) {}
func deleteUsersPhotoUrl(completion: (success: Bool) -> Void) {}
func deleteUserFromFirebase(completion: (success: Bool) -> Void) {}
let group = dispatch_group_create()
dispatch_group_enter(group)
deleteUsersData { (success) in
if success {
dispatch_group_leave(group)
}
}
dispatch_group_enter(group)
deleteUsersPhotoUrl { (success) in
if success {
dispatch_group_leave(group)
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
deleteUserFromFirebase({ (success) in
print("Completely deleted user from Firebase")
})
}
Dispatch groups are great for handling situations like this where you only want to perform a task after the other tasks you specified have completed. Here is more info on how to use dispatch groups.

Related

Background thread - two network calls [duplicate]

This question already has answers here:
DispatchGroup logical workflow
(2 answers)
Closed 1 year ago.
I have two methods in the completeOnboarding method and both of them have network operation which should be done in the background thread as follows. However, I am wondering if I am doing why completion(true) gets called first, how could I able to handle that issue?
DispatchQueue.global(qos: .background).async {
self?.completeOnboarding( completion: { (success) in
DispatchQueue.main.async {
if success {
print("success")
} else {
print("failed")
}
}
})
func completeOnboarding(completion: #escaping(Bool) -> Void){
// has network post operation
classRegistration() {(success) in
if !success {
completion(false)
return
}
}
// has network post operation
classLocation() { (success) in
if !success {
completion(false)
return
}
}
completion(true)
}
The final completion(true) is not waiting for classLocation() and classRegistration() calls. If you have multiple network calls and you want to wait for all of them to finish you could (one approach) add them to a DispatchGroup and wait for that one to finish:
func dispatchAndWait(completion: #escaping () -> Void) {
func networkOne(completion: #escaping (_ success: Bool) -> Void) {
print("[DEBUG] Enter \(#function)")
DispatchQueue.main.asyncAfter(deadline: .now() + Double.random(in: 0...3)) {
completion(Bool.random())
}
print("[DEBUG] Return \(#function)")
}
func networkTwo(completion: #escaping (_ success: Bool) -> Void) {
print("[DEBUG] Enter \(#function)")
DispatchQueue.main.asyncAfter(deadline: .now() + Double.random(in: 0...3)) {
completion(Bool.random())
}
print("[DEBUG] Return \(#function)")
}
// Create a DispatchGroup and add both calls
let dispatchGroup = DispatchGroup()
// Enter first network call
dispatchGroup.enter()
networkOne { success in
print("[DEBUG] Complete networkOne with success: \(success)")
// Exit first network call
dispatchGroup.leave()
}
// Enter second network call
dispatchGroup.enter()
networkTwo { success in
print("[DEBUG] Complete networkTwo with success: \(success)")
// Exit second network call
dispatchGroup.leave()
}
// notify gets called when all tasks have exited
dispatchGroup.notify(queue: DispatchQueue.main) {
completion()
}
}
One more advise:
classRegistration() {(success) in
if !success {
completion(false)
return
}
}
will never complete in case of success==true, you should make sure that the completion is called on every path
classRegistration() {(success) in
// ... do whatever needs to be done here
completion(success)
}
Assuming classRegistration needs to succeed for classLocation to begin --
quick & dirty --
func completeOnboarding(completion: #escaping(Bool) -> Void){
// has network post operation
classRegistration() {(success) in
if success {
// has network post operation
classLocation() { (success) in
completion(success)
}
} else {
completion(false)
}
}
}
a proper way (others include - NSOperations with dependency, Dispatch group)
func completeOnboarding(completion: #escaping(Bool) -> Void){
let serialQueue = DispatchQueue(label: "classname.serial")
var proceedWithSuccess = true
serialQueue.async {
serialQueue.suspend() //run 1 operation at a time
classRegistration() {(success) in
proceedWithSuccess = success
serialQueue.resume() //let next operation run
}
}
serialQueue.async {
guard proceedWithSuccess else { return }
serialQueue.suspend()
classLocation() { (success) in
proceedWithSuccess = success
serialQueue.resume()
}
}
serialQueue.async {
completion(proceedWithSuccess)
}
}
If you want classLocation() to fire even if registration fails - just get rid of guard statement above.
If it were up to me I'd use a custom NSOperation subclass for Async operation & explicitly mention dependency between operations but it needs a ton of boilerplate code (perhaps something to look into later); serial queue (or dispatch group from the other answer) oughta be enough for you in this case though.

Firebase observer architecture

Okay, so I'm trying to build an iOS app that relies on Firebase (To work with its android version)
I started with creating a repository for each actor in my app and a general repository to manage them all
Each repository manages the observers of this actor. An example:
Inside the PagesRepository, this is a function that retrieves all the pages from Firebase and returns it inside a completionHandler:
//MARK: Gets the whole pages list
func getPagesList(completionHandler: #escaping (_ pages: [Page]?, _ error: NSError?) -> Void) {
func displayError(error: String) {
print(error)
completionHandler(nil, self.getErrorFromString(error))
}
pagesRef.observe(DataEventType.value) { pagesSnapshot in
guard pagesSnapshot.exists() else {
displayError(error: "Pages snapshot doesn't exist")
return
}
var pagesList = [Page]()
for pageSnapshot in pagesSnapshot.children {
pagesList.append(Page(snapshot: pageSnapshot as! DataSnapshot))
}
completionHandler(pagesList, nil)
}
}
And then I call it from the ViewController like this:
repository.getPagesList { (pages, error) in
guard error == nil else {
return
}
//Do processing
}
I know this may be a lot to take in, but my problem is that every time I call the function, it creates a new observer but doesn't cancel the old one... So, the completionHandler is called multiple times with different values
How should I manage this problem?
(Sorry for being complicated and a little unclear, I'm just really lost)
It seems like you only want to observe the value once so I would use this instead:
func getPagesList(completionHandler: #escaping (_ pages: [Page]?, _ error: NSError?) -> Void) {
func displayError(error: String) {
print(error)
completionHandler(nil, self.getErrorFromString(error))
}
pagesRef.observeSingleEvent(of: .value, with: { (pagesSnapshot) in
var pagesList = [Page]()
for pageSnapshot in pagesSnapshot.children {
pagesList.append(Page(snapshot: pageSnapshot as! DataSnapshot))
}
completionHandler(pagesList, nil)
}) { (error) in
// Display error
}
}

HKObserverQuery only runs when the application is reopened

So I've been following the instructions in this answer...
Healthkit background delivery when app is not running
The code runs fine and works whilst the application is open and says that background delivery is successful, however when I test the application by walking around and changing the clock on the device to an hour forward I do not receive any logs to let me know it has run. However, if I open the application again the observer query runs.
private func checkAuthorization(){
let healthDataToRead = Set(arrayLiteral: self.distanceQuantityType!)
healthKitStore.requestAuthorization(toShare: nil, read: healthDataToRead) { (success, error) in
if error != nil {
print(error?.localizedDescription)
print("There was an error requesting Authorization to use Health App")
}
if success {
print("success")
}
}
}
public func enableBackgroundDelivery() {
self.checkAuthorization()
self.healthKitStore.enableBackgroundDelivery(for: self.distanceQuantityType!, frequency: .hourly) { (success, error) in
if success{
print("Background delivery of steps. Success = \(success)")
}
if let error = error {
print("Background delivery of steps failed = \(error.localizedDescription)")
}
}
}
func observeDistance(_ handler:#escaping (_ distance: Double) -> Void) {
let updateHandler: (HKObserverQuery?, HKObserverQueryCompletionHandler?, Error?) -> Void = { query, completion, error in
if !(error != nil) {
print("got an update")
completion!()
} else {
print("observer query returned error: \(error)")
}
}
let query = HKObserverQuery(sampleType: self.distanceQuantityType!, predicate: nil, updateHandler: updateHandler)
self.healthKitStore.execute(query)
}
The query is initialised in the appDelegate method didFinishLaunching
This particular HealthKitQuery is asynchronous. You should wait until it finishes processing.
However, this case is not possible in didFinishLaunching. The application just ended execution and there is not enough time to process the query.
I would seriously suggest to rethink the logic behind the operation of your code. A good way to solve this would be to put the request elsewhere, preferrably after the needed operations were completed.

Execute all background threads before executing another block

I am trying to load photos in Core Data but this requires some background threads, so it happens that my Object gets saved in Core Data without its photo, I get that photo is nil. And the same is happening with the photos of my groups.
In short, at each iteration I save an object in Core Data and the saving happens faster than the loading of my Photo data.
I tried Semaphors, barriers, groups,... but none worked. I am certainly doing something wrong but I don't know what. If someone could help me I would really appreciate, it has been 2 weeks I am struggling with this same problem. This is my code in a simplified version, the full version is accessible below:
// MARK - ViewDidLoad
override func viewDidLoad() {
// This method is only called once, so you want to create any controls or arrays here
super.viewDidLoad()
// Verifying connectivity to internet
if Reachability.isConnectedToNetwork() == true {
// Getting data from Parse + getDataInBackgroundWithBlock
}else{
}
}
// MARK - Action of the login button
#IBAction func loginBtn_click(sender: AnyObject) {
if usernameTxt.text == "" || passwordTxt.text == "" {
self.displayAlert("Error", message: "Please enter a username and password")
}else{
// MARK - Login the user
PFUser.logInWithUsernameInBackground(usernameTxt.text!, password: passwordTxt.text!) { (user, error) -> Void in
if error == nil {
let queue = dispatch_queue_create("com.karagan.app.queue", DISPATCH_QUEUE_CONCURRENT)
dispatch_async(queue, { () -> Void in
self.getObjectsFromQueryWithAscendingOrder("Add", key: "user", value: userName, ascendingOrder: "added", completionHandler: { (objects1) -> Void in
// Update of an array in background
})
})
dispatch_async(queue, { () -> Void in
// Update of an array in background
})
dispatch_async(queue, { () -> Void in
self.getObjectsFromQueryWithAscendingOrder("Group", key: "membersUsername", value: userName, ascendingOrder: "membersUsername", completionHandler: { (objects3) -> Void in
// Update of an array in background
// MARK - Adding all the users in Core data
for var i = 0; i < usersFromParseDataBase.count; i++ {
let entity = NSEntityDescription.entityForName("Users", inManagedObjectContext: self.managedObjectContext)
let newUser = Users(entity: entity!, insertIntoManagedObjectContext: self.managedObjectContext)
if self.arrayAddedUsers.contains(usersFromParseDataBase[i].username){
// Saving new records in Core Data
}
}
})
})
dispatch_async(queue, { () -> Void in
self.getObjectsFromQueryWithDescendingOrder("Group", key: "membersUsername", value: userName, descendingOrder: "conversationUpdate", completionHandler: { (objects4) -> Void in
if array.count == 2 {
// Getting data in background - photo.getDataInbackgroundWithBlock
}
}else{
// Getting data in background - photo.getDataInbackgroundWithBlock
}
}
})
})
dispatch_barrier_async(queue, { () -> Void in
// Doing some stuff in background which needs the data from threads above
})
}else{
// Print error from loginWithUsernaemInBackground
}
}
}
}
This is what gets displayed to the log, so you can clearly see that the barrier gets executed before the loading of pictures is done executing.
TEST 3 - interacting user has been saved to core data
Loading profile image
Loading profile image
Loading group image
Loading profile image
Loading profile image
9
5
Loading group image
Loading group image
Loading group image
Loading group image
The thing is that I need this data for other operations later. My code is not consistent.
I think you are on the right track. When I create a simple program and use dispatch_barrier_async it works perfectly:
let queue = dispatch_queue_create("com.karagan.app.queue", DISPATCH_QUEUE_CONCURRENT)
dispatch_async(queue, { () -> Void in
sleep(1)
print("1")
})
dispatch_async(queue, { () -> Void in
sleep(2)
print("2")
})
dispatch_async(queue, { () -> Void in
sleep(3)
print("3")
})
dispatch_async(queue, { () -> Void in
sleep(4)
print("4")
})
dispatch_barrier_async(queue, { () -> Void in
print("done")
})
Output:
1
2
3
4
done
I think the problem is that dispatch_async doesn't wait for the call toPFFile.getDataInBackgroundWithBlock to finish:
photoFile.getDataInBackgroundWithBlock({ (imageData, error) -> Void in
if error == nil {
self.arrayImageFiles.append(imageData!)
print("Loading group image")
}
})
Try switching all calls to getDataInBackgroundWithBlock with the synchronous call to PFFile.getData (https://parse.com/docs/osx/api/Categories/PFFile%28Synchronous%29.html#/c:objc%28cs%29PFFile%28im%29getData:) instead:
var imageData = photoFile.getData
if (imageData != nil) {
self.arrayImageFiles.append(imageData!)
}
This shouldn't really have any performance issues since you are already executing this code asynchronously.

How do I "Look Me Up By Email"

I am making an iOS application and I would like to use the iCloud feature "Look Me Up By Email" found under iCloud in the Settings of iOS.
I would like to simplify the user experience by identifying users by their iCloud so they don't have to remember a login for my app.
As I understand this, this is a CloudKit feature related to CKDiscoverAllContactsOperation. You can discover people from your contacts with the same app and can be discovered by them. You need to request permission for that ability first, like this:
CKContainer.defaultContainer().requestApplicationPermission(CKApplicationPermissions.UserDiscoverability) { [unowned self] (status, error) -> Void in
//Your code handling error or success...
}
Note
Release notes for iOS 10 mention, that this operation and related functions will be changed. In iOS 10+ you need to use CKDiscoverAllUserIdentitiesOperation
Example of usage
init a container first, note that your container accountStatus must be correct
let container = CKContainer(identifier: "iCloud.com.YourContainerID")
Later, ask a permission
container.requestApplicationPermission(CKApplicationPermissions.userDiscoverability) { [unowned self] (status, error) -> Void in
if let err = error {
print(err)
}
else if status == CKApplicationPermissionStatus.granted{
//success
}
else{
print("Permission not granted")
print(status)
}
}
Later, you can get your current user record id and create a subscription for example (note that you don't need a permission and user record ID to create a subscription, but in my case it was needed to create a predicate):
container.fetchUserRecordID { [weak self] (recordID, error) in
let predicate = NSPredicate(format: "user = %#",userRecordID.recordName)
let subscription = CKSubscription(recordType: "myRecordType", predicate: predicate, options: CKSubscriptionOptions.firesOnRecordCreation)
container.publicCloudDatabase.save(subscription, completionHandler: { (subscription, error) in
//completion stuff here
}
}
Friends discoverability example
let discoverOperation = CKDiscoverAllUserIdentitiesOperation()
var users = [CKUserIdentity]()
discoverOperation.discoverAllUserIdentitiesCompletionBlock = { [weak self] (error: Error?) -> Void in
if let err = error as? NSError{
print("Discover Friends Error: \(err)")
} else {
//do whatever you want with discovered contacts
}
}
discoverOperation.userIdentityDiscoveredBlock = { (userIdentity: CKUserIdentity) -> Void in
users.append(userIdentity)
}
discoverOperation.queuePriority = Operation.QueuePriority.high //this option is up to your tasks
container.add(discoverOperation)

Resources