how to detach a listener in a local scope? - swift

I want to detach a snapshotListener in a viewController when a button is pressed. I was reading other stack over flow questions and the documentation and they were calling the remove method for the listener in the same function. I tried to do that in my situation but my snapshotListener just didn't end up working at all.
Here's my function and block of code that I want to tweak.
#objc func doneTapped() {
let updateListener = db.collection("school_users/\(user?.uid)/events").whereField("event_name", isEqualTo: navigationItem.title).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
if let error = error {
print("There was an error fetching the documents: \(error)")
} else {
self.eventName = querySnapshot!.documents.map { document in
return EventName(eventName: (document.get("event_name") as! String))
}
self.db.document("school_users/\(self.user?.uid)/events/\(self.docIDUneditableTextF.text!)").updateData(["event_date": self.dateEditableTextF.text, "event_cost": self.costEditableTextF.text, "for_grades": self.gradesEditableTextF.text]) { (error) in
if let error = error {
print("There was an error updating the document: \(error)")
} else {
print("The document was successfully updated."
}
}
}
}
dateEditableTextF.resignFirstResponder()
dateEditableTextF.isEnabled = false
costEditableTextF.resignFirstResponder()
costEditableTextF.isEnabled = false
gradesEditableTextF.resignFirstResponder()
gradesEditableTextF.isEnabled = false
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(editTapped))
}
I tried calling updateListener.remove() but it made my snapshotListener not work at all, also when the document updates, the print statement is never ending, is that also because the listener is still active or is that a different issue?

addSnapshotListener will give you updates whenever the data changes. I'm unclear why you would want to immediately remove the listener, if you really did want to receive updates -- and, as you pointed out, immediately removing it will basically cause to it not function at all. Perhaps post a link to some of those posts/documentation where you saw the code you're referencing and someone can give insight into what's happening.
My suspicion is that you don't actually need the updates to the data. In that case, you can just use .getDocuments() instead. See the Firestore documentation here about different ways to get data: https://firebase.google.com/docs/firestore/query-data/get-data
The second problem (the infinite print) is related to the first. Because you have a listener, which will return updates when the data changes, when you do your second database call (your updateData), that updates your data, triggering the listener again. This will keep looping because they will keep calling each other. This is another sign that perhaps you don't actually want a listener, but a single call to get the data. If you do in fact want updates, you'll have to find a way to decouple your second request so that you don't get in the loop.
Update based on comments: (Example of removing the listener in a different function)
On your view, view controller, etc, declare a property for the listener:
class MyViewController : UIViewController {
private var documentListener: ListenerRegistration? //assuming that ListenerRegistration is the correct type here, but you can check the current type of your updateListener to check
}
Then, in your function, set your listener to that:
documentListener = db.collection("school_users/\(user?.uid)/events").whereField("event_name", isEqualTo: navigationItem.title).addSnapshotListener()...
Then, later (like in viewDidDisappear), you can remove it:
documentListener?.remove()

Related

How do I wait for a download to complete before continuing?

I have this block of code. It fetches data from the API and adds it to a locationDetails array, which is part of a singleton.
private func DownloadLocationDetails(placeID: String) {
let request = AF.request(GoogleAPI.shared.getLocationDetailsLink(placeID: placeID))
request.responseJSON { (data) in
guard let detail = try? JSONDecoder().decode(LocationDetailsBase.self, from: data.data!),
let result = detail.result else {
print("Something went wrong fetching nearby locations.")
return
}
DownloadManager.shared.locationDetails.append(result)
}
}
This block of code is the block in question. I'm creating a caching system of sorts that only downloads new information and retains any old information. This is being done to save calls to the API and for performance gains. The line DownloadLocationDetails(placeID: placeID) is a problem for me because if I execute this line of code it will continue to loop over and over again using unnecessary API calls while waiting for the download to complete. How do I effectively manage this?
func GetLocationDetail(placeID: String) -> LocationDetail {
for location in locationDetails {
if location.place_id == placeID { return location }
}
DownloadLocationDetails(placeID: placeID)
return GetLocationDetail(placeID: placeID)
}
I expect this GetLocationDetail(....) to be called whenever a user interacts with an interface object, so how do I also ensure that the view that calls this is properly notified that the download is complete?
I attempted using a closure but I can't get it to return the way I'm wanting it to. I have a property on the singleton that I want to set this value so that it can be called globally. I am also considering using GCD but I'm not sure of the structure for that.
Generally the pattern for something like this is to store the request object you created in DownloadLocationDetails so you can check to see if one is active before making another call. If you only want to support one at a time, then it's as simple as keeping the bare reference to the request object, but you could make a dictionary of request objects keyed off the placeID (and you probably want to think about maximum request count, and queue up additional requests).
Then the trick is to get notified when the given request object completes. There are a couple ways you could do this, such as keeping a list of callbacks to invoke when it completes, but the easiest would probably be just to refactor the code a bit so that you always update your UI when the request completes, so something like:
private func DownloadLocationDetails(placeID: String) {
let request = AF.request(GoogleAPI.shared.getLocationDetailsLink(placeID: placeID))
request.responseJSON { (data) in
guard let detail = try? JSONDecoder().decode(LocationDetailsBase.self, from: data.data!),
let result = detail.result else {
print("Something went wrong fetching nearby locations.")
return
}
DownloadManager.shared.locationDetails.append(result)
// Notify the UI to refresh for placeID
}
}

How to use AXObserver in Swift

I tried to get notified when another Application creates a window using the Accessibility API and AXObserver. Here's my code:
let pid = NSWorkspace.shared.frontmostApplication!.processIdentifier
var observer: AXObserver?
if AXObserverCreate(pid, { (observer, element, notification, userData) in
// Does this get executed when a notification comes in?
print(notification)
}, &observer) == .success {
// This does get printed!
print("Successfully created Observer!")
}
if AXObserverAddNotification(observer!, element, notification, nil) == .success {
// This also gets printed!
print("Successfully added Notification!")
}
CFRunLoopAddSource(RunLoop.current.getCFRunLoop(), AXObserverGetRunLoopSource(observer!), CFRunLoopMode.defaultMode)
Am I missing something? The code compiles and runs, but my calback doesn't get executed.
Does the callback get executed when a notification comes in?
see: https://developer.apple.com/documentation/applicationservices/1459139-axobservergetrunloopsource
Note that releasing the AXObserverRef automatically removes the run loop source from the run loop
your var observer: AXObserver? is deallocated so you'll never get the call. you need to keep a reference to your AXObserver somewhere.
good luck. the AX API is a pain.

SwiftUI: List does not update automatically after deleting all Core Data Entity entries

I know SwiftUI uses state-driven rendering. So I was assuming, when I delete Core Data Entity entries, that my List with Core Data elements gets refreshed immediately.
I use this code, which gets my Entity cleaned succesfully:
func deleteAll()
{
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = ToDoItem.fetchRequest()
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
let persistentContainer = (UIApplication.shared.delegate as! AppDelegate).persistentContainer
do {
try persistentContainer.viewContext.execute(deleteRequest)
} catch let error as NSError {
print(error)
}
}
To get the List in my View visually empty I have to leave the View afterwards (for example with " self.presentationMode.wrappedValue.dismiss()") and open it again. As if the values are still stored somewhere in the memory or something.
This is of course not user-friendly and I am sure I just oversee something that refreshes the List immediately.
Maybe someone can help.
The reason is that execute (as described in details below - pay attention on first sentence) does not affect managed objects context, so all fetched objects remains in context and UI represents what is really presented by context.
So in general, after this bulk operation you need to inform back to that code (not provided here) force sync and refetch everything.
API interface declaration
// Method to pass a request to the store without affecting the contents of the managed object context.
// Will return an NSPersistentStoreResult which may contain additional information about the result of the action
// (ie a batch update result may contain the object IDs of the objects that were modified during the update).
// A request may succeed in some stores and fail in others. In this case, the error will contain information
// about each individual store failure.
// Will always reject NSSaveChangesRequests.
#available(iOS 8.0, *)
open func execute(_ request: NSPersistentStoreRequest) throws -> NSPersistentStoreResult
For example it might be the following approach (scratchy)
// somewhere in View declaration
#State private var refreshingID = UUID()
...
// somewhere in presenting fetch results
ForEach(fetchedResults) { item in
...
}.id(refreshingID) // < unique id of fetched results
...
// somewhere in bulk delete
try context.save() // < better to save everything pending
try context.execute(deleteRequest)
context.reset() // < reset context
self.refreshingID = UUID() // < force refresh
No need to force a refresh, this is IMO not a clean solution.
As you correctly mentioned in your question, there are still elements in memory. The solution is to update your in-memory objects after the execution with mergeChanges.
This blog post explains the solution in detail under "Updating in-memory objects".
There, the author provides an extension to NSBatchDeleteRequest as follows
extension NSManagedObjectContext {
/// Executes the given `NSBatchDeleteRequest` and directly merges the changes to bring the given managed object context up to date.
///
/// - Parameter batchDeleteRequest: The `NSBatchDeleteRequest` to execute.
/// - Throws: An error if anything went wrong executing the batch deletion.
public func executeAndMergeChanges(using batchDeleteRequest: NSBatchDeleteRequest) throws {
batchDeleteRequest.resultType = .resultTypeObjectIDs
let result = try execute(batchDeleteRequest) as? NSBatchDeleteResult
let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self])
}
}
Here is an update to your code on how to call it:
func deleteAll() {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = ToDoItem.fetchRequest()
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
let persistentContainer = (UIApplication.shared.delegate as! AppDelegate).persistentContainer
do {
try persistentContainer.viewContext.executeAndMergeChanges(deleteRequest)
} catch let error as NSError {
print(error)
}
}
Some more info also here under this link: Core Data NSBatchDeleteRequest appears to leave objects in context.

How to test a function that gets into the main thread in Swift with RxSwift and XCTest?

I came across this problem when testing my View:
In my ViewModel I call to an asynchronous operation and when the response arrives, I use a PublishSubject to produce a change in my View. In my View, I call DispatchQueue.main.async in order to hide or show a button.
ViewModel
let refreshButtons = PublishSubject<Bool>(true)
refreshButtons.onNext(true)
View
model.refreshButtons.asObservable()
.subscribe(onNext: {
[unowned self] success in
self.updateButtons(success)
})
.addDisposableTo(disposable)
private func updateButtons(_ show:Bool) {
DispatchQueue.main.async{
button.isHidden = !show
}
}
Now I don't know how to unit test that refreshButtons.onNext(true) will hide or show my button.
The solutions I can think of are:
Overriding the method and having an async expectation, but for that I need to make the method public, what I don't want, or
Dispatching the main queue in my ViewModel and not in the view, what it sounds odd to me, but might me ok.
How can I solve this?
Thank you in advance.
You could use an async expectation based on a predicate in your unit test to wait an see if the button is not hidden anymore.
func testButtonIsHidden() {
// Setup your objects
let view = ...
let viewModel = ...
// Define an NSPredicate to test your expectation
let predicate = NSPredicate(block: { input, _ in
guard let _view = input as? MyView else { return false }
return _view.button.isHidden == true
})
// Create an expectation that will periodically evaluate the predicate
// to decided whether it's fulfilled or not
_ = expectation(for: predicate, evaluatedWith: view, handler: .none)
// Call the method that should generate the behaviour you are expecting.
viewModel.methodThatShouldResultInButtonBeingHidden()
// Wait for the
waitForExpectationsWithTimeout(1) { error in
if let error = error {
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
}
}
}
Something worth noting is that the value you pass to the NSPredicate should be a class. That is because classes are passed by reference, so value inside the predicate block will be the same as the one touched by your view model. If you were to pass a struct or enum though, which are passed by copy, the predicate block would receive a copy of the value as it is at the time of running the setup code, and it will always fail.
If instead you prefer to use UI tests as suggested by #Randall Wang in his answer, then this post might be useful for you: "How to test UI changes in Xcode 7". Full disclosure, I wrote that post.
First of all, You don't need test private method
If you want to test if the button is hidden or not,try UI testing
here is the WWDC of UI testing.
https://developer.apple.com/videos/play/wwdc2015/406/

Wait for Parse Async functions to complete in Swift

I'm trying to wait for Parse async functions in Swift to reload my UITableView
I'm not sure if Completion Handler is useful in this case. or Dispatch Async.
I'm really confused ! Can someone help out with this
var posts = [PFObject]()
for post in posts {
post.fetchInBackground()
}
tableView.reloadData() // I want to execute that when the async functions have finished execution
You want to use fetchAllInBackground:Block I've had issues launching a bunch of parse calls in a loop where it will take a lot longer to return all of them than expected.
fetch documentation
It should look something like this:
PFObject.fetchAllInBackground(posts, block: { (complete, error) in
if (error == nil && complete) {
self.tableView.reloadData()
}
})
One thing to note is that in your example posts are empty and a generic PFObject. I'm assuming this is just for the example. Otherwise if you want to get all posts in Parse (as opposed to updating current ones) you will want to use PFQuery instead of fetching. query documentation
You need to use fetchInBackgroundWithBlock. Alternatively, if you want to wait until all have loaded and then update the UI, use PFObject's +fetchAllInBackground:block:. Note that this is a class method, and would therefore be called as PFObject.fetchAllInBackground(.... See documentation here.
Either way, because you're running in a background thread, you must update the UI on the main thread. This is normally done using dispatch_async.
The other thing to watch out for is if you run fetchInBackgroundWithBlock in a loop and collect all the results in an array, arrays are not thread safe. You will have to use something like dispatch_barrier or your own synchronous queue to synchronise access to the array. Code for the second option is below:
// Declared once and shared by each call (set your own name)...
let queue = dispatch_queue_create("my.own.queue", nil)
// For each call...
dispatch_sync(queue) {
self.myArray.append(myElement)
}
Here's a little class I made to help with coordination of asynchronous processes:
class CompletionBlock
{
var completionCode:()->()
init?(_ execute:()->() )
{ completionCode = execute }
func deferred() {}
deinit
{ completionCode() }
}
The trick is to create an instance of CompletionBlock with the code you want to execute after the last asynchronous block and make a reference to the object inside the closures.
let reloadTable = CompletionBlock({ self.tableView.reloadData() })
var posts = [PFObject]()
for post in posts
{
post.fetchInBackground(){ reloadTable.deferred() }
}
The object will remain "alive" until the last capture goes out of scope. Then the object itself will go out of scope and its deinit will be called executing your finalization code at that point.
Here is an example of using fetchInBackgroundWithBlock which reloads a tableView upon completion
var myArray = [String]()
func fetchData() {
let userQuery: PFQuery = PFUser.query()!
userQuery.findObjectsInBackgroundWithBlock({
(users, error) -> Void in
var userData = users!
if error == nil {
if userData.count >= 1 {
for i in 0...users!.count-1 {
self.myArray.append(userData[i].valueForKey("dataColumnInParse") as! String)
}
}
self.tableView.reloadData()
} else {
print(error)
}
})
}
My example is a query on the user class but you get the idea...
I have experimented a bit with the blocks and they seem to get called on the main thread, which means that any UI changes can be made there. The code I have used to test looks something like this:
func reloadPosts() {
PFObject.fetchAllIfNeededInBackground(posts) {
[unowned self] (result, error) in
if let err = error {
self.displayError(err)
}
self.tableView.reloadData()
}
}
if you are in doubt about whether or not the block is called on the main thread you can use the NSThread class to check for this
print(NSThread.currentThread().isMainThread)
And if you want it to be bulletproof you can wrap your reloadData inside dispatch_block_tto ensure it is on the main thread
Edit:
The documentation doesn't state anywhere if the block is executed on the main thread, but the source code is pretty clear that it does
+ (void)fetchAllIfNeededInBackground:(NSArray *)objects block:(PFArrayResultBlock)block {
[[self fetchAllIfNeededInBackground:objects] thenCallBackOnMainThreadAsync:block];
}