How does DispatchQueue.main.async store it's blocks - ios

I have a code similar to this:
func fetchBalances() -> Observable<Result<[User], Error>> {
Observable.create { observer in
var dataChangeDisposable: Disposable?
DispatchQueue.main.async {
let realm = try! Realm()
let user = realm.objects(UserData.self)
dataChangeDisposable = Observable.collection(from: user)
.map { $0.map { UserData.convert($0) } }
.subscribe(onNext: {
observer.onNext(.success($0))
})
}
return Disposables.create {
dataChangeDisposable?.dispose()
}
}
}
I need to use some thread with run loop in order to maintain subscription to Realm database (Realm's restriction). For now I'm using DispatchQueue.main.async {} method and I noticed that subscription remains active all the time, how does DispatchQueue.main stores it's submitted blocks and if Observable destroys does it mean that I'm leaking blocks in memory?

The block sent to the dispatch queue is deleted immediately after execution. It isn't stored for very long at all.
If your subscription "remains active all the time" then it's because it's not being disposed of properly. Likely what is happening here is that the block sent to Disposables.create is being called before dataChangeDisposable contains a value.
Test my hypothesis by changing the code to:
return Disposables.create {
dataChangeDisposable!.dispose()
}
If your app crashes because dataChangeDisposable is nil, then that's your problem.

Related

How to move to the next view upon data reception?

I am struggling to trigger the logic responsible for changing the view at the right time. Let me explain.
I have a view model that contains a function called createNewUserVM(). This function triggers another function named requestNewUser() which sits in a struct called Webservices.
func createNewUserVM() -> String {
Webservices().requestNewUser(with: User(firstName: firstName, lastName: lastName, email: email, password: password)) { serverResponse in
guard let serverResponse = serverResponse else {
return "failure"
}
return serverResponse.response
}
}
Now that's what's happening in the Webservices' struct:
struct Webservices {
func requestNewUser(with user: User, completion: #escaping (Response?) -> String) -> String {
//code that creates the desired request based on the server's URL
//...
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
serverResponse = completion(nil)
}
return
}
let decodedResponse = try? JSONDecoder().decode(Response.self, from: data)
DispatchQueue.main.async {
serverResponse = completion(decodedResponse)
}
}.resume()
return serverResponse //last line that gets executed before the if statement
}
}
So as you can see, the escaping closure (whose code is in the view model) returns serverResponse.response (which can be either "success" or "failure"), which is then stored in the variable named serverResponse. Then, requestNewUser() returns that value. Finally, the createNewUserVM() function returns the returned String, at which point this whole logic ends.
In order to move to the next view, the idea was to simply check the returned value like so:
serverResponse = self.signupViewModel.createNewUserVM()
if serverResponse == "success" {
//move to the next view
}
However, after having written a few print statements, I found out that the if statement gets triggered way too early, around the time the escaping closure returns the value, which happens before the view model returns it. I attempted to fix the problem by using some DispatchQueue logic but nothing worked. I also tried to implement a while loop like so:
while serverResponse.isEmpty {
//fetch the data
}
//at this point, serverResponse is not empty
//move to the next view
It was to account for the async nature of the code.
I also tried was to pass the EnvironmentObject that handles the logic behind what view's displayed directly to the view model, but still without success.
As matt has pointed out, you seem to have mixed up synchronous and asynchronous flows in your code. But I believe the main issue stems from the fact that you believe URLSession.shared.dataTask executes synchronously. It actually executes asynchronously. Because of this, iOS won't wait until your server response is received to execute the rest of your code.
To resolve this, you need to carefully read and convert the problematic sections into asynchronous code. Since the answer is not trivial in your case, I will try my best to help you convert your code to be properly asynchronous.
1. Lets start with the Webservices struct
When you call the dataTask method, what happens is iOS creates a URLSessionDataTask and returns it to you. You call resume() on it, and it starts executing on a different thread asynchronously.
Because it executes asynchronously, iOS doesn't wait for it to return to continue executing the rest of your code. As soon as the resume() method returns, the requestNewUser method also returns. By the time your App receives the JSON response the requestNewUser has returned long ago.
So what you need to do to pass your response back correctly, is to pass it through the "completion" function type in an asynchronous manner. We also don't need that function to return anything - it can process the response and carry on the rest of the work.
So this method signature:
func requestNewUser(with user: User, completion: #escaping (Response?) -> String) -> String {
becomes this:
func requestNewUser(with user: User, completion: #escaping (Response?) -> Void) {
And the changes to the requestNewUser looks like this:
func requestNewUser(with user: User, completion: #escaping (Response?) -> Void) {
//code that creates the desired request based on the server's URL
//...
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(nil)
}
return
}
let decodedResponse = try? JSONDecoder().decode(Response.self, from: data)
DispatchQueue.main.async {
completion(decodedResponse)
}
}.resume()
}
2. View Model Changes
The requestNewUser method now doesn't return anything. So we need to accommodate that change in our the rest of the code. Let's convert our createNewUserVM method from synchronous to asynchronous. We should also ask the calling code for a function that would receive the result from our Webservice class.
So your createNewUserVM changes from this:
func createNewUserVM() -> String {
Webservices().requestNewUser(with: User(firstName: firstName, lastName: lastName, email: email, password: password)) { serverResponse in
guard let serverResponse = serverResponse else {
return "failure"
}
return serverResponse.response
}
}
to this:
func createNewUserVM(_ callback: #escaping (_ response: String?) -> Void) {
Webservices().requestNewUser(with: User(firstName: firstName, lastName: lastName, email: email, password: password)) { serverResponse in
guard let serverResponse = serverResponse else {
callback("failure")
return
}
callback(serverResponse.response)
}
}
3. Moving to the next view
Now that createNewUserVM is also asynchronous, we also need to change how we call it from our controller.
So that code changes from this:
serverResponse = self.signupViewModel.createNewUserVM()
if serverResponse == "success" {
//move to the next view
}
To this:
self.signupViewModel.createNewUserVM{ [weak self] (serverResponse) in
guard let `self` = self else { return }
if serverResponse == "success" {
// move to the next view
// self.present something...
}
}
Conclusion
I hope the answer gives you an idea of why your code didn't work, and how you can convert any existing code of that sort to execute properly in an asynchronous fashion.
This can be achieve using DispatchGroup and BlockOperation together like below:
func functionWillEscapeAfter(time: DispatchTime, completion: #escaping (Bool) -> Void) {
DispatchQueue.main.asyncAfter(deadline: time) {
completion(false) // change the value to reflect changes.
}
}
func createNewUserAfterGettingResponse() {
let group = DispatchGroup()
let firstOperation = BlockOperation()
firstOperation.addExecutionBlock {
group.enter()
print("Wait until async block returns")
functionWillEscapeAfter(time: .now() + 5) { isSuccess in
print("Returned value after specified seconds...")
if isSuccess {
group.leave()
// and firstoperation will be complete
} else {
firstOperation.cancel() // means first operation is cancelled and we can check later if cancelled don't execute next operation
group.leave()
}
}
group.wait() //Waits until async closure returns something
} // first operation ends
let secondOperation = BlockOperation()
secondOperation.addExecutionBlock {
// Now before executing check if previous operation was cancelled we don't need to execute this operation.
if !firstOperation.isCancelled { // First operation was successful.
// move to next view
moveToNextView()
} else { // First operation was successful.
// do something else.
print("Don't move to next block")
}
}
// now second operation depends upon the first operation so add dependency
secondOperation.addDependency(firstOperation)
//run operation in queue
let operationQueue = OperationQueue()
operationQueue.addOperations([firstOperation, secondOperation], waitUntilFinished: false)
}
func moveToNextView() {
// move view
print("Move to next block")
}
createNewUserAfterGettingResponse() // Call this in playground to execute all above code.
Note: Read comments for understanding. I have run this in swift playground and working fine. copy past code in playground and have fun!!!

using NWPathMonitor with BehaviorSubject to monitor network connectivity

I need an observer to keep my app updated on the connectivity status of the device.
NWPathMonitor seems to be the standard approach. So I go like this:
class NetworkService {
let monitor = NWPathMonitor()
let connected = BehaviorSubject(value: true)
private init() {
monitor.pathUpdateHandler = { path in
let value = path.status == .satisfied
self.connected.onNext(value)
}
let queue = DispatchQueue(label: "NetworkMonitor")
monitor.start(queue: queue)
}
}
And this is where I subscribe to connected
NetworkService.shared.connected.subscribe(onNext: { connected in
print("network connected: \(connected)")
}).disposed(by: disposeBag)
As soon as the app starts, onNext starts firing like crazy, flooding the console with network connected: true until the app crashes.
I tried adding a local cache variable so the onNext part fires only if there's been a change on the value.
if (value != self.previousValue) {
self.previousValue = value
self.connected.onNext(value)
}
Same happens still. So I guessed maybe the monitor is updating too frequently to allow the cache variable to get assigned, and I tried adding a semaphore ...
self.semaphore.wait()
if (value != self.previousValue) {
self.previousValue = value
self.connected.onNext(value)
}
self.semaphore.signal()
And event that didn't help. Still getting a flood of print messages and the app crashes.
BTW, if you were wondering this is how I declare the semaphore in my class:
let semaphore = DispatchSemaphore( value: 1)
I'm not seeing the same behavior from the class as you, but a simple solution is to use .distinctUntilChanged() which will stop an event from propagating unless it is different than the previous event.
If the above doesn't stop the flood of events, then the problem isn't with the code you have presented, but with something else you haven't told us about.
Also, I would have written it like this:
extension NWPathMonitor {
var rx_path: Observable<NWPath> {
Observable.create { [self] observer in
self.pathUpdateHandler = { path in
observer.onNext(path)
}
let queue = DispatchQueue(label: "NetworkMonitor")
self.start(queue: queue)
return Disposables.create {
self.cancel()
}
}
}
}
With the above, it's easy to access by doing:
let disposable = NWPathMonitor().rx_path
.map { $0.status == .satisfied }
.debug()
.subscribe()
The subscription will keep the NWPathMonitor object alive for the duration of the subscription. Calling dispose() on the disposable will shut down the subscription and release the NWPathMonitor object.

Performing selector on Main thread from the current thread inside performAndWait block of a NSManagedObjectContext

I have an NSManagedObjectContext which is initialised from newBackgroundContext of the persistentContainer as following:
managedContext = coreDataStack.persistentContainer.newBackgroundContext()
This is how persistentContainer looks like:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "myXCDataModelName")
container.loadPersistentStores(completionHandler: { [weak self] (_, error) in
if let self = self,
let error = error as NSError? {
print("error!")
}
})
return container
}()
I'm using newBackgroundContext to make sure any CRUD operation with CoreData can be done safely, regardless what thread attempts to make changes on the managedContext, instead of making sure or forcing each operation is done on main thread.
I have a saveContext method where I try to perform save operation on managedContext inside performAndWait block as following:
managedContext.performAndWait {
do {
guard UIApplication.shared.isProtectedDataAvailable,
managedContext.hasChanges else {
return
}
try managedContext.save()
} catch {
print("error!")
}
}
It looks like performAndWait is most of the time runs on main thread but when it is run on another thread, the thread checker generates a warning for following check UIApplication.shared.isProtectedDataAvailable since it should be done on main thread.
I decided to run a selector on main thread for this check so I declared a isProtectedDataAvailable Bool by defaulting it to false at class level and then update its value when this selector runs.
#objc private func checker() {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
}
Now I refactored the performAndWait block as following to run the check on main thread if it is called from another thread.
managedContext.performAndWait {
do {
if Thread.isMainThread {
checker()
} else {
print("RUNNING ON ANOTHER THREAD")
Thread.current.perform(#selector(checker),
on: Thread.main,
with: nil,
waitUntilDone: true,
modes: nil)
}
guard isProtectedDataAvailable,
managedContext.hasChanges else {
return
}
try managedContext.save()
} catch {
print("error!")
}
}
It seems to be working fine when I run it on simulator or real device, I generate different core data related operations which would trigger saving context both on main and background threads.
But what happens is, if I put some breakpoints inside performAndWait block and stop execution to examine how the code block is working, it sometimes results in application freeze when I continue execution, like a deadlock occurs. I wonder if it is somehow related to stopping execution with breakpoints or something is wrong with my implementation even though it is working fine without breakpoints, no app freeze or anything.
I'm worried because before going with this solution, I tried synchronizing on main thread by the inspiration from this answer to just switch to main thread to make this check, as following (which resulted in app freeze and I assume a deadlock) inside performAndWait block:
var isProtectedDataAvailable = false
if Thread.isMainThread {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
} else {
DispatchQueue.main.sync {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
}
}
I had to use sync instead of async, because I had to retrieve updated value of isProtectedDataAvailable before proceeding execution.
Any ideas on this?
I assume your question is about the right approach so that the code is working fine and it allows to set any breakpoints without running into deadlocks. :)
Why not try it this way (from what you stated i cannot see reasons against it):
First evaluating UIApplication.shared.isProtectedDataAvailable on the main thread. (I guess that there are more complex conditions that must hold not only UIApplication.shared.isProtectedDataAvailable = true, but to keep it simple ...)
If UIApplication.shared.isProtectedDataAvailable (and otherConditions) evaluates as true continue with data processing and saving on background thread. As managedContext was created as a newBackgroundContext it can be used there.
Instructions on main thread
if UIApplication.shared.isProtectedDataAvailable && otherConditions {
DispatchQueue.global(qos: .userInitiated).async {
dataProcessing()
}
}
Instructions on background thread
static func dataProcessing() {
// get the managedContext
let context = AppDelegate.appDelegate.managedContext
context.performAndWait {
if context.hasChanges {
do {
try context.save()
} catch {
print("error!")
}
}
}
}
For some reason, moving the following check and synchronising with main queue outside of the performAndWait solves all the problems.
No deadlock or app freeze occurs either with break points or without, regardless which thread triggers the method which contains performAndWait
So the method body looks like following now:
var isProtectedDataAvailable = false
if Thread.isMainThread {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
} else {
DispatchQueue.main.sync {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
}
}
managedContext.performAndWait {
do {
guard isProtectedDataAvailable,
managedContext.hasChanges else {
return
}
try managedContext.save()
} catch {
print("error")
}
}

Combine Future Publisher is not getting deallocated

I am using the Combine Future to wrap around an async block operation and adding a subscriber to that publisher to receive the values.. I am noticing the future object is not getting deallocated, even after the subscribers are deallocated. The XCode memory graph and instruments leaks graph itself shows no reference to these future objects. I am puzzled why are they still around.
func getUsers(forceRefresh: Bool = false) -> AnyPublisher<[User], Error> {
let future = Future<[User], Error> { [weak self] promise in
guard let params = self?.params else {
promise(.failure(CustomErrors.invalidData))
return
}
self?.restApi.getUsers(params: params, forceRefresh: forceRefresh, success: { (users: [User]?, _) in
guard let users = users else {
return promise(.failure(CustomErrors.invalidData))
}
promise(.success(users))
}) { (error: Error) in
promise(.failure(error))
}
}
return future.eraseToAnyPublisher()
}
Here's how I am adding a subscription:
self.userDataService?.getUsers(forceRefresh: forceRefresh)
.sink(receiveCompletion: { [weak self] completion in
self?.isLoading = false
if case let .failure(error) = completion {
self?.publisher.send(.error(error))
return
}
guard let users = self?.users, !users.isEmpty else {
self?.publisher.send(.empty)
return
}
self?.publisher.send(.data(users))
}) { [weak self] (response: Array<User>) in
self?.users = response
}.store(in: &self.subscribers)
deinit {
self.subscribers.removeAll()
}
This is the screenshot of the leaked memory for the future that got created above.. It's still staying around even after the subscribers are all deleted. Instruments is also showing a similar memory graph. Any thoughts on what could be causing this ??
Future invokes its closure immediately upon creation, which may be impacting this. You might try wrapping the Future in Deferred so that it isn't created until a subscription happens (which may be what you're expecting anyway from scanning the code).
The fact that it's creating one immediately is what (I think) is being reflected in the objects listed when there are no subscribers.

Realm notification token on background thread

I was trying to fetch realm data on the background thread and add a notification block (iOS, Swift).
Basic example:
func initNotificationToken() {
DispatchQueue.global(qos: .background).async {
let realm = try! Realm()
results = self.getRealmResults()
notificationToken = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
switch changes {
case .initial:
self?.initializeDataSource()
break
case .update(_, let deletions, let insertions, let modifications):
self?.updateDataSource(deletions: deletions, insertions: insertions, modifications: modifications)
break
case .error(let error):
fatalError("\(error)")
break
}
}
}
}
func initializeDataSource() {
// process the result set data
DispatchQueue.main.async(execute: { () -> Void in
// update UI
})
}
func updateDataSource(deletions: [Int], insertions: [Int], modifications: [Int]) {
// process the changes in the result set data
DispatchQueue.main.async(execute: { () -> Void in
// update UI
})
}
When doing this I get
'Can only add notification blocks from within runloops'
I have to do some more extensive processing with the returned data and would like to only go back to the main thread when updating the UI after the processing is done.
Another way would probably to re-fetch the data after any update on the background thread and then do the processing, but it feels like avoidable overhead.
Any suggestions on the best practice to solve this?
To add a notification on a background thread you have to manually run a run loop on that thread and add the notification from within a callout from that run loop:
class Stuff {
var token: NotificationToken? = nil
var notificationRunLoop: CFRunLoop? = nil
func initNotificationToken() {
DispatchQueue.global(qos: .background).async {
// Capture a reference to the runloop so that we can stop running it later
notificationRunLoop = CFRunLoopGetCurrent()
CFRunLoopPerformBlock(notificationRunLoop, CFRunLoopMode.defaultMode.rawValue) {
let realm = try! Realm()
results = self.getRealmResults()
// Add the notification from within a block executed by the
// runloop so that Realm can verify that there is actually a
// runloop running on the current thread
token = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
// ...
}
}
// Run the runloop on this thread until we tell it to stop
CFRunLoopRun()
}
}
deinit {
token?.stop()
if let runloop = notificationRunLoop {
CFRunLoopStop(runloop)
}
}
}
GCD does not use a run loop on its worker threads, so anything based on dispatching blocks to the current thread's run loop (such as Realm's notifications) will never get called. To avoid having notifications silently fail to do anything Realm tries to check for this, which unfortunately requires the awakward PerformBlock dance.

Resources