Last active
January 31, 2022 07:44
-
-
Save jlubawy/89b79a11d9ef4ee9a57853fd593bbb8b to your computer and use it in GitHub Desktop.
Using TheadRunner class to read/write Realm objects on single background thread
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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