Using NSView instances as NSDictionary keys? - objective-c

I'm trying to create a semi-complex set of view animations (think an animated version of an NSMatrix form, where rows slide around as other rows are added or removed), and to build the animation I'm making a little helper class.
There I have to keep track of the different views, their ordered indices, and few some other values associated with their animations.
To that end, I'm using an NSArray instance to keep track of the ordering (indices) of the views, and I'd like to use an NSDictionary with the views as keys to keep track of the values (the values themselves are in nested dictionaries). I.e. I'd like to be able to do something this, for instance (pseudo code):
NSMutableDictionary* viewValuesDict = [NSDictionary dictionary];
// Loop thru an ordered NSArray
for( (NSView*) view in viewsArray ) {
// Get some values we'll need later
NSDictionary* associatedValues = [view getSomeValues];
// ...and put them into viewValuesDict...
[viewValuesDict setObject:associatedValues forKey:view];
// and then things break because the NSView 'view'
// doesn't support copyWithZone.... darn
}
Problem is, I of course can't use NSView instances as dictionary keys, because the keys are added using copyWithZone, which NSView does not implement.
So, what's a good way to get a unique key for an NSView instance? I could conceivably use [obj description] since the memory address you get back is a perfect UID, but of course the system has to work with any kind of NSView subclass that might return something else entirely, so that's no good.
Or should I maybe try something else entirely? Is there maybe some alternative to NSDictionary, where keys just aren't copied? Because in this case I really have no need for the keys to ever be copied.

Sometimes there are occasions where you want to use a view (whether NS or UI) as the key in a dictionary. I've come across one such situation. I would've preferred to use objc_setAssociatedObject, but that requires Snow Leopard. Boxing with NSValue will work, but if you need to be doing lots and lots of look ups given a view, the continual boxing and unboxing of pointers may become tedious.
There are two options to creating an NSView => <object> dictionary.
Use NSMapTable
Use CFMutableDictionaryRef
NSMapTable is a class introduced in 10.5 that is very similar to an NSMutableDictionary, except that it has extra abilities that make it work more nicely with garbage collection. In your case, you'll probably want a map table with "weak" keys and "strong" values, but read the documentation for all the fun details.
CFMutableDictionaryRef is the Core Foundation equivalent of an NSDictionary (they are toll-free bridged), but it has some extra creation options. You create one using CFDictionaryCreateMutable(), and that wants two struct parameters. One is a structure that defines the memory management (and other) behavior of how to deal with the keys of the dictionary, and the other is a struct for defining the behavior of the values. You can create a CFMutableDictionaryRef with the options of retaining the keys (instead of copying them) and then retaining the values. Once you've done this, you can cast the CFMutableDictionaryRef to an NSMutableDictionary and use it as you'd expect, just that the keys will be retained instead of copied.

Use NSMapTable instead of NSDictionary (of course you'll have to be sure to manage your object lifetimes carefully if you're not using garbage collection). This article has a good summary of how to use it.

As andyvn22 said, reorganize! But if that isn’t practical:
If you’re targeting Snow Leopard, and the associations are likely to persist for the lifetime of the views, use objc_setAssociatedObject().
Otherwise, use [NSValue valueWithNonretainedObject:] in preference to -description. (Like it says, it doesn’t retain the object, but your array does.)

Create your dictionaries so that one of their values is the view; then rearrange the code so that you need not look up dictionary based on view, but rather begin either with the dictionaries, or the index (putting the dictionaries into an array), or a unique ID of your own creation (putting the dictionaries into a dictionary; the ID could be as simple as a consecutive number for each new view you begin to keep track of). Unless you're doing something very complicated and dynamic, it should be possible to avoid needing to look up information given only an NSView.

Related

How to receive notifications from an NSMutableArray subclass

I have subclassed NSMutableArray to allow for a datasource. This is called BaseObjectArray. The array actually only holds a list of rowids (as uint64_t), and when asking for objectAtIndex it asks the datasource delegate for the object with that rowid (to allow for lazy DB queries).
The internal list of rowids is a class in it's own right (a RowIDSet, or the OrderedRowIDSet subclass, which is just a subclass of NSObject), that maintains just the list of unique rowids.
What I need is to somehow listen for changes to the BaseObjectArray (which is actually listening to changes on it's RowIDSet object, perhaps through a similar method).
As objects may be added/removed from the BaseObjectArray not using the standard addObject:, but instead with addRowID:, the object that owns the BaseObjectArray will probably not get standard KVO notifications.
Possible solutions I have considered:
The BaseObjectArray has owner and ownerKey properties, and the BaseObjectArray triggers [owner willChangeForKey:ownerKey]; whenever anything changes.
Use will/didChangeNotificationBlocks - listeners can simply add a block to the BaseObjectArray (retaining these blocks in an NSMutableArray), and all the blocks in this array are triggered when something in the BaseObjectArray changes. I am uncertain about the possible retain-cycle nightmare that may ensue.
KVO on a 'contents' property of the BaseObjectArray. Anyone wanting to observe the BaseObjectArray actually observes the keyPath 'contents', and inside the BOArray it calls [self willChangeForKeyPath:#"contents"]. The contents property just returns self.
... something obvious that i have missed ...
Please let me know if any of these make the most (or any) sense, or if there is a better solution out there.
Thanks :)
Unless you know what you are doing, you should not subclass NSMutableArray. NSMutableArray is a class cluster and requires special treatment.
Why not just create a custom object that uses a plain NSMutableArray as its storage class? There seems to be no good reason to subclass NSMutableArray in your case, but maybe I'm misunderstanding your question.
I don't know if this will work, but if it does, it's probably the best way.
Make sure your NSMutableArray subclass is KVC compliant for the key self (if this doesn't work for self add a new property e.g rows which returns self or a copy of self). To make self (or whatever new property you use) KVC compliant you need to follow the Indexed To-Many Relationship Compliance rules for mutable ordered collections:
Implement a method named - that returns an array.
Or have an array instance variable named or _.
Or implement the method -countOf and one or both of -objectInAtIndex: or -AtIndexes:.
Optionally, you can also implement -get:range: to improve performance.
self ticks the box on the first of these. Also:
Implement one or both of the methods -insertObject:inAtIndex: or -insert:atIndexes:.
Implement one or both of the methods -removeObjectFromAtIndex: or -removeAtIndexes:.
ptionally, you can also implement -replaceObjectInAtIndex:withObject: or -replaceAtIndexes:with: to improve performance
So you'll need e.g. -insertObject:inSelfAtIndex: and -removeObjectFrom<Key>AtIndex:
Then you can use manual KVO notifications wherever you want to notify obeservers of the self property on that object. So you might use
NSIndexSet* indexes = // index set containing the index or indexes of objects to remove
[self willChange: NSKeyValueChangeRemoval valuesAtIndexes: indexes forKey:#"self"];
when removing objects.

What happens when an NSArray element gets deallocated?

Let's suppose I create a few objects and I add them to an array.
House *myCrib = [House house];
House *johnHome = [House house];
House *lisaHome = [House house];
House *whiteHouse = [House house];
NSArray *houses = [NSArray arrayWithObjects: myCrib, johnHome, lisaHome, whiteHouse, nil];
Normally, all House objects have a retain count of two, but they're being autoreleased once. After a while, I decide to release myCrib, even if I'm not the owner — I never retained or initialized.
[myCrib release];
The retain count should drop to zero and my object should be deallocated. My question now is: will this illegal action cause my app to work erroneously or even crash, or will NSArray simply delete my object from its list with bad consequences.
I'm looking for a way to maintain a list of objects, but I want the list to maintain itself. When some object disappears, I want the reference to it to disappear from my array gracefully and automatically. I'm thinking of subclassing or wrapping NSArray.
Thank you.
My question now is: will this illegal
action cause my app to work
erroneously or even crash, or will
NSArray simply delete my object from
its list with bad consequences.
Your array now has an invalid object pointer. There's no way to tell that the pointer is invalid just by looking at it, and the array isn't notified that the object has been deallocated. The problem isn't with the array, after all, the problem is with the code that improperly releases the object. So yes, the application will likely crash or otherwise behave incorrectly due to that bad pointer, and no, NSArray won't detect and deal with the problem for you.
I'm looking for a way to maintain a
list of objects, but I want the list
to maintain itself. When some object
disappears, I want the reference to it
to disappear from my array gracefully
and automatically.
If the objects in the list are all instances of a common class, you could define your own memory management methods that both retain/release the object and add/remove it from the list, or broadcast appropriate notifications in case there can be multiple lists. I suppose you could even override -retain and -release for this purpose, but I'd think long and hard about that before doing it, and document it well if you do; it's not the sort of thing that other developers would expect.
Another option might be Core Data. If you delete a managed object from the object graph, it'll disappear from any relationships. Strictly speaking, a to-many relationship is a set, not a list, but the difference may not be a concern for your purposes.
Update: I just noticed that you didn't tag your question ios. If you're working under MacOS X, you should definitely take a look at NSPointerArray. If you use garbage collection, NSPointerArray can be configured to use weak references and to replace references to collected objects with null references. This is exactly what you seem to be looking for.
You should not release myCrib if you are not the owner. To do so is a violation of the memory management guidelines and will make your code extremely difficult to maintain. I cannot stress enough that you absolutely should never do this under any sort of circumstance. You're asking for crashes; the array has declared ownership of the object, and you must not subvert that ownership in any way.
So the answer here is: your code is absolutely wrong and you should fix it. If you can't fix it, you should trash it and start over and keep rewriting it until you've come up with another way to achieve the same effect without subverting object ownership. I guarantee that it's possible.
If what you want is a weak-referencing array, then there are a couple ways you can do this (this was just asked a couple of days ago):
NSPointerArray - weakly references its pointers. When you use garbage collection, they're autozeroing (ie, the pointers get removed when the object is deallocated). Unfortunately, this is not available on iOS.
CFMutableArrayRef - you can specify a custom retain and release callback, or just not specify one at all. If you leave them out, the array will simply not retain the objects it contains. However, this does not automatically remove the pointer when the object is deallocated.
DDAutozeroingArray - an NSMutableArray subclass I wrote the other day to provide a weakly-referencing and auto-zeroing array that works on both Mac OS and iOS. However, I strongly encourage you to use this only as a last resort; There are probably much better ways of doing what you're looking for. https://github.com/davedelong/Demos
I'm looking for a way to maintain a
list of objects, but I want the list
to maintain itself. When some object
disappears, I want the reference to it
to disappear from my array gracefully
and automatically. I'm thinking of
subclassing or wrapping NSArray.
If I have understood right, what you want is an array of weak references. Then, you might be interested in reading this post.
You're asking for a crash here. Your NSArray will still have a reference to the object that now no longer exists -- and who knows what it will be pointing to after a while?
Subclassing NSArray might not be the answer either. It's a class cluster which, in short, means that it's harder to subclass than you might hope.
Not entirely sure how you'd implement this. Something like the element sending a notification when they're about to be deallocated which the array would then pick up. You'd need to be careful that you didn't leak or over-release your objects.
I created a wrapper class — in my code it's called a controller — which maintains the (mutable) array for me. I initialize the controller class in my view controllers — the place where I need them — instead of using an array directly.
No invalid code for me. :-p

How does one effectively handle temporary objects in Core Data since the objectID changes between temporary objects and permanent objects?

What is the best way to handle temporary objects in Core Data? I've seen solutions where temporary contexts are created, where they are inserted into nil contexts, etc.
However, here's the issue I'm seeing in both of these solutions. I'm using Core Data for my object model and and in some of my views store a NSSet of Core Data objects. The problem I have is when the object is stored, the objectID changes which effectively invalidates anything stored in any NSSet since the isEqual and hash are now different. While I could invalidate the object stored in the NSSet, it often is not practical and certainly not always easy.
Here's the things I've considered:
1) override isEqual method and hash on NSManagedObject (obviously bad)
2) do not place any NSManagedObject in a NSSet (use a NSDictionary where the key is always fixed)
3) use an entirely different type to store in NSSet where I could correctly implement the isEqual and hash code methods
Does anyone have a better solution for this?
ManagedObjects in an NSSet -- that sounds like a Core Data relationship. Why not simply store your temporary managedObjects in a relationship, and have Core Data take care of the problems you're now running into. Then you can concentrate on when and how to delete the temporary objects, or break the relationship or whatever is needed.
However, here's the issue I'm seeing in both of these solutions. I'm using Core Data for my object model and and in some of my views store a NSSet of Core Data objects. The problem I have is when the object is stored, the objectID changes which effectively invalidates anything stored in any NSSet since the isEqual and hash are now different.
tjg184,
Your problem here is not the transition to permanent IDs but that your container class depends upon an immutable hash. Hence, change your container class to an array or dictionary and this problem goes away. (You give up uniquing with an array but that is easy to handle with a trip through a transient set to perform the uniquing.)
Andrew
A possible solution would be to convert the temporary IDs to permanent ones using [NSManagedObjectContext obtainPermanentIDsForObjects:error:].
But be aware that this may be expensive, especially if you have a lot of objects you need to process this way.
You could possibly subclass NSManagedObject and override the willSave and didSave methods to remove and then re-add you objects to your set.
I actually ended up using a different approach, that of using a NIL context and providing a base class to handle insertion into a context. It works really well and is the cleanest solution I have found. Code can be found here... Temporary Core Data

Using non-copyable object as key for NSMutableDictionary?

I tried to figure out this code referencing: Cocoa: Dictionary with enum keys?
+ (NSValue*)valueWithReference:(id)target
{
return [NSValue valueWithBytes:&target objCType:#encode(id*)];
}
And,
[table setObject:anObject forKey:[NSValue valueWithReference:keyObject]];
But it feels something not good. Any recommendations?
You're absolutely right it's not good.
For one, you're encoding the wrong type (it should be #encode(id), not #encode(id*)), but in most cases this shouldn't cause a big problem.
The bigger problem is that this completely ignores memory management. The object won't be retained or copied. If some other code releases it, it could just disappear, and then your dictionary key will be a boxed pointer to garbage or even a completely different object. This is basically the world's most advanced dangling pointer.
You have two good options:
You could either add NSCopying to the class or create a copyable subclass.
This option will only work for objects that can meaningfully be copied. This is most classes, but not necessarily all (e.g. it might be bad to have multiple objects representing the same input stream)
Implementing copying can be a pain even for classes where it makes sense — not difficult, per se, but kind of annoying
You could instead create the dictionary with the CFDictionary API. Since Core Foundation types don't have a generic copy function, CFDictionary just retains its keys by default (though you can customize its behavior however you like). But CFDictionary is also toll-free bridged with NSDictionary, which means that you can just cast a CFDictionaryRef to an NSDictionary* (or NSMutableDictionary*) and then treat it like any other NSDictionary.
This means that the object you're using as a key must not change (at least not in a way that affects its hash value) while it's in the dictionary — ensuring this doesn't happen is why NSDictionary normally wants to copy its keys
For the later reference.
Now I know that there are some more options.
Override methods in NSCopying protocol, and return the self instead of copying itself. (you should retain it if you are not using ARC) Also you ensure the object to always return same value for -hash method.
Make a copyable simple container class holds strong reference to the original key object. The container is copyable but, it just passes original key when it being copied. Override equality/hash methods also to match semantics. Even just an instance of NSArray contains only the key object works well.
Method #1 looks pretty safe but actually I'm not sure that's safe. Because I don't know internal behavior of NSDictionary. So I usually use #2 way which is completely safe in Cocoa convention.
Update
Now we Have NSHashTable and NSMapTable also in iOS since version 6.0.
I'm not 100% sure about the correctness of this solution, but I'm posting it just in case.
If you do not want to use a CFDictionary, maybe you could use this simple category:
#implementation NSMutableDictionary(NonCopyableKeys)
- (void)setObject:(id)anObject forNonCopyableKey:(id)aKey {
[self setObject:anObject forKey:[NSValue valueWithPointer:aKey]];
}
- (id)objectForNonCopyableKey:(id)aKey {
return [self objectForKey:[NSValue valueWithPointer:aKey]];
}
- (void)removeObjectForNonCopyableKey:(id)aKey {
[self removeObjectForKey:[NSValue valueWithPointer:aKey]];
}
#end
This is a generalization of a similar method I saw online (can't find the original source) for using an NSMutableDictionary that can store objects with UITouch keys.
The same restriction as in Chuck's answer applies: the object you're using as a key must not change in a way that affects its hash value and must not be freed while it's in the dictionary .
Also make sure you don't mix -(void)setObject:(id)anObject forNonCopyableKey:(id)aKey and - (id)objectForKey:(id)aKey methods, as it won't work (the latter will return nil).
This seems to work fine, but there might be some unwanted side effects that I am not thinking of. If anybody finds out that this solution has any additional problems or caveats, please comment.

Passing around sets of data

A question that has pondered me for the last while. I am primarily a .net developer who dabbles in Objective-C for iPhone and Mac.
How do you go about sending "datasets" between methods in objective-c. For example in C# you can populate a custom class with data and pass it around in a List of type custom class. EG if you had a customer class you would just do something like:
List<Customer> customers = DataLayer.GetAllCustomers();
The only way I can see how this could be done in obj-c would be to populate an NSArray with custom objects? Is this an efficient way to do things? Any other recommendations? I am using sqlite as the database/data I want to return.
You're on the right track.
Cocoa's collection classes — which all have mutable an immutable variants — are:
NSArray: ordered, can contain an object multiple times
NSDictionary: unordered, mapping from keys to values, keys are copied
NSSet: unordered, can contain an object only once
NSCountedSet: unordered, can contain an object multiple times
The immutable variants help a lot with efficiency. The standard pattern for accessors of classes that have mutable variants is to copy rather than retain. This is codified in the #property mechanism, by using the copy attribute on the property:
// Department.h
#interface Department : NSObject
#property (readwrite, copy) NSSet *employees;
#end
This means that if you pass a mutable array to something that takes an array, it will be copied, and if you pass that to something else, it will be copied again. The trick is though that "copying" an immutable object really just retains it, so you only take a hit for that first copy. You probably want to make a copy that first time anyway so you don't pass a mutable array to something else, then mutate it behind the back of whatever you passed it to.
For Cocoa on Mac OS X, I'd also strongly encourage you to take a look at Core Data. It's an alternative to the "data set" pattern you might be used to from .NET/ADO/etc. With Core Data, you don't "get all customers" and then pass that collection around. Instead you query for the customers you care about, and as you traverse relationships of the objects you've queried for, other objects will be pulled in for you automatically.
Core Data also gets you features like visual modeling of your entities, automatic generation of property getters & setters, fine-grained control over migration from one schema version to another, and so on.