Realm notification token on background thread - ios

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.

Related

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

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.

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")
}
}

Completion handlers and Operation queues

I am trying to do the following approach,
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 10
func registerUser(completionHandler: #escaping (Result<Data, Error>) -> Void) -> String {
self.registerClient() { (result) in
switch result {
case .success(let data):
self.downloadUserProfile(data.profiles)
case .failure(let error):
return self.handleError(error)
}
}
}
func downloadUserProfile(urls: [String]) {
for url in urls {
queue.addOperation {
self.client.downloadTask(with: url)
}
}
}
I am checking is there anyway I can get notified when all operations gets completed and then I can call the success handler there.
I tried checking the apple dev documentation which suggests to use
queue.addBarrierBlock {
<#code#>
}
but this is available only from iOS 13.0
Pre iOS 13, we’d use dependencies. Declare a completion operation, and then when you create operations for your network requests, you’d define those operations to be dependencies for your completion operation.
let completionOperation = BlockOperation { ... }
let networkOperation1 = ...
completionOperation.addDependency(networkOperation1)
queue.addOperation(networkOperation1)
let networkOperation2 = ...
completionOperation.addDependency(networkOperation2)
queue.addOperation(networkOperation2)
OperationQueue.main.addOperation(completionOperation)
That having been said, you should be very careful with your operation implementation. Do I correctly infer that downloadTask(with:) returns immediately after the download task has been initiated and doesn’t wait for the request to finish? In that case, neither dependencies nor barriers will work the way you want.
When wrapping network requests in an operation, you’d want to make sure to use an asynchronous Operation subclass (e.g. https://stackoverflow.com/a/32322851/1271826).
The pre-iOS 13 way is to observe the operationCount property of the operation queue
var observation : NSKeyValueObservation?
...
observation = operationQueue.observe(\.operationCount, options: [.new]) { observed, change in
if change.newValue == 0 {
print("operations finished")
}
}
}

How to ensure to run some code on same background thread?

I am using realm in my iOS Swift project. Search involve complex filters for a big data set. So I am fetching records on background thread.
But realm can be used only from same thread on which Realm was created.
I am saving a reference of results which I got after searching Realm on background thread. This object can only be access from same back thread
How can I ensure to dispatch code at different time to the same thread?
I tried below as suggested to solve the issue, but it didn't worked
let realmQueue = DispatchQueue(label: "realm")
var orginalThread:Thread?
override func viewDidLoad() {
super.viewDidLoad()
realmQueue.async {
self.orginalThread = Thread.current
}
let deadlineTime = DispatchTime.now() + .seconds(2)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
self.realmQueue.async {
print("realm queue after some time")
if self.orginalThread == Thread.current {
print("same thread")
}else {
print("other thread")
}
}
}
}
Output is
realm queue after some time
other thread
Here's a small worker class that can works in a similar fashion to async dispatching on a serial queue, with the guarantee that the thread stays the same for all work items.
// Performs submitted work items on a dedicated thread
class Worker {
// the worker thread
private var thread: Thread?
// used to put the worker thread in the sleep mode, so in won't consume
// CPU while the queue is empty
private let semaphore = DispatchSemaphore(value: 0)
// using a lock to avoid race conditions if the worker and the enqueuer threads
// try to update the queue at the same time
private let lock = NSRecursiveLock()
// and finally, the glorious queue, where all submitted blocks end up, and from
// where the worker thread consumes them
private var queue = [() -> Void]()
// enqueues the given block; the worker thread will execute it as soon as possible
public func enqueue(_ block: #escaping () -> Void) {
// add the block to the queue, in a thread safe manner
locked { queue.append(block) }
// signal the semaphore, this will wake up the sleeping beauty
semaphore.signal()
// if this is the first time we enqueue a block, detach the thread
// this makes the class lazy - it doesn't dispatch a new thread until the first
// work item arrives
if thread == nil {
thread = Thread(block: work)
thread?.start()
}
}
// the method that gets passed to the thread
private func work() {
// just an infinite sequence of sleeps while the queue is empty
// and block executions if the queue has items
while true {
// let's sleep until we get signalled that items are available
semaphore.wait()
// extract the first block in a thread safe manner, execute it
// if we get here we know for sure that the queue has at least one element
// as the semaphore gets signalled only when an item arrives
let block = locked { queue.removeFirst() }
block()
}
}
// synchronously executes the given block in a thread-safe manner
// returns the same value as the block
private func locked<T>(do block: () -> T) -> T {
lock.lock(); defer { lock.unlock() }
return block()
}
}
Just instantiate it and let it do the job:
let worker = Worker()
worker.enqueue { print("On background thread, yay") }
You have to create your own thread with a run loop for that. Apple gives an example for a custom run loop in Objective C. You may create a thread class in Swift with that like:
class MyThread: Thread {
public var runloop: RunLoop?
public var done = false
override func main() {
runloop = RunLoop.current
done = false
repeat {
let result = CFRunLoopRunInMode(.defaultMode, 10, true)
if result == .stopped {
done = true
}
}
while !done
}
func stop() {
if let rl = runloop?.getCFRunLoop() {
CFRunLoopStop(rl)
runloop = nil
done = true
}
}
}
Now you can use it like this:
let thread = MyThread()
thread.start()
sleep(1)
thread.runloop?.perform {
print("task")
}
thread.runloop?.perform {
print("task 2")
}
thread.runloop?.perform {
print("task 3")
}
Note: The sleep is not very elegant but needed, since the thread needs some time for its startup. It should be better to check if the property runloop is set, and perform the block later if necessary. My code (esp. runloop) is probably not safe for race conditions, and it's only for demonstration. ;-)

How to runlong running tasks in iOS which are dependent on each other

I want to run some tasks which are dependent on each other so should be performed in an order. Currently, it blocks my UI thread and also there is some issue in ordering.
Couple of questions regarding this:
Tasks are not performed in correct order. What change to be made if we want them to be performed one after other
Is the code optimised in terms of memory usage and resource consumption? How it can be made more optimised?
Do we need global queues inside function call also as shown in code below?
Here are my code details. I have created some serial queues as follows:
var Q0_sendDisplayName=dispatch_queue_create("Q0_sendDisplayName",DISPATCH_QUEUE_SERIAL)
var Q1_fetchFromDevice=dispatch_queue_create("fetchFromDevice",DISPATCH_QUEUE_SERIAL)
var Q2_sendPhonesToServer=dispatch_queue_create("sendPhonesToServer",DISPATCH_QUEUE_SERIAL)
I have an idea that serial queues perform tasks in order so i have called my tasks on serial queues. Here is my code:
dispatch_sync(Q0_sendDisplayName,
{
self.sendNameToServer(displayName){ (result) -> () in
dispatch_sync(self.Q1_fetchFromDevice,
{
self.SyncfetchContacts({ (result) -> () in
dispatch_sync(self.Q2_sendPhonesToServer,
{ self.SyncSendPhoneNumbersToServer(self.syncPhonesList, completion: { (result) in
//.......
//....
The code inside these functions is also running on global queue. Dont know if it is a correct way to code. I have used completion handlers to notify that method has completed executing. Here is the code of function1:
func sendNameToServer(var displayName:String,completion:(result:Bool)->())
{
Alamofire.request(.POST,"\(urlToSendDisplayName)",headers:header,parameters:["display_name":displayName]).responseJSON{
response in
switch response.result {
case .Success:
return completion(result: true) //......
Here is the code of function2. This function is long as it reads whole contacts book so i have placed it inside global queue(dont know if it is right way). I call completion handler on main queue. Here is code:
func SyncfetchContacts(completion:(result:Bool)->())
{
let contactStore = CNContactStore()
var keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactPhoneNumbersKey, CNContactImageDataAvailableKey,CNContactThumbnailImageDataKey, CNContactImageDataKey]
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)){
do {
try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keys)) { (contact, pointer) -> Void in
if (contact.isKeyAvailable(CNContactPhoneNumbersKey)) {
for phoneNumber:CNLabeledValue in contact.phoneNumbers {
let a = phoneNumber.value as! CNPhoneNumber
}
}
}
dispatch_async(dispatch_get_main_queue())
{
completion(result: true)
}
}
//........
Here is the code for function3 which again inside has a global queue(dont know if its right) and calls completion handler on main queue.
func SyncSendPhoneNumbersToServer(phones:[String],completion: (result:Bool)->()){
Alamofire.request(.POST,"\(url)",headers:header,parameters:["display_name":displayName]).responseJSON{
response in
switch response.result {
case .Success:
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0))
{
//enter some large data in database in a loop
dispatch_async(dispatch_get_main_queue())
{
return completion(result: true)
}
}//......
In SyncfetchContacts you are calling the completion handler before contactStore.enumerateContactsWithFetchRequest has finished, outside of its completion closure.
Simply move it in there:
func SyncfetchContacts(completion:(result:Bool)->()) {
...
do {
try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keys)) { (contact, pointer) -> Void in
if (contact.isKeyAvailable(CNContactPhoneNumbersKey)) {
for phoneNumber:CNLabeledValue in contact.phoneNumbers {
let a = phoneNumber.value as! CNPhoneNumber
}
}
// here ...
dispatch_async(dispatch_get_main_queue()) {
completion(result: true)
}
}
// ... not here.
// dispatch_async(dispatch_get_main_queue()) {
// completion(result: true)
// }
}
}

Resources