Skip to content

Instantly share code, notes, and snippets.

@jlubawy
Last active January 31, 2022 07:44
Show Gist options
  • Save jlubawy/89b79a11d9ef4ee9a57853fd593bbb8b to your computer and use it in GitHub Desktop.
Save jlubawy/89b79a11d9ef4ee9a57853fd593bbb8b to your computer and use it in GitHub Desktop.
Using TheadRunner class to read/write Realm objects on single background thread
import Foundation
class ClassThatUsesThreadRunner {
let bgThread: ThreadRunner
/// Global Realm instance that may only be accessed from background thread
var bgRealm: Realm?
public init() {
self.bgThread = ThreadRunner(name: "Realm Background Thread")
self.bgThread.start()
self.bgThread.run {
// Initialize Realm instance on background thread
self.bgRealm = try? Realm(configuration: someConfig)
}
}
deinit {
self.bgThread.cancel()
}
/// Create a new Realm object on the background thread.
public func createObjectAsync(someData: SomeDataClass, _ completion: @escaping () -> Void) {
self.bgThread.run {
createObject(someData: someData)
completion()
}
}
// Must be called on background thread.
private func createObject(someData: SomeDataClass) {
assert(self.bgThread.inThreadContext, "must call from background thread")
let someRealmObject = SomeRealmObject(someData)
self.bgRealm?.write {
self.bgRealm.add(someRealmObject)
}
}
public func getObject() -> SomeDataClass? {
var dataMaybe: SomeDataClass?
// Call synchronous method to wait for data to be returned on this thread
self.bgThread.runSync {
if let someRealmObject = self.bgRealm?.objects(SomeRealmObject.class).first {
dataMaybe = SomeDataClass(someRealmObject)
}
}
return dataMaybe
}
}
import Foundation
/// A ThreadRunner is a convenience wrapper that allows running blocks on the same Thread every time.
public class ThreadRunner: NSObject {
private var thread: Thread!
public init(name: String? = nil) {
super.init()
self.thread = Thread(target: self, selector: #selector(threadWorker), object: nil)
if let threadName = name {
self.thread.name = threadName
}
}
deinit {
self.cancel()
}
/// Returns true if the current thread is equal to the ThreadRunner's thread.
public var inThreadContext: Bool {
return Thread.current.isEqual(self.thread)
}
/// Starts the ThreadRunner's thread and enters the run loop.
public func start() {
self.thread.start()
}
/// Cancels the ThreadRunner's thread and exits the run loop.
public func cancel() {
self.thread.cancel()
}
/// Runs a block in the ThreadRunner's thread context asynchronously.
///
/// - parameter block: block to run
///
public func run(_ block: @escaping () -> Void) {
if self.inThreadContext {
block()
} else {
perform(#selector(runBlockOnThread),
on: self.thread,
with: RunBlockOnThreadArgs(block: block),
waitUntilDone: false,
modes: [RunLoopMode.defaultRunLoopMode.rawValue])
}
}
/// Runs a block in the ThreadRunner's thread context synchronously.
///
/// - parameter block: block to run
///
public func runSync(_ block: @escaping () -> Void) {
let semaphore = DispatchSemaphore(value: 0)
self.run {
block()
semaphore.signal()
}
semaphore.wait()
}
// Encapsulates the run block in an Obj-C object so it can be passed as an argument
// to `runBlockOnThread`. Without this we get an exception trying to pass the block on
// its own.
private class RunBlockOnThreadArgs: NSObject {
let block: () -> Void
init(block: @escaping () -> Void) {
self.block = block
}
}
@objc private func runBlockOnThread(args: RunBlockOnThreadArgs) {
args.block()
}
@objc private func threadWorker() {
defer { Thread.exit() }
while self.thread != nil, !self.thread.isCancelled {
RunLoop.current.run(mode: RunLoopMode.defaultRunLoopMode, before: Date.distantFuture)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment