accessing NSUserDefaults within cellForRow(at:IndexPath) in Swift, memory usage - ios

Is it memory intensive to retrieve data from NSUserDefaults within the cellForRow(at:IndexPath method? Code shown below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let recentFavorite = UserDefaults.standard.string("recentlyStoredFavorite")
if self.favorite == recentFavorite {
cell.imgForFavorite = self.imgForFavorite
}
}
Or should I save the value from UserDefaults in viewWillAppear and use that within cellForRow?
Please help me understand the memory implications of both the choices.

According to Apple
At runtime, you use UserDefaults objects to read the defaults that
your app uses from a user’s defaults database. UserDefaults caches the
information to avoid having to open the user’s defaults database each
time you need a default value.
You should be 'ok' to retrieve the info in cellForRow, as its likely sitting in a dictionary in memory (assumption), fetched by the key you provide, however to vadian's point, you could just put it in a model or property and eliminate the assumption.
Also, consider if that data will be changed by another process and if you need to observe UserDefaults key.

Related

UITableViewDataSourcePrefetching->prefetchRowsAt always has 0 and 1 in indexPaths array

I have used UITableViewDiffableDataSource as the datasource for my tableView.
On initial load, 8 items are fetched from server and displayed.
When I try to scroll,
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
is called, but indexPaths always has 0 and/or 1. It is never getting incremented to ask for prefetching 10, 11,12, etc rows.
What am I missing here?
From Apple Docs:
Table views do not call this method for cells they require immediately, so your data source object must also be able to fetch the data itself.
This approach is offered by the docs:
The tableView(:prefetchRowsAt:) method is not necessarily called for every cell in the table view. Your implementation of tableView(:cellForRowAt:) must therefore be able to cope with the following potential situations:
Data has been loaded via the prefetch request, and is ready to be displayed.
Data is currently being prefetched, but is not yet available.
Data has not yet been requested.
One approach that handles all of these situations is to use Operation to load the data for each row. You create the Operation object and store it in the prefetch method. The data source method can then either retrieve the operation and the result, or create it if it doesn’t exist.

set value in UserDefaults synchronously

I am trying to save boolean in UserDefault in swift.
so when I set value in userDefault, my very next instruction is to switch to view controller and close the current view controller.
so, what is happening now is, sometimes userDefault saves the value in DB, and sometimes it doesn't.
I read documentation from Apple
https://developer.apple.com/documentation/foundation/userdefaults
and found that
At runtime, you use UserDefaults objects to read the defaults that your app uses from a user’s defaults database. UserDefaults caches the information to avoid having to open the user’s defaults database each time you need a default value. When you set a default value, it’s changed synchronously within your process, and asynchronously to persistent storage and other processes.
so, I guess because in the very next line I open a new controller and close the current one so due to which there is inconsistency.
here is my code
func setWalkthroughShown(completionHandler: #escaping ()->()) {
UserDefaults.standard.set(true, forKey: isWalkthroughCompleted)
UserDefaults.standard.synchronize()
completionHandler()
}
I even called UserDefaults.standard.synchronize() so that operation may become synchronous.
even though in the documentation it is clearly written not to use this function.
can someone please guide me where I am wrong? how can I save across all places before closing the current process?
this is the function by which I am retrieving value
func isWalkthroughShown() -> Bool {
return UserDefaults.standard.bool(forKey: isWalkthroughCompleted)
}
here isWalkthroughCompleted is a string and you can see I am using same string for saving and retrieving value
Actually, there was no syntax error in coding.
I was actually testing it in the wrong way.
after submitting for the request of saving data in userdefaults, I was recompiling immediately and as value stores asynchronously so sometimes due to killing of the process I was getting this issue.
thanks to #matt.
for detail iOS UserDefaults falls behind saved content

return a key using only a value from firebase in swift

I'm trying to retrieve the key, using only the value, in Firebase.
Am working in Swift, for IOS
Trying to write a function that would like to return "hey", when given only the users email address "hey#hey.com"
Basically have the value at hand, and would like to retrieve the key, am trying to just delete the entry.
Anyone have any suggestions?
Here's the bit im unsure of.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
let friendsSelectedEmail = friendsEmailsFromFirebase[indexPath.row]
let refToUserFriendList = fir.child("mybuddies").child(userID)
print(friendsSelectedEmail)
//query for the key of the email address, then use that to delete the object
//Or is there an easier firebase method?
self.tableView.reloadData()
}
}
**edit
In a nutshell, i just wanna delete this key-value pair, and I have access to only the value(email address), is that doable via a single firebase method? **assuming i dont use the better structure that #jay posted.
It appears the intention here is to use the hey part of hey#hey.com as the key.
A better approach is to create the child nodes with childByAutoId and store the properties within that node. So your structure becomes
zCu.....
email: "hey#hey.com"
buddy_name: "Leroy"
domain: "hey.com"
Then you can simply query all nodes for email that equals "hey#hey.com" which will return a snapshot that also has the user_name (hey) and domain (hey.com if needed)
If you need the code for that query, let me know. Keep in mind Firebase is asynchronous so it doesn't really "return" a value. You work with the value within the closure following the query.
From a comment, it appears we want to remove the buddy_name and email but leave the node. If the node is known, you can just create that reference, otherwise you can query for it. Either way you'll get the parent key to the node
let buddyRef = parent_node_key.child("buddy_name")
let domainRef = parent_node_key.child("domain")
buddyRef.setValue("")
domainRef.setValue("")
Keep in mind that I left email in this case as if we delete that, then the entire node will go away; a node must have at least one value to exist.
To add some clarity;
To create that structure, you would need to know the users uid which is used as a key or if you want it in another node, use .childByAutoId to create the key.
let ref = thisUsersUid //or let ref = your_firebase.childByAutoId()
ref.child("email").setValue("hey#hey.com")
ref.child("domain").setValue("hey.com")
ref.child("buddy_name").setValue("Leroy")

Displaying Leaderboard using UITableViewController with Firebase backend (Swift 4.1)

I am using Firebase to store my backend data. I am trying to display a leaderboard using a UITableViewController in Swift.
One of the data source methods include,
override func tableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
which in my case requires me to fetch data from Firebase and store the data in a local array variable e.g var leaderboardArr = [Int]()
Problem arises when there is a change to the rankings in the leaderboard. I know Firebase has built-in methods to sort the data and adjust the rank accordingly but how do I manage the local array variable?
UITableViewController dataSource method displays the data according to the indices of the local array variable.
How to manipulate the array according to the changes in data from Firebase??
When the user refreshes (or when you decide the view should refresh) retrieve the leaderboard stats from Firebase again and then reload the table view.
If you're looking to update the data in real time you can add a ChildEventListener. So when the array changes, it activates the listener, and from there you can retrieve the necessary data and reload the view.
Here is some reference: https://firebase.google.com/docs/reference/android/com/google/firebase/database/ChildEventListener

Implement UITableViewDataSourcePrefetching with an NSFetchedResultsController data source

I have a UITableView that gets data from an NSFetchedResultsController. To this end, i basically copied Apples example implementation from https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/nsfetchedresultscontroller.html.
Now at WWDC16 Apple announced the UITableViewDataSourcePrefetching protocol, which provides callbacks that let you prefetch data so it's already loaded when it's needed to be displayed by the tableview. I'm looking for an example on how to integrate this with the NSFetchedResultsController, because i can't figure out if i'm doing this correctly.
Should i simply create a dictionary, as an in-memory cache, to hold prefetched data and be used in cellForRowAtIndexPath instead of querying fetchedResultsController.object(at: indexPath) directly?
As i understand it, CoreData already caches fetched data automatically, so maybe i just need to call fetchedResultsController.object(at: indexPath) in the prefetch callbacks, to ensure the data gets cached?
Or shouldn't i prefetch data at all when using a fetched results controller, because i would work around the magical integration provided by Apple?
Or something else entirely?
*edit*: I found a slide from the Core Data talk at WWDC16 that supposedly explains this, but i understand it at all.
An async fetch request? I thought they don't work with NSFetchedResultsController. I guess that's why it's performed on the managedObjectContext directly?
The async fetch request is created from the results of calling .performFetch() on the NSFetchedResultsController. Nothings showing up until i call that. But since all the results are there after calling it, i don't get why i would need to prefetch them again.
It will improve performance if your model has faults. Whether your models have faults or not depends on iOS optimization, and how you create your schema and relationships.
So to be safe your could add the prefetch code. I have the code for Swift 4.
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
let fetchRequest: NSFetchRequest<MyModel> = MyModel.fetchRequest()
fetchRequest.returnsObjectsAsFaults = false
let items = indexPaths.map { fetchedResultsController.object(at: $0) }
fetchRequest.predicate = NSPredicate(format: "SELF IN %#", items)
let asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest)
do {
try fetchedResultsController.managedObjectContext.execute(asyncFetchRequest)
} catch { }
}
What it does is pretty self explanatory. It runs an async fetch request on the managed object context, therefore resolving faults, if any.
In short, you are creating a custom predicate to "warm" the predicted specific objects in the MOC. The idea, I think, is that the table view is telling you more targeted scroll target info (based on velocity etc.) about where it thinks the table view will end up. This allows you to execute an async request to pre-fetch these objects in the MOC so that by the time you are actually asking for properties on it, they're ready to go.
This is why you also have a nil completion on the async request. You aren't directly using the results because you may not actually be displaying any of these index paths yet. It also means it's unnecessary to do additional tracking (like in a Dictionary, etc.) of results when you get pre-fetched objects.
The real question is, if you are using an FRC with batching, pre-fetched properties, etc., is this really buying you anything to side-step it? I'm not really sure. I haven't noticed a huge difference between a pretty highly tuned FRC+UITableView with vs without pre-fetching on a table of around 1000 results. It's also possible by merely touching those objects that the FRC will see that (because you are sharing the same MOC) and then will run through it's property/relationship pre-fetching, etc. I haven't seen much documentation on this mechanism, but if Apple is suggesting it, especially using an FRC, I have to think it helps.

Resources