What happens to a pointer referenced by a closure that is effectively let go? - swift

The following is pseudo-code to help demonstrate my question:
var dataSourceArray = [CustomClassObject]()
databaseListener { (data) in
dataSourceArray.removeAll()
let id = data.id
let imageURL = data.imageURL
let listing = CustomClassObject(id: id, image: nil)
remoteFileStorage.getImage(url: imageURL) { (image) in
listing.image = image
}
dataSourceArray.append(listing)
tableView.reloadData()
}
databaseListener is a realtime database listener that can return updates rapidly. In its completion handler is an async call that downloads an image. If databaseListener returns new data before remoteFileStorage has a chance to return from the last run, what happens to the listing pointer in the remoteFileStorage closure? The listing pointer no longer has a home in dataSourceArray (it was just purged) so can the remoteFileStorage handler safely access it anyway? My assumption is that the listing pointer is still pointing to a valid address in memory since the remoteFileStorage closure has a reference to it, but I am not certain?

Related

Firebase Storage getData cannot be awaited [duplicate]

This question already has an answer here:
How to use downloaded URL correctly in AsyncImage?
(1 answer)
Closed 2 months ago.
My function ends before all data can be retrieved from the fire storage and returns empty.
How can I get the data before returning?
Code:
func listItem() -> [imageitem]{
let storage = Storage.storage()
let storageRef = storage.reference().child("images/image")
var array = [imageitem]()
storageRef.listAll { (result, error) in
if let error = error {
print("error")
print(error)
}
print("storagereference")
result?.items.forEach({ StorageReference in
print(StorageReference)
print(StorageReference.name)
StorageReference.getData(maxSize: 5*1024*1024) {data, error in
if error == nil && data != nil{
if let image = UIImage(data: data!){
let element = imageitem(title: StorageReference.name, image: Image(uiImage: image))
array.append(element)
print(array)
}
}
}
})
print("end",array)
}
return array
}
Console:
storagereference
gs://proj-69776.appspot.com/images/image/gorilla.jpg
gorilla.jpg
gs://proj-69776.appspot.com/images/image/test.jpg
test.jpg
end []
[proj.ListView.imageitem(title: "test.jpg", image: SwiftUI.Image(provider: SwiftUI.ImageProviderBox<__C.UIImage>))]
[proj.ListView.imageitem(title: "test.jpg", image: SwiftUI.Image(provider: SwiftUI.ImageProviderBox<__C.UIImage>)), proj.ListView.imageitem(title: "gorilla.jpg", image: SwiftUI.Image(provider: SwiftUI.ImageProviderBox<__C.UIImage>))]
adding an await before the addData gives me an error
Cannot pass function of type '(StorageReference) async -> Void' to parameter expecting synchronous function type
You cannot return data that is loaded asynchronously with a return statement like that, because the return runs well before you ever append any element to the array. If you set breakpoints and run the code in a debugger you can most easily validate that.
Instead, you'll want to pass in a closure/callback or use a dispatch queue to get the value out of your function into the calling context.
For examples of how to do this, see:
Return image from asynchronous call
Unable to assign value within a function in Firebase API callback block
When I try to access my array outside of this function it appears to be empty.I t is something to do with an asynchronous call, any suggestion?
Accessing Array Outside Closure in Swift 3
How to make synchronous operation with asynchronous callback?
How to upload the profile image URL to firebase database

Return Image from Function in Swift

I have tried to return an Image or UIImage from the function below but there is an issue that says
"Unexpected non-void return value in void function."
I am retrieving the Image from Firebase Storage and just want to return it...
func retrivePhotos(path: String) -> UIImage {
let storageRef = Storage.storage().reference()
let fileRef = storageRef.child(path)
// Retrieve the data with the path
fileRef.getData(maxSize: 5 * 1024 * 1024) { data, error in
if error == nil && data != nil {
let image = UIImage(data: data!)
return image
}
}
}
The thing to understand is that your call to getData(maxSize:) is a separate asynchronous function. That function does not, and cannot, return the image in its return. It needs to start a network request. It starts that network request and then returns immediately. When the function returns, the network request hasn't even begun sending over the network yet.
The return in the braces following that function call are a return from a closure, which is a block of code that you are passing to the function.
Your function also cannot return an image if it is getting the image using an async function. (unless you rewrite it using async/await, a new language feature that is a little beyond the scope of this question, and likely beyond your current understanding.)
You need to rewrite your function to take a completion handler, just like the getData(maxSize:) function takes a completion handler:
typealias ImageHandler = (UIImage?) -> Void
func retrivePhotos(path: String, completion: ImageHandler) {
let storageRef = Storage.storage().reference()
let fileRef = storageRef.child(path)
// Retrieve the data with the path
fileRef.getData(maxSize: 5 * 1024 * 1024) { data, error in
if error == nil && data != nil {
let image = UIImage(data: data!)
completion(image)
} else {
completion(nil)
}
}
}
(That is over-simplified. You'd probably write it to take a Result instead of an Optional UIImage.)
Think of async functions like this. The main program is you, cooking dinner for your family. You realize you are out of milk, so you send your kid to the store to buy some. You don't hand your kid some money and expect them to hand you back the milk right then and there. You hand them the money, tell them to get you milk, and then go back to the rest of the cooking. At some time in the future, your kid comes back and either gives you the milk (The success case) or tells you there was a problem that prevented them from getting the milk (the error case.)
At the point your kid come back from the store, you run the "kid is back from the store" completion handler. You either go into sauce-making mode with the milk, or an error handler where you decide what to do since you don't have milk.

Firebase Observe called after following command

I am trying to load a username form a Firebase which is than supposed to be set in an Object. But the Firebase Observe Command is getting called after the name already gets set. What is the problem and how can I fix it?
let ref = Database.database().reference().child("Users").child(currentMessage.senderId).child("name")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
// This is supposed to be called first
self.username = snapshot.value as! String
print(self.username)
})
// This somehow gets called first
let nameModel = NameModel(name: self.username, uid: *some UID*)
decoratedItems.append(DecoratedChatItem(chatItem: nameModel, decorationAttributes: nil))
Firebase loads data from its database asynchronously. This means that the code executes in a different order from what you may expect. The easiest way to see this is with some log statements:
let ref = Database.database().reference().child("Users").child(currentMessage.senderId).child("name")
print("Before attaching observer")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
print("In completion handler")
})
print("After attaching observer")
Unlike what you may expect, this code prints:
Before attaching observer
After attaching observer
In completion handler
This happens because loading data from Firebase (or any other web service) may take some time. If the code would wait, it would be keeping your user from interacting with your application. So instead, Firebase loads the data in the background, and lets your code continue. Then when the data is available, it calls your completion handler.
The easiest way to get used to this paradigm is to reframe your problems. Instead of saying "first load the data, then add it to the list", frame your problem as "start loading the data. when the data is loaded, add it to the list".
In code this means that you move any code that needs the data into the completion handler:
let ref = Database.database().reference().child("Users").child(currentMessage.senderId).child("name")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
self.username = snapshot.value as! String
let nameModel = NameModel(name: self.username, uid: *some UID*)
decoratedItems.append(DecoratedChatItem(chatItem: nameModel, decorationAttributes: nil))
})
For some more questions on this topic, see:
Swift: Wait for Firebase to load before return a function
Can Swift return value from an async Void-returning block?
Array items are deleted after exiting 'while' loop?
Asynchronous functions and Firebase with Swift 3

Upvote/Downvote system within Swift via Firebase

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.

Swift Array is Empty After Parse Queries - Completion Handler?

I don't understand why the arrays become empty after the query with block. I did some research and it's most likely because I need a completion handler, but I can't figure out how to implement it in this case. Can I just add an activity indicator until the method is done?
var usernamesFollowing = [""]
var useridsFollowing = [""]
func refresh(completion: (Bool)){
//find all users following the current user
var query = PFQuery(className: "Followers")
query.whereKey("following", equalTo: PFUser.currentUser()!.objectId!)
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if error == nil {
//remove all from arrays
self.usernamesFollowing.removeAll(keepCapacity: true)
self.useridsFollowing.removeAll(keepCapacity: true)
//get all userIds of following current user and add to useridsFollowing array
if let objects = objects {
for userId in objects {
var followerId = userId["follower"] as! String
self.useridsFollowing.append(followerId)
//get usernames from followerId and add to usernamesFollowing array
var query = PFUser.query()
query!.whereKey("objectId", equalTo: followerId)
query!.findObjectsInBackgroundWithBlock({ (objects2, error) -> Void in
if let objects2 = objects2 {
for username in objects2 {
var followerUsername = username["username"] as! String
self.usernamesFollowing.append(followerUsername)
}
}
//WORKS. usernamesFollowing array is now full.
println(self.usernamesFollowing)
})
//BROKEN. usernamesFollowing array is now empty outside of block.
println(self.usernamesFollowing)
}
}
}
//WORKS. useridsFollowing is now full.
println(self.useridsFollowing)
})
//BROKEN. usernamesFollowing is now empty outside of block.
println(self.usernamesFollowing)
}
To elaborate on Larme's point - asynchronous methods return immediately, and dispatch the work into another queue. To put this in context, consider your two println statements:
println(self.usernamesFollowing) //1. inside async fetch closure
println(self.usernamesFollowing) //2. outside async fetch closure
The asynchronous method will take your closure and dispatch it on to a different queue. After doing so, it returns immediately, and continues to execute your code, which goes to your 2nd println statement right away. In this situation, your second println statement will actually print before your first.
If possible, do all your data manipulations within the block. It'll save you a lot of work. If you must offload the objects outside of the block, consider using NSOperations, which is perfectly equipped to deal with that type of scenario.