How to init ManagedObjectContext on the right queue? - ios

Extremely need an advice, currently run out of ideas. I stack with core data concurrency related issue, to debug I use -"com.apple.CoreData.ConcurrencyDebug" and what I have:
Stack:
Thread 3 Queue: coredata(serial)
0 +[NSManagedObjectContext Multithreading_Violation_AllThatIsLeftToUsIsHonor]:
CoreData`-[NSManagedObjectContext executeFetchRequest:error:]:
1 -[NSManagedObjectContext executeFetchRequest:error:]:
2 NSManagedObjectContext.fetch (__ObjC.NSFetchRequest) throws -> Swift.Array:
3 AppDelegate.(fetchRequest NSFetchRequest) -> [A]).(closure #1)
I get into AppDelegate::fetchRequest from here:
let messageRequest: NSFetchRequest<ZMessage> = ZMessage.fetchRequest();
messageRequest.sortDescriptors = [NSSortDescriptor(key: "id", ascending: false)];
let messageArray: Array<ZMessage> = self.fetchRequest(messageRequest);
I perform all coredata stuff on the serial queue(self.queueContainer).
public func fetchRequest<T>(_ request: NSFetchRequest<T>) -> Array<T>
{
var retval: Array<T> = Array<T>();
self.queueContainer.sync {
do {
retval = try self.persistentContainer.viewContext.fetch(request);
} catch {
let nserror = error as NSError;
fatalError("[CoreData] Unresolved fetch error \(nserror), \(nserror.userInfo)");
}
}
return retval;
}
This is what I found useful.
Below are some of the rules that must be followed if you do not want your app that uses CoreData to crash (or) corrupt the database:
A NSManagedObjectContext should be used only on the queue that is associated with it.
If initialized with .PrivateQueueConcurrencyType, a private, internal queue is created that is associated with the object. This queue can be accessed by instance methods .performBlockAndWait (for sync ops) and .performBlock (for async ops)
If initialized with .MainQueueConcurrencyType, the object can be used only on the main queue. The same instance methods ( performBlock and performBlockAndQueue) can be used here as well.
An NSManagedObject should not be used outside the thread in which it is initialized
Now I'm digging around but honestly speaking can't be sure that my managed object context(MOC) is associated with the right queue.
From manual:
...A consequence of this is that a context assumes the default owner is the thread or queue that allocated it—this is determined by the thread that calls its init method.
In the AppDelegate I do not operate with MOC directly, instead of this I instantiating NSPersistentContainer which owns this MOC. Just in case I'm doing this on the same serial queue as well.
public lazy var persistentContainer: NSPersistentContainer =
{
self.queueContainer.sync {
let container = NSPersistentContainer(name: "Joker")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}
}()
Thanks in advance.

I am not a Swift coder , but What is queueContainer?
You should not do the threading yourself, you should use the NSManagedObjectContext block methods as you wrote in your quotation:
The same instance methods ( performBlock and performBlockAndQueue) can
be used here as well.
managedObjectContext.performBlock {
Whatever managedObjectContext you are using, you should use that context block method and do your stuff inside the block methods.
Look at the documentation here for examples on how to do this correctly.
Also to avoid crashes and threading errors:
NSManagedObject instances are not intended to be passed between
queues. Doing so can result in corruption of the data and termination
of the application. When it is necessary to hand off a managed object
reference from one queue to another, it must be done through
NSManagedObjectID instances.
You retrieve the managed object ID of a managed object by calling the
objectID method on the NSManagedObject instance.

Related

Realm Object in Completion Handler: 'Realm accessed from incorrect thread.'

I am developing an SDK that basically writes wrapper methods hiding API calls and returns data objects in completion Handler:
public static func getDogs(token: String?, completionhandler: #escaping (Int?, [Dog]?) -> Void) {
}
Dogs are Realm Objects:
public class Dog: Object {
#objc public dynamic var name: String?
#objc public dynamic var age: Int = 0
}
Now when I try to access Dogs in a call to getDogs,
getDogs(token: token) { status, dogs in
\\ Access Dog[0]
}
As expected, I get the RealmException:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.'
Now, How can I pass such a list of Realm objects in a completion handler and access it later without getting this exception? I know of ThreadSafeReference but I do not want end user to read a manual of third party dependency before being able to use the SDK. What should be the best practice here? Obviously, I cannot send a list of Realm objects. Also from the design considerations for an independent framework, I should not assume that our end users should know Realm apriori. Realm support should be good to have and not foremost.
Another option that I have is to keep two copies of the same data model: a normal object and a Realm object. But this will increase the parsing time.
As stated in the Realm documentation:
Instances of RLMRealm, RLMResults, or RLMArray, or managed instances of RLMObject are thread-confined, meaning that they can only be used on the thread on which they were created, otherwise an exception is thrown
In order to avoid this exception, you should create a Realm instance every time you need one and make sure you're releasing it with an autorelease pool.
// Query and update from any thread
autoreleasepool {
let realm = try! Realm()
let theDog = realm.objects(Dog.self).filter("age == 1").first
try! realm.write {
theDog!.age = 3
}
}

How to use Core Data for main app and unit tests?

When I try to use Core Data with NSInMemoryStoreType for unit testing I always get this error:
Failed to find a unique match for an NSEntityDescription to a managed object subclass
This is my object to create the core data stack:
public enum StoreType {
case sqLite
case binary
case inMemory
.................
}
public final class CoreDataStack {
var storeType: StoreType!
public init(storeType: StoreType) {
self.storeType = storeType
}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Transaction")
container.loadPersistentStores(completionHandler: { (description, error) in
if let error = error {
fatalError("Unresolved error \(error), \(error.localizedDescription)")
} else {
description.type = self.storeType.type
}
})
return container
}()
public var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
public func reset() {
for store in persistentContainer.persistentStoreCoordinator.persistentStores {
guard let url = store.url else { return }
try! persistentContainer.persistentStoreCoordinator.remove(store)
try! FileManager.default.removeItem(at: url)
}
}
}
And this is how I am using it inside my unit test project:
class MyTests: XCTestCase {
var context: NSManagedObjectContext!
var stack: CoreDataStack!
override func setUp() {
stack = CoreDataStack(storeType: .inMemory)
context = stack.context
}
override func tearDown() {
stack.reset()
context = nil
}
}
From what I read here which seems to be the same issue that I have, I have to cleanup everything after every test, which I (think) I am doing.
Am I not cleaning up correctly ? Is there another way to do this ?
Is the CoreDataStack class initialised in your application? For instance, in an AppDelegate class? When the unit test is run it will initialise the AppDelegate some time before the test is ran. I believe this is so that your tests can call into anything from the app in order to test it, as per the line #testable import MyApp. If you're initialising a Core Data stack via your AppDelegate and in MyTests then you will be loading the Core Data stack twice.
Just to note, having two or more NSPersistentContainer instances means two or more NSManagedObjectModel instances will be loaded into memory, which is what causes the issue. Both models are providing additional NSManagedObject subclasses at runtime. When you then try to use one of these subclasses the runtime doesn't know which to use (even though they're identical, it just sees that they have the same name). I think it'd be better if NSManagedObjectModel could handle this case, but it's currently up to the developer to ensure there's never more than one instance loaded.
I know this question is old, however, I've encountered this problem recently and didn't find an answer elsewhere.
Building on #JamesBedford 's answer, a way to setup your Core Data stack is:
Ensure you only have a single instance of CoreDataStack in your app across both the app and test targets. Don't create new instances in your test target. In your app target, you could use a singleton as James suggests. Or, if you are keeping a strong reference to your Core Data stack in the AppDelegate and initialising at launch, provide a convenience static property in your app target to access from your test target. Something like:
extension CoreDataStack
static var shared: CoreDataStack {
(UIApplication.shared.delegate as! AppDelegate).stack
}
}
Add an environment variable to your test scheme in Xcode. Go to Xcode > Edit Scheme > Test > Arguments > Environment Variables. Add a new name-value pair such as: name = "persistent_store_type", value = "in_memory". Then, at runtime, inside your CoreDataStack initialiser, you can check for this environment variable using ProcessInfo.
final class CoreDataStack {
let storeType: StoreType
init() {
if ProcessInfo.processInfo.environment["persistent_store_type"] == "in_memory" {
self.storeType = .inMemory
} else {
self.storeType = .sqlLite
}
}
}
From here your test target will now use the .inMemory persistent store type and won't create the SQLLite store. You can even add a unit test asserting so :)

How to deal with thread and Realm ? (iOS)

I use Realm to store my model objects. In my object I have a function which generate NSData from its own properties values. This generation can be long, So I would like to generate my NSData in a thread with handler block.
My problem is that Realm data access is only possible on the Realm creation entity (actually the main thread). So when I access to my RealmObject properties in a thread, application crash. According to Realm specs, it's normal. But what is the best solution to make my NSData generation in a thread depending to Realm limitation ?
Actually I have two ideas :
make a Realm specific dispatch queue and make all my Realm access write in this queue
get all properties in need in a temp struct(or a set of variables) and work with this struct/variables to generate my NSData in a thread.
I assume that a lot of Realm users need to deal with threads and Realm, so what did you do in this kind of case ?
Pass the object id to the code that is running in the separate thread. Within that thread create a Realm instance (let realm = try! Realm()) and retrieve your object. Then you can do your long generation and return the result with a callback.
let objectId = "something"
dispatch_async(queue) {
let realm = try! Realm()
let myObject = realm.objectForPrimaryKey(MyObject.self, key: objectId)
let result = myObject.longOperation()
// call back with results
}
or
let objectRef = ThreadSafeReference(to: myObject)
DispatchQueue(label: "background").async {
autoreleasepool {
let realm = try! Realm()
guard let myObject = realm.resolve(objectRef) else {
return // object was deleted
}
let result = myObject.longOperation()
// call back with results
}
}

How to access NSManagedObject entity using objectID which is not saved yet in iOS?

I have created one entity using private queue MOC and called performBlock() to save it asynchronously.
Let’s say I want this newly created entity in other context / MOC, so I have only one way to access it is use object id.
But as it’s asynchronous call to save entity, it is possibility of situation where entity is not saved yet and I want to access it immediately after performBlock(), but on other context.
What is the way to access it in this situation?
Thanks in advance!
Update
let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = mainThreadManagedObjectContext()
let newUser = Contact(nameStr: "name", moc: privateMOC)
This is my generic code to create new entities like:
init(nameStr: String, moc:NSManagedObjectContext) {
let mEntity = NSEntityDescription.entityForName("Contact", inManagedObjectContext: moc)
super.init(entity: mEntity!, insertIntoManagedObjectContext: moc)
name = nameStr
}
After creating, I am saving it immediately as:
performBlock { () -> Void in
do {
try privateMOC.save()
}catch {
fatalError("Error asynchronously saving private moc - \(errorMsg): \(error)")
}
}
//accessing
let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = mainThreadManagedObjectContext()
let pred = NSPredicate(format: "name == %#", "name")
if let results = DatabaseInterface.sharedInstance.fetchEntityFromCollection("Contact", withPredicate: pred, moc: privateMOC) {
if let user = results.last {
return user as! Contact
}
}
else {
DDLogWarn("No user found")
}
I am not getting any user, but I can see this new contact is saved in DB when look using sqlitebrowser
Forgive me for indentation of code :(
What you're asking is impossible.
When you create a managed object on one context, it only exists in memory until you save changes. No other managed object context can get to it, even if you use the object ID. The other context can't look up the data from the persistent store file, and it can't get it from the other context. It can't get the object at all. You need to wait until the save completes.
A couple of things you might try, depending on how your app is structured:
Use performBlockAndWait instead of performBlock, so that you'll know that the save has completed by the time the block finishes.
Keep using performBlock but put calls inside that block that lead to accessing the object on a different context-- via a nested performBlock call on that other context.

Whether performBlockAndWait calling twice on single thread cause deadlock?

I have found something like this in performBlockAndWait documentation:
This method may safely be called reentrantly.
My question is whether it means that it never cause deadlock when I e.g. will invoke it like that on single context?:
NSManageObjectContext *context = ...
[context performBlockAndWait:^{
// ... some stuff
[context performBlockAndWait:^{
}];
}];
You can try it yourself with a small code snippet ;)
But true, it won't deadlock.
I suspect, the internal implementation uses a queue specific token in order to identify the current queue on which the code executes (see dispatch_queue_set_specific and dispatch_queue_get_specific).
If it determines that the current executing code executes on its own private queue or on a children-queue, it simply bypasses submitting the block synchronously - which would cause a dead-lock, and instead executing it directly.
A possible implementation my look as below:
func executeSyncSafe(f: () -> ()) {
if isSynchronized() {
f()
} else {
dispatch_sync(syncQueue, f)
}
}
func isSynchronized() -> Bool {
let context = UnsafeMutablePointer<Void>(Unmanaged<dispatch_queue_t>.passUnretained(syncQueue).toOpaque())
return dispatch_get_specific(&queueIDKey) == context
}
And the queue might be created like this:
private var queueIDKey = 0 // global
init() {
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL,
QOS_CLASS_USER_INTERACTIVE, 0))
let context = UnsafeMutablePointer<Void>(Unmanaged<dispatch_queue_t>.passUnretained(syncQueue).toOpaque())
dispatch_queue_set_specific(syncQueue, &queueIDKey, context, nil)
}
dispatch_queue_set_specific associates a token (here context - which is simply the pointer value of the queue) with a certain key for that queue. And later, you can try to retrieve that token for any queue specifying the key and check whether the current queue is the same queue or a children-queue. If this is true, bypass dispatching to the queue and instead call the function f directly.

Resources