thread safety for NSManagedObject instances? - cocoa

I have one global instance for one of NSManagedObject type. For the global instance, it has few member variables who are #dynamic properties declared in the following way
#property (retain) NSString *value;
And I have a few threads that would do the following simple operations
myInstance.value = [NSString stringWithString:newValue];
So the question is - do I need to synchronize the operation above? or is it naturally thread-safe already (as they are taking care by NSManagedObject)?

According to the documentation, this is not thread-safe. There's a whole article about threading and Core Data which you can read here. Essentially, it says that to properly use threading with Core Data, you need a separate managed object context for each thread. In the "If You Don’t Use Thread Containment" section, it specifically notes that both reading and mutating managed objects across threads can have unwanted effects.

Related

Why use initWith.. methods as opposed to "class/factory methods" in Objective-C?

I've been coding in Objective-C for a few months now and I've noticed that sometimes a class is instantiated (as recommended by documentation) with an init method. Therefore, one must alloc first, and then init. [[Example Class Alloc] initWithProperty1:andTwo:]. However, sometimes the doc recommends using "factory methods" as constructors. Such as [NSArray arrayWithObjects:__].
It seems that with a factory/class method you get the allocation done behind the scenes and the actual method is indistinguishable from the init, AFAIK.
Therefore, what is the practical reason to prefer one over the other? Is my analysis of the two being nearly identical even correct?
I recommend using factory method if there is one that does what you need. Aside from the syntactic sugar (shorter), there are also differences in the object's ownership (and hence who should free it). You shouldn't worry so much about memory if you use ARC.
From Apple's documentation:
Factory methods can be more than a simple convenience. They can not
only combine allocation and initialization, but the allocation can
inform the initialization. As an example, let’s say you must
initialize a collection object from a property-list file that encodes
any number of elements for the collection (NSString objects, NSData
objects, NSNumber objects, and so on). Before the factory method can
know how much memory to allocate for the collection, it must read the
file and parse the property list to determine how many elements there
are and what object type these elements are.
That is a little mystic but consider a use case like this: you want to populate an NSMutableArray with the content of a file. If you choose "alloc and init", the OS must constantly allocate new memory to store the additional data as you read them from the file. The class method needs to parse the file first, so it know how many lines there are and how big of a memory it should ask for in one go.

implementing custom accessor methods

I am reading "Core Data Programming Guide". It contains this text:
You must, however, change attribute values in a KVC-compliant fashion.
For example, the following typically represents a programming error:
NSMutableString *mutableString = [NSMutableString stringWithString:#"Stig"];
[newEmployee setFirstName:mutableString];
[mutableString setString:#"Laura"];
For mutable values, you should either transfer ownership of the value
to Core Data, or implement custom accessor methods to always perform a
copy. The previous example may not represent an error if the class
representing the Employee entity declared the firstName property
(copy) (or implemented a custom setFirstName: method that copied the
new value). In this case, after the invocation of setString: (in the
third code line) the value of firstName would then still be “Stig” and
not “Laura”.
Question regarding text: "In this case" is which case--the one where property is declared as "copy" or when its not?
Question regarding copy and programming practice:
From what I have read here:
NSString property: copy or retain?
I understand
that using copy will ensure that firstName is "Stig", not Laura
it is wise to do so because "in almost all cases you want to prevent mutating an object's attributes behind its back"
I would really like to know what is the above quoted text trying to tell us in the context of Core Data. We have to use "copy" anyway whether using Core Data or not. Also, I would be glad if someone could throw more light on point "2" (it is wise to...) above as in what will be the consequences of mutating an object's attributes behind its back?
your "Question regarding text: "In this case" is which case--the one where property is declared as "copy" or when its not?"
mis-matched the point that Apple document wants to explain, I believe.
As Apple document points out, if custom-accessor-method is implemented normally, the default implementation does NOT copy attribute values. If the attribute value may be mutable and implements the NSCopying protocol (as is the case with NSString, for example), you can copy the value in a custom accessor to help preserve encapsulation (for example, in the case where an instance of NSMutableString is passed as a value).
Here is a copying setter snippet
#interface Department : NSManagedObject
{
}
#property(nonatomic, copy) NSString *name;
#end
#implementation Department
#dynamic name;
- (void)setName:(NSString *)newName
{
[self willChangeValueForKey:#"name"];
// NSString implements NSCopying, so copy the attribute value
NSString *newNameCopy = [newName copy];
[self setPrimitiveName:newNameCopy];
[self didChangeValueForKey:#"name"];
} #end
The issue is when to use (and how) immutable values.
Since core data use KVO heavily when detecting changes done to objects, if you use a mutable property that is changed directly through it object and not through the property, CoreData will not detect the change to the object and your changes might not persist to the store.
If you use mutable NSManagedObject attributes, override the setter/getter method and use only them to mutate the underlying object (this mean that you are responsible to let CoreData know that a change did happen to the object, and it must be persisted to the store.
Also, if you use transformable properties for complex objects, you must trigger the change notifications yourself in order for CoreData to realise that a change has occurred, and the object should be re-transformed and saved when the context saves.
I would highly recommend that when it comes to simple objects like strings, you use immutable property values which will force you to go through the object properties and trigger the default KVO notification (copy attributes will also force the KVO notifications).

Memory Leak with NSMutableArray

I am trying to set up a multi-dimensional NSMutableArray. I am initially setting all position to a [NSNumber numberWithInt:0] then replacing the object with another [NSNumber numberWithInt:4] (for example). When I am done I would like to rebuild the array. I am correct in saying [array release]? Will that release all the NSNumber objects? Or do I need to do more advance memory management, like set all objects to nil first?
You can either release the array and recreate it or—slightly more efficiently—just call the array’s -removeAllObjects. The NSNumber objects you’re populating it with are autoreleased, so the array, by taking ownership of them when you add them to it, also assumes responsibility for releasing them when it itself gets released or has its contents removed.
Your array will properly retain and release your NSNumbers as you add/replace and remove objects, as well as when you release the array holding the items. So yes you are correct since you are using the NSNumbers convenience constructor which will return an autoreleased object.
Philosophically, you shouldn't know or care what the NSArray does with respect to retain and release. The extent of your contract with it is that addObject:/etc will put an object into the array and objectAtIndex:/etc will subsequently return the same objects. At most you need to consider whether you need to continue owning an object after putting it into an array, entirely according to your own requirements. NSArray is entirely responsible for its own memory management.
In the case of NSArray, how it manages retains and releases internally is well known and your literal question is already answered by Noah and Joe. But you should never, ever rely on another object having a specific implementation.

Debugging NSOperationQueue Blocking

I need some guidance in how to debug problems with concurrency in Cocoa under 10.6. I'm converting a 'for' loop to use NSOperations but most of the time, the code just freezes at some point through the loop. I can see NSLog output in the console to that effect. On the rare occasion, the code will run all the way through and it's fine.
The code is model-layer only, initiated from a method in the controller. The method loops through just 8-10 model objects, instructing them to each write their output to a uniquely named file. 8 model objects = 8 separate files. There are no calls up to the GUI and the model objects are NSManagedObject subclasses, which contain a set of child NSManagedObject objects (0..n of them), which each owning object summarizes and writes out. The output format is JSON.
Code:
__block NSMutableArray *collectionOfCourses = [[NSMutableArray alloc] initWithCapacity:[[self courses] count]];
/* Create a filename. Use our title and set it to lowercase */
NSURL *ourFileURL = [aURL URLByAppendingPathComponent:[[self title] lowercaseString]];
ourFileURL = [ourFileURL URLByAppendingPathExtension:#"js"];
for (Course *aCourse in [self courses]) {
[[self opQueue] addOperationWithBlock:^{
NSArray *arrayForOneCourse = [aCourse arrayAndWriteToFileURL:aURL fileFormat:format];
[collectionOfCourses addObject:arrayForOneCourse];
}];
}
I do a lot of NSLogs, would that be the issue? Is NSLog'ing from background threads a bad thing?
Since I'm adding to the mutable array from withinside a block, is it correct that I declare the mutable array as __block? I've tried it both ways and seemingly no difference relating to this freezing problem.
How do I debug this problem using Xcode v4? I want to know the line of code that it's freezing on, or what two lines of code are executing at the same time and causing it to block the execution.. My former techniques of setting a single breakpoint and stepping through the code no longer work, because of the concurrency.
thanks
It's nothing to do with your block-scoped variable. Your problem is that neither NSMutableArray nor NSManagedObject are in any way, shape or form thread-safe. You cannot do what you're doing here. If you want to have this processed off the main thread, you need to use a dispatch queue or something similar to process each item serially (and even when you're back on the main thread, you should use that same queue before you read from your mutable array. It'd probably be easier and safer, however, to do something like copy the mutable array to an immutable version when you're finished and then dispatch a notification or call-back to the main thread with the new, immutable copy embedded.

NSManagedObject as NSDictionary key?

In my app, I have a NSDictionary whose keys should be instances of a subclass of NSManagedObject.
The problem, however, is that NSManagedObject does not implement the NSCopying protocol which means that no Core Data objects / instances of NSManagedObject can be used as dictionary keys even though the -[hash] method works fine for them.
Was should I do?
There are four options:
Use a different object as the dictionary key instead, and lookup from that. [object objectID] or +[NSValue valueWithNonretainedObject:] seem the most obvious
Use CFDictionaryCreateMutable() to create a dictionary with retained keys, rather than copied, instead, and then call CFDictionarySetValue() to store the objects
On OS X or iOS6+, [NSMapTable mapTableWithStrongToStrongObjects] gives you a purely Objective-C equivalent to CFMutableDictionary
Implement NSCopying for your managed object subclass, such that it returns self (with a bumped reference count if you're not using ARC)
Notes
+valueWithNonretainedObject: is pretty dangerous, since it's possible to be left with a dangling pointer; likely best to avoid.
Storing object IDs is fine, apart from the fact that new objects start out life with a temporary ID. That ID then changes to a permanent one when the context is saved to disk (or -obtainPermanentIDsForObjects:… is called). Your mapping code needs to be smart enough to handle this unless it can guarantee that all incoming objects already have a permanent ID.
Implementing NSCopying like this feels a bit icky, but should work just fine. As it happens, this is exactly the approach NSURLSessionTask takes, I presume for dictionary friendliness.
Prior to OS X 10.8 Mountain Lion, it used to be possible to create a regular NSMutableDictionary and then call CFDictionarySetValue() for it. That's no longer the case though; new dictionaries now have proper copy callbacks specified down at the CF level, rather than purely being a feature of NSMutableDictionary.
I suggest to use [[[myManagedObject objectID] URIRepresentation] absoluteString] as your key.
Could you create a wrapper class, that contains a reference to the instance of NSManagedObject that you want to use as a dictionary key? You could then make this wrapper class implement NSCopying, along with a hash method (perhaps just calling the NSManagedObject's hash method), and use this wrapper as the dictionary key.
I had a similar problem, in which I needed to bundle several entities with additional data for each, and initially tried:
#{entity1:data1, #entity2:data2, #entity3:data3}
this didn't work for the reason above (NSCopying), so I did:
#[
#{#"entity":entity1, #"data":data1},
#{#"entity":entity2, #"data":data2},
#{#"entity":entity3, #"data":data3}
]
But this solution makes sense only if you don't need dictionary style access to these entities or are happy to iterate to find what you need. In my case this was a packaging problem. Note that if you pass these entities around the NSManagedObjectContext need to be the same to use them.

Resources