Most straightforward method of getting an NSArray to an NSManagedObject - objective-c

This is a very basic question but I'm struggling to find the answer. I have an NSManagedObject called iSelectedObject.
Supposing I have a condition later on that pulls either representedObject from an NSOutlineView or selectedObjects from an NSArrayController, depending on (irrelevant to this question) criteria, I then need to run a method that requires an NSManagedObject to be fed to it. This works fine when I feed it the representedObject from the NSOutlineView - it's naturally an NSManagedObject when picked out this way. However, if I grab the selectedObjects of my NSArrayController, it's an NSArray and I can't feed it to my method.
I'm aware that an NSArray can contain NSManagedObjects and feel the solution maybe as simple as going one level deeper into my NSArray or something similar but how would I feed my NSArray to my method that requires and NSManagedObject. Should I extract/convert it in some way?
(As a help: The reason that I was even trying to feed the method the NSArray is because it and the, alternative, NSManagedObject both have the same data held inside. ie, if I make two functions exactly the same with one expecting an NSArray and one expecting an NSManagedObject, they work perfectly. The only reason I have separate choices of picking out an NSArray or NSManagedObject is because that seems to be the default that selectedObjects and representedObject spit out, respectively. If I could pull out one or the other for both, that would be the perfect solution but I'm unsure how to.)

If the NSArray only contains one object, you could just pull the NSManagedObject out of the NSArray with
[array lastObject];
If there are multiple objects, run through the array and test each object with
[object isKindOfClass:[NSManagedObject class]];
Either way, that will give you an NSManagedObject that you can use for your method.

I was able to change the method to expect a type id rather than an NSManagedObject and this stopped it complaining.

Related

NSArrayController + NSTableView : automatically save changes without Core Data

OK, so I'm implementing a classic scenario :
A NSPopupButton with some items in it
When the selected value changes, my itemsArray is updated
The itemsArray is linked to an NSArrayController
Each item in the itemsArray is an NSMutableDictionary (with keys : title,content)
An NSTableView displays the titles of the arrangedObjects (binding)
An NSTextView displays the content of the selected item.
Now, what I want is to automatically save any changes to the itemsArray (or itemsArray's item title/content), but without using Core Data (which I suspect might have been the best way to go about it).
I imagine it's quite a basic question this one, but honestly I've never really like Cocoa's auto-magical way of doing things... so, I need your help...
How should I go about that?
You can write an array to a file very easily:
[yourArray writeToURL:someFileURL atomically:YES];
This will work if all the contents of the array are property list objects (i.e. they are NSNumber, NSString, NSDictionary, NSArray or NSData objects). This is the case in your example.
You can then recreate the array using either the arrayWithContentsOfURL: or initWithContentsOfURL: methods when you load from disk.
If your model object is more complex than just an array, then you should make your model object conform to the NSCoding protocol. This means you need to implement the initWithCoder: and encodeWithCoder: methods. You can then use the NSKeyedArchiver and NSKeyedUnarchiver classes to convert your object to and from an NSData representation that you can write to disk.
You should read the Archives and Serialization Programming Guide for more detailed information.
Another solution might be to add a Shared User Defaults Controller and bind the Controller Content Array from the Array Controller to the Shared User Defaults Controller

How to add alive object to NSMutableArray and remove them when they're released?

I have class Item and class List (which has an NSMutableArray).
Every time class Item is instantiated (and destroyed) it posts a notification, which is listened-to by class List. When class List receives the notification is adds the instance of class Item to its list.
I'm trying to have class Item also post a notification that its about to be dealloc'd. The problem is that class List's NSMutableArray retains the instance of class Item.
What's the most appropriate means of handling this situation? If I decrement the count when adding it to List's array, then an exception will be thrown when class List attempts to call removeObject (since it'll try to dealloc the object.)
Basically, I want a "monitor" class List that contains a list of all "live" instances of Item. But, I also need the ability to release/dealloc the instances and have them report they're being dealloc'd so List can remove them from its NSMutableArray.
Thanks for your help.
If I understand correctly, you want an array that maintains weak references to its items, as opposed to strong references?
I don't know of a way to do this with anything "built-in" in Cocoa. The only way I'd know of to do this is to make the array yourself, and have the storage be __weak id[]. That would automatically zero-out the place in the array when the object deallocates. If you're under the retain-release model, you could use something like MAZeroingWeakRef to get the same behavior.
This is definitely an interesting question, and I don't know of an easier answer. I'd love to be proven wrong!
Ha, I love being wrong!
There's a class called NSPointerArray that looks like it can do what you're looking for. However, it's only available on the Mac, and it only auto-zeros when you're using garbage collection.
I'll keep thinking about this. This is an interesting problem! :)
So I kept thinking about this, and came up with a solution. It uses two unconventional things:
A subclass of NSMutableArray (egads!)
Using an associated object to determine object deallocation
For the first bit, I had to to subclass NSMutableArray so that I could inject some custom logic into addObject: (and related methods). I didn't want to do this via swizzling, since NSArray and friends are a class cluster, and swizzling into/out of clusters is fraught with peril. So, a subclass. This is fine, but we're going to lose some of the awesome features we get from "pure" NSArray instances, like how they do weird things when they get big. Oh well, such is life.
As for the second bit, I needed a way for any arbitrary object to notify that it is about to or just finished deallocating. I thought of dynamically subclassing the object's class, injecting my own dealloc/finalize method, calling super, and then smashing the isa of the object, but that just seemed a little too crazy.
So, I decided to take advantage of a fun little thing called associated objects. These are to ivars what categories are to classes: they allow you to dynamically add and remove pseudo-instance variables at runtime. They also have the awesome side effect of getting automatically cleaned up with the object deallocates. So what I did is just created a little throw away object that posts a notification when it is deallocated, and then attached it to the regular object. That way when the regular object is deallocated, the throw away object will be as well, resulting in a notification being posted, which I then listen for in the NSMutableArray subclass. The notification contains a (stale) pointer to the object that is in the process of getting destroyed, but since I only care about the pointer and not the object, that's OK.
The upshot of all of this is that you can do:
DDAutozeroingArray *array = [DDAutozeroingArray array];
NSObject *o = [[NSObject alloc] init];
[array addObject:o];
NSLog(#"%ld", [array count]); //logs "1"
[o release];
NSLog(#"%ld", [array count]); //logs "0"
The source is on github, and it should (theoretically) work just as well on iOS as Mac OS X (regardless of GC mode): https://github.com/davedelong/Demos
Cheers!
... and I just thought of a way to do this without a custom subclass, but I'm tired and will post the updated answer tomorrow.
the next morning...
I've just updated the project on Github with an NSMutableArray category that allows you to create a true NSMutableArray that auto-zeroes its objects as they're deallocated. The trick was to create a CFMutableArrayRef with a custom retain callback that sets up the proper observation, and then just cast that CFMutableArrayRef to an NSMutableArray and use that (ah, the magic of Toll-Free Bridging).
This means you can now do:
NSMutableArray *array = [NSMutableArray autozeroingArray];
I added a typedef to define these as NSAutozeroingMutableArray, just to make it explicitly clear that while this is an NSMutableArray, it doesn't retain its objects like a normal NSMutableArray. However, since it's just a typedef and not a subclass, you can use them interchangeably.
I haven’t tested this, so comments are welcome.
You could use an NSPointerArray for the list (in a retain property):
self.array = [NSPointerArray pointerArrayWithWeakObjects];
When an Item object is created, it would post a notification that’s listened by your List class. Upon receiving the notification, List adds the object to the pointer array:
[array addPointer:pointerToTheObject];
In this setting, the pointer array doesn’t keep a strong reference to its elements — in particular, it doesn’t retain them. This applies to both garbage-collected and non-garbage-collected builds.
In a garbage-collected build, if an element is garbage collected then the garbage collector automatically assigns NULL to the position in the array where the object was stored.
In a non-garbage-collected build, you’ll need to manually remove the element or assign NULL to the position in the array where it was stored. You can do this by overriding -[Item dealloc] and posting a notification that the object is being deallocated. Your List class, upon receiving the notification, would act upon it.
Note that, since objects are not owned by the pointer array, you must keep a strong reference to (or retain) them if you want to keep them alive.

How to refresh an NSTableVIew when you populate the NSMutableArray (bind) associated with it

I tried to bind my NSArraycontroller to an NSMutableArray; the array holds objects of type "iData" (it's a custom class). The class further contains some NSMutableString variables which are set as keys for KVC. Then I bound the my NSTableColumn to the NSArrayController and set key model paths of every column respectively.
When I try to populate the array, the GUI does not show anything. What did I forget?
So it's likely that you solved this long ago, but in case someone else stumbles across this question...
I am populating the array via NSMutableArray, but I am not sure how can I populate the array via NSArrayController, since I don't have the instance in my Controller.m class. Please tell me how can I resolve this issue.
It is possible that you were doing something like
[myData addObject:someObject];
However, your NSArrayController will not learn of this change to the NSMutableArray instance because addObject is not KVC compliant. You need to inform any object that is observing that your NSMutableArray instance has changed. There are at least two ways to do this. Assuming that your NSMutableArray instance property is named "myData", then you can do something like the following:
[self.willChangeValueForKey:#"myData"];
[myData addObject:someObject];
[self.didChangeValueForKey:#"myData"];
or
NSMutableArray *bindingsCompliantArray = [self mutableArrayValueForKey:#"myData"];
[bindingsCompliantArray addObject:someObject];
Another SO answer (linked) has a good explanation on what mutableArrayValueForKey actually does, but I recommend reading the Apple developer docs on key-value coding and key-value observation to help understand it.
I have dragged the NSController instance in my mainmenu.nib tray. Do i need to declare an IBOutLet NSArrayController in my Controller.h file and then connect it with the NSArrayController instance in the tray ?
You need a NSArrayController instance in your nib file, but you do not need an IBOutlet in your interface for the situation that you've described here. The NSArrayController should be bound to the key of your NSMutableArray (myData in my example) and it sounds like you already have your table columns bound correctly.
Although Stephen's answer is probably "the way to go", I think the OP's original question "How do I insert/delete/manage my NSMutableArray using the NSArrayController, deserves a simpler and more direct answer:
NSArrayController provides a full and rich set of methods and even IBActions to fulfill almost anything you want on the managed NSMutableArray, with all the niceties of handling things "through filters", through selection, and "keeping sorting rules"
e.g. insert a new item so that it is inserted according to the current sort-descriptions.
Here's an excerpt from these methods (open NSArrayController.h for the full set) and remember that the 'content' is your NSMutableArray, while 'arrangedObjects' is an array provided by the NSArrayController that applies filtering and sorting to the content, "on its way" to the display in the NSTableView.
- (BOOL)addSelectedObjects:(NSArray *)objects;
- (BOOL)removeSelectedObjects:(NSArray *)objects;
- (IBAction)add:(nullable id)sender; // overridden to add a new object to the content objects and to the arranged objects
- (IBAction)remove:(nullable id)sender; // overridden to remove the selected objects
- (IBAction)insert:(nullable id)sender;
- (void)addObject:(id)object; // overridden to add to the content objects and to the arranged objects if all filters currently applied are matched
- (void)addObjects:(NSArray *)objects;
- (void)insertObject:(id)object atArrangedObjectIndex:(NSUInteger)index; // inserts into the content objects and the arranged objects (as specified by index in the arranged objects) - will raise an exception if the object does not match all filters currently applied
- (void)insertObjects:(NSArray *)objects atArrangedObjectIndexes:(NSIndexSet *)indexes;
- (void)removeObjectAtArrangedObjectIndex:(NSUInteger)index; // removes from the content objects and the arranged objects (as specified by index in the arranged objects)
- (void)removeObjectsAtArrangedObjectIndexes:(NSIndexSet *)indexes;
- (void)removeObject:(id)object; // removes from the content objects and the arranged objects (if currently contained)
- (void)removeObjects:(NSArray *)objects;
All this, of course, is for direct programmatic control over the content, and not "automagically" via Cocoa-Bindings.

subclassed NSManagedObject does not call description when NSLog'd

I have a data model which has two entities in a relationship of one to many.
Each entity has a class that is subclassed from NSManagedObject.
I get the relation set for the one entity and walk it casting each set member to the specific subclass as I enumerate the collection.
When I do
NSLog(#"My Entity: %#", myEntityInstance);
It logs but does not call my subclass's method for:
- (NSString*) description
It does call if I send:
NSLog(#"My Entity: %#", [myEntityInstance description]);
Any ideas what is being called and why description has to be manually called?
Thanks!
If a class instance responds to descriptionWithLocale:, then NSLog will use that instead. Although descriptionWithLocale: does not appear in NSManagedObject's list of instance methods, it could possibly still be implemented.
Try overriding descriptionWithLocale: and see if that makes a difference.
- (NSString *) descriptionWithLocale:(id) locale
{
return #"my description";
}
I've never seen that. I don't think it's a NSManagedObject behavior. You might log the class before making the call to make sure your instance is of the class you think it is.
Might be two years or so late to the game, but for the benefit of others, I had this problem tonight. The cause was that, while I had made NSManagedObject subclasses for my entities, one of the entities in the CoreData Modler had it's "Class" set back to NSManagedObject instead of the custom subclass.
It didn't matter what I put into -description in my subclass files, because the objects were coming out of the Context as NSManagedObjects instead of my custom subclass.
Putting the subclass name back in the Entity Inspector in the Xcode Coredata Model Editor fixed it.

Am I updating my NSArray that's bound to an NSTableView right?

I have a pretty standard setup where I have an array in my controller that I'm binding to an NSTableView. When I add an object to this array with the UI, I add elements by copying the array to a mutable array, adding the contents, then copying it back. I saw this idiom somewhere, but I'm not really sure where, or whether this is actually a sane thing to do. I t works fine on Snow Leopard, but it crashes really badly on Leopard. GDB tells me it happens right at the marked line, where I copy the new array back.
#interface MyAppDelegate : NSObject {
NSArray * urls;
//other declarations
}
#property (nonatomic, retain) NSArray *urls;
#implementation MyAppDelegate
#synthesize urls;
- (void)addThing:(id)sender {
NSMutableArray *newUrls = [NSMutableArray arrayWithArray: [self urls]];
[newUrls addObject: newurlimadding];
[self setUrls: [NSArray arrayWithArray: newUrl]];
}
I'm pretty sure I must be doing something boneheaded here. Is there a better way to do this? Why am I crashing?
NSMutableArray *newUrls = [NSMutableArray arrayWithArray: [self urls]];
[newUrls addObject: newurlimadding];
[self setUrls: [NSArray arrayWithArray: newUrl]];
What did you create newUrls for if not to set it as the new value of urls?
Besides that, there are a couple of things you're doing wrong:
No model objects. Bindings hates this. Unless your table view exists solely to display the different parts of the URL (scheme, host, path, etc.), each in one column, you're not giving Bindings what it wants.
Pay attention to the fields in the Bindings Inspector. Note that there are two: Controller Key and Model Key Path. They're exactly what they say on their tins: The Controller Key is the key for a property of the controller, which should provide model objects. The Model Key Path is the key path for one or more properties in the model, which usually should provide value objects, such as strings and numbers.
You probably need to create a model class and make the URL a property of that class. I'd guess that you have other objects, perhaps in parallel arrays, that you can move to properties of the model class. Do that, so that you're working with Bindings instead of against it.
Not using array accessors. You're just setting the whole array at once, which is inefficient and may cause display problems (such as selection highlights disappearing). Implement and use array accessors for this array property. Accessorizer can help you here.
I actually have no idea whether this will fix your crash because you haven't told us anything about it. You should edit your question to include any exception messages or other crash-describing output, which you'll find in the Run Log/Debugger Console.
Also, since the type of urls can be mutable, you should set it to copy, not retain.
NSArray * things; - since this can be modified you better represent using NSMutableArray instead NSArray.
When ever you need to add some new element to the list just use 'addObject' and insert element to the list.
Your binding will make sure that UI is updated using KVO and KVC.
It looks like the problem was that I had NSURLs as my object type. Once I changed the object in my array to a custom-made KVC compliant object, I wasn't getting any more crashes.
Maybe NSURL isn't KVC-compliant on 10.5, but it is on 10.6?