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

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?

Related

What is the point of key-value coding?

Since Objective-C 2.0, we've had #properties and autogenerated accessor methods. So today, what is the point of key-value coding? Under what circumstances is it preferable to write
[myObject setValue:foo forKey:#"bar"];
instead of writing
[myObject setBar:foo];
or even
myObject.bar = foo;
I keep seeing articles and documentation making use of KVC, but always in a way where it seems like simply using properties would be better. So why would I ever use KVC? Thanks for any and all insight.
It's almost never preferable to write out [myObject setValue:foo forKey:#"bar"] by hand, with a literal #"bar". We usually use KVC to access a property when we don't know which property we want to access until runtime.
One example is an outlet in a xib. When you connect a text field's delegate outlet to the File's Owner in the xib, the xib records the connection as an object with three fields:
a reference to the text field (the object that has the outlet)
a reference to the file's owner placeholder (the destination of the connection)
the name of the outlet as a string, #"delegate"
At runtime, the xib loader (part of the UIKit framework) deserializes the text field. Then it deserializes the connection object and uses it to establish the connection that you wired up in the xib. The xib loader has to set a property of the text field (the delegate property), but it doesn't know which property until it loads the xib at runtime, long after both your app and the UIKit framework were compiled.
Another example of not knowing which property to access until runtime is the (little-known) ability of Core Animation to animate a custom property of your CALayer subclass. Say you create a subclass of CALayer called PolygonLayer, with a property named sides. You can animate the sides property using a standard CABasicAnimation:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"sides"];
animation.fromValue = #3;
animation.toValue = #9;
animation.autoreverses = YES;
animation.duration = 1;
[myPolygonLayer addAnimation:animation forKey:#"sides"];
Presto, Core Animation will animate your layer's sides property from 3 to 9 and back. Yet the source code of Core Animation doesn't know anything about your sides property. (Check out this question for more details.)
There are times we use KVC even though we know the property at compile-time. One example is when we want to take advantage of extra work KVC will do for us. For example, if you have an NSArray full of Person objects, and you want to get an array of every person's first name, you could write this:
NSMutableArray *firstNames = [NSMutableArray array];
for (Person *person in people) {
[firstNames addObject:person.firstName];
}
But this is a case where KVC has a feature that makes it simpler. If you access a property of an array using KVC, KVC will actually access that property of every element in the array for you:
NSArray *firstNames = [people valueForKey:#"firstName"];
Another example where we might use KVC even though we know the property at compile-time is when the property is not statically declared as part of the class. For example, each NSManagedObject (part of Core Data) dynamically gives itself properties based on whatever entity that instance of NSManagedObject is representing. You can access those properties using KVC, although generally we prefer to declare them in a subclass of NSManagedObject or in a category of NSManagedObject.
KVC can be useful if you are using Key Value Observers to detect value changes on an object. If you wanted to use KVO and #properties you would have to wrap every mutator method with:
[self willChangeValueForKey:#"bar"];
bar = foo;
[self didChangeValueForKey:#"bar"];
Sometimes you don't know what property you want to set/get until run-time.
In this case you can use KVC by constructing the property key path as a string.
For example i have an object with multiple NSArray properties and i want to keep the last NSDate they were updated.
Let's say i have an array property called: comments and an array property called likes.
I define a properties: commentsLastModified and likesLastModified.
when an array is updated (i have the property name as string), i use:
[object setValue:[NSDate date] forKey:[NSString stringWithFormat:#"%#%#", arrayString, #"LastModified"];

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.

Most straightforward method of getting an NSArray to an NSManagedObject

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.

Using KVO to observe changes to a property on an object inside a collection in Objective-C

I have a Core Data object (AGroup) with a to-many relationship to a collection of other Core Data objects (items, which are instances of ANItem). I'm using the AGroup object in a UITableView (this is on iOS) and the order in which the items are displayed is determined by a property on the items (sortProperty). So, code wise (ignoring that they are Core Data objects, so this is semi-pseudocode) it looks like this:
#interface AGroup : NSManagedObject
{
NSSet *items; // these are instances of ANItem
}
#property (retain) NSSet *items;
#end
#interface ANItem : NSManagedObject
{
NSNumber *sortProperty;
}
#property (retain) NSNumber *sortProperty;
#end
Ok, so, the point here is that the UITableView has an instance of an AGroup object and is displaying the list of items, and the order in which the items are displayed is determined by the sortProperty property.
So, I want to know when the sortProperty changes on an instance of ANItem so that I can update the table view. I'm wondering, is there a way I can use KVO to observe the sortProperty from the AGroup object? Something like:
[group addObserver:self forKeyPath:#"items.sortProperty" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:nil];
or even:
[group addObserver:self forKeyPath:#"items.#sum.sortProperty" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:nil];
would work, since the sum would change when the value changes.
I've tried this, though, and I get an invalid argument error.
Does anyone know if this is possible? If so, how?
I realize I could do a fetch request and sort the request by sortProperty, but due to some other application architecture limitations, that's not really possible in this case. So essentially, this kinda comes down to imagining that I'm just dealing with a plain old NSSet in an object kind of relationship. I only mention the core data aspect in case that impacts the answer due to additional limitations/etc?
Im pretty sure there's no way to directly observe the contents of a collection like that. I think the best bet is to define the KVC/KVO compliant set accessors/mutators, observe self.items, and use the change notifications to directly observe sortProperty on each of the objects as they are added to the collection.
Since this is Core Data, you may also/instead need to use the context save notifications to detect changes in items. I don't have enough experience with Core Data to say for sure though.

#property(copy) - is an entire copy being made?

I'm getting sublayers of a CALayer with this property accessor:
// #property(copy) NSArray *sublayers
NSArray* layer = mylayer.layer.sublayers;
Since this property uses "copy", every time I simply execute:
mylayer.layer.sublayers
is an entire copy of the sublayers array being made for me? If so that might be bad, because I could have one hundred+ layers, and definitely don't want to create a huge copy of them. I'm just looking to get a count and iterate through the existing layers, just a pointer to the existing layers,
Thank you
When you use the 'copy' declaration and you #synthesize the property, then copy is used when the property is set.
In your example, you are only accessing the value which just gives you a pointer to the sublayers array.
Here's a link to the ADC documentation on this point.
Update
IIRC, the runtime is smart enough to know if the object being set is mutable. If an immutable object is being passed in to the property it is retained and not copied.
In some cases, if you are passing in a mutable object that you want to be able to modify, then you should write your own setter that calls mutableCopy on the object. This is shown in the documentation link that I provided.
I'm not sure I understand your answer Abizern so let me say this:
If you use (copy) on a property you will be making a whole new copy of that object so would be wasting a lot of memory. I'm not sure why they are doing that, they could just use (readonly) instead if they want to protect the values from change. Plus remember most value classes are immutable in Obj-C so they can't change the value anyway.