I have an object called Settings that inherits from NSMutableDictionary. When I try to initialize this object using
Settings *settings = [[Settings alloc] initWithContentsOfFile: #"someFile"]
it returns an object of type NSCFDictionary. As a result, it doesn't recognize my additional methods. For example, when I call the selector "save", it objects:
[NSCFDictionary save]: unrecognized selector sent to instance 0x524bc0
Of course, it's OK when I initialize using the garden variety
Settings *settings = [[Settings alloc] init]
I tried to cast it again to Settings but that didn't work. This seems really simple - what am I missing?
Thanks
NSDictionary is a class cluster. This means that the value returned from its init methods is not strictly an NSDictionary, but a subclass that implements the actual functionality. In almost every case, it is better to give your class an NSDictionary as an instance variable or to simply define a category on NSDictionary.
Chuck is correct about NSDictionary (and Dave, by extension, about NSArray/Set/String) and class clusters. Odds are that -[NSDictionary initWithContentsOfFile:] calls down to a different initializer than -init does, which is why it swaps out your allocated Settings instance for another subclass of NSMutableDictionary. (The initialization action when reading from a file may select a particular known subclass of NSDictionary which performs well for loading from a file, etc.)
I'll echo Chuck's guidance that it is almost always better to use composition or categories than inheritance for an NSDictionary. It's highly likely that you could accomplish what you're doing with categories in a much simpler way, and expose yourself to fewer potential bugs in the process. Consider yourself warned before deciding to subclass.
That being said, both NSDictionary and NSMutableDictionary have been designed to support subclassing, and on rare occasions that's the right thing to do. Think long and hard about it before trying it. If you find it's the right choice for your design, here are some key points to know and do as needed:
Override the following primitive methods from NSDictionary:
-count
-objectForKey:
-keyEnumerator
-initWithObjects:forKeys:count: (designated initializer)
Override the following primitive methods from NSMutableDictionary:
-setObject:forKey:
-removeObjectForKey:
If you're supporting NSCoding, be aware of classForKeyedArchiver and replacementObjectForKeyedArchiver: (both instance methods from NSObject) — they can totally change how your class responds, and you often unintentionally inherit some odd behavior from NS(Mutable)Dictionary. (You can verify if they are the culprit by setting a breakpoint on them, or implementing them to call super and breaking on your own code.)
I've implemented a number of these points in an NSMutableDictionary subclass of my own. You can check it out and use the code however may be helpful to you. One that particularly helped me (and could be the solution for your problem) was overloading the designated initializer, which is currently undocumented (Radar #7046209).
The thing to remember is that even though these bullets cover most common uses, there are always edge cases and less common functionality to account for. For example, -isEqual: and -hash for testing equality, etc.
If you actually read the spec for NSDictionary (a rash action, I know) you'll find a section named "Subclassing Notes". In it you will read:
If you do need to subclass NSDictionary, you need to take into account
that is represented by a Class cluster—there are therefore several
primitive methods upon which the methods are conceptually based:
initWithObjects:forKeys:
count
objectForKey:
keyEnumerator
In a subclass, you must override all these methods.
From https://stackoverflow.com/a/1191351/467588, this is what I did to make a subclass of NSDictionary works. I just declare an NSDictionary as an instance variable of my class and add some more required methods. I don't know what to call them though.
I posted my code sample here https://stackoverflow.com/a/10993594/467588.
This question is very old, and since most of these answers were posted, Apple has introduced object subscripting, which allows you to make your own classes behave more like NSMutableArray or NSMutableDictionary. This is simpler than the alternatives discussed above.
At a minimum, you have to override these methods:
//Array-style
- (id)objectAtIndexedSubscript:(NSUInteger)idx;
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
//Dictionary-style
- (id)objectForKeyedSubscript:(id <NSCopying>)key;
- (void)setObject:(id)obj forKeyedSubscript:(id <NSCopying>)key;
Here's a nice tutorial on how to do just that.
Related
I have an object that used to be an NSMutableSet but needed some more stuff attached to it. The obvious (and obviously not supported) thing to do is to subclass NSMutableSet and tack on the two additional properties. Since NSMutableSet, like basically all Cocoa data structures, is a class cluster I cannot subclass it in the usual way, since the super class just throws exceptions. This led me down several paths.
The first path was to create sort of a composite object that declared itself as a subclass of NSMutableSet but really just forwarded the invocations to an internal NSMutableSet. I didn't want to have to implement every method on NSMutableSet, so I thought forwardInvocation: would be a good way to accomplish my mission. Unfortunately, the abstract class of NSMutableSet implements all of the methods on the interface and their implementations throw exceptions, so I was never getting to the point where I could forward an invocation.
The second path was to subclass NSProxy and forward the invocation from there. This solution falls short in that I need to copy the interface of NSMutableSet over unless there's a way to declare "this class implements this interface" that I don't know about (this could very well be the solution).
The third path was to create a category on NSMutableSet and import it just for the class that needs to use it but that falls short since you cannot add non-dynamic properties via a category. That led me to using associated objects in a category. I'm willing to admit that that is the correct solution for this use case, but I wish it weren't since it's kind of clunky. It's doubly clunky since the properties I'm adding are primitive so I'll have to wrap and unwrap them when setting and getting the association (unless there's a way to associate primitives which I'm unfamiliar with).
Essentially, what I would like is something that behaves functionally as a subclass of NSMutableSet (and all class clusters) but cannot figure out the best approach. Thanks!
Trying to subclass Cocoa class clusters will just create an awful lot of hurt. It may seem a good idea, but you will forever run into problems.
Just create an NSObject with an NSMutableSet as the first member object.
Subclassing Cocoa class cluster is kind of discouraged. Not without reasons. Please do not enter this crashy world.
Either of your solutions will work. I've successfully used the first path with NSArray and NSDictionary, so I believe it should work fine for NSMutableSet as well. Just remember that you need to override not only forwardInvocation:, but a few of other methods as well. Please consult Surrogate Objects sections of Apple docs:
Although forwarding mimics inheritance, the NSObject class never confuses the two. Methods like respondsToSelector: and isKindOfClass: look only at the inheritance hierarchy, never at the forwarding chain.
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html
In my case, I've overridden:
conformsToProtocol:
isKindOfClass:
isMemberOfClass:
respondsToSelector:
instancesRespondToSelector:
forwardInvocation:
methodSignatureForSelector:
instanceMethodSignatureForSelector:
from which isKindOfClass:, conformsToProtocol: and respondsToSelector: are definitely crucial.
I've also used the third path with good results, but I admit the associated objects API is clunky.
First, gnasher729 is correct. Don't subclass class clusters. Just don't do it. Can you do it? If I tell you that you can't, will it help you convince yourself that you shouldn't? I can lie if it helps you make good choices.
But in all seriousness, it is almost always meaningless as well. Is your subclass really a specific kind of set? Or is it really kind of like a set. Consider NSAttributedString. It isn't a kind of string, it has-a string. This is almost always better.
And also, class clusters happen to be a royal pain to subclass.
That said, adding associated values onto a data structure, as you've already discovered, is generally just fine, because what you really want is "hey, I have some data that needs to go along with this other data." Wrapping has gotten so easy that it shouldn't really slow you down. See https://stackoverflow.com/a/14918158/97337:
objc_setAssociatedObject(self, animatingKey, #(value), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
And with "one weird trick", you can make this really easy:
#interface NSObject (BoolVal)
#property (nonatomic, readwrite, assign) BOOL boolVal;
#end
#implementation NSObject (BoolVal)
- (BOOL)boolVal {
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)setBoolVal:(BOOL)value {
objc_setAssociatedObject(self, #selector(boolVal), #(value), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end
But I'd still come back to the question of whether this is really a kind of set (rather than just like a set), and whether it really needs to respond to every message that can be sent to a set. As with NSAttributedString, your real needs are often much smaller than that in practice, and wrapping the handful of methods you need is often worth the simplicity and control.
For completeness, let's look at your first path:
create sort of a composite object that declared itself as a subclass of NSMutableSet but really just forwarded the invocations to an internal NSMutableSet
Can you subclass an NSMutableSet? Yes, but should you? The documentation for NSMutableSet says:
Subclassing Notes
There should be little need of subclassing. If you need to customize behavior, it is often better to consider composition instead of subclassing.
So weigh that up and if you want to subclass refer again to the documentation:
Methods to Override
In a subclass, you must override both of its primitive methods:
addObject:
removeObject:
You must also override the primitive methods of the NSSet class.
And looking at the NSSet class documentation we find its primitive methods are:
Methods to Override
In a subclass, you must override all of its primitive methods:
count
member:
objectEnumerator
That's it, 5 methods.
You can define your own class as a subclass of NSMutableSet, add an instance variable which is an instance of NSMutableSet, implement 5 methods and redirect them to the set instance, add whatever init methods you wish, and then add your additional properties.
If performance is of concern then the tradeoff is between redirecting those five methods and accessing associated objects for your additional properties. You'll need to profile to work that out, but if and only if performance becomes an issue.
Let's say I'm subclassing NSString, for example. My subclass is named MyString, and I want to override [NSString stringWithFormat:], but also add another parameter, so my method would be [MyString stringWithParameter:format:]. This method would serve to replace [NSString stringWithFormat:].
If this was my setup, users would still be able to call [MyString stringWithFormat:], which would in turn call [NSString stringWithFormat:]. Is there a way to stop users from calling [MyString stringWithFormat:]? In other words, is there a way to prevent a subclass of an object from calling particular methods of its parent class?
Use compiler attributes to mark the method unavailable. In your header file:
+ (id)stringWithFormat:(NSString *)format, ... __attribute__((unavailable("Do not call +stringWithFormat")));
This will cause a compiler error and prevent the method from being used.
As others have hinted at, NSString is a class cluster, and as such can be quite tricky to subclass correctly. But apparently your question was about broader concepts of subclassing, rather than about dealing with the added complexity of class clusters, so let's leave that aside.
Rather than thinking in terms of preventing callers from sending stringWithFormat: to an instance of MyString, consider having your MyString class override stringWithFormat: to call your custom stringWithParameter:format: method and provide a default value for the parameter argument.
As #Stephen Melvin pointed out, typically your custom method would then call [super stringWithFormat:] to preserve the existing functionality.
EDIT
Here are a few points to consider when subclassing a class cluster:
A new class that you create within a class cluster must:
Be a subclass of the cluster’s abstract superclass
Declare its own storage
Override all initializer methods of the superclass
Override the superclass’s primitive methods (described below)
Source: https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/ClassClusters/ClassClusters.html
If you feel like it'd be worth the effort, Mike Ash has a great blog post on how to go about it: http://www.mikeash.com/pyblog/friday-qa-2010-03-12-subclassing-class-clusters.html
But the first question to ask yourself is: "What problem am I trying to solve by subclassing NSString (or whatever the case may be), and is there perhaps a simpler approach to solving the problem?"
I need to have a class, which has all methods of NSArray, which behave the same way, but 2 methods are modified.
I want to override these 2 methods in my custom class:
1) countByEnumeratingWithState:objects:count:
2) objectAtIndex:
After hours of research I don't see any reasonable way to do that, because:
I don't want to use category, because not all NSArray instances should have the modified behaviour. (Plus that throws warnings)
I don't want to re-write all initializers plus all arrayWith... methods + the primitive methods + implemented my own storage (because this functionality is already implemented in Cocoa, right? Why would I re-implement all the functionality of a class that is already there?)
If I have my custom class inherit NSObject and use NSArray as storage in an ivar, then all NSArray's methods are not available when programming in Xcode (even if I can forward them to the NSArray ivar)
I had some success overwriting the method implementations on demand by using method_setImplementation(...), but still can't figure out a way to have dynamically a class created at runtime, which then will have custom implementation of the 2 methods I mentioned.
Looking forward to your ideas! Thanks
Mantra: If something is hard (or seems like it requires more code than is necessary), it is likely that your design is counter to the design principals of the iOS / OS X frameworks. It may yield a better solution to revisit your design.
To answer the original question, if you want to subclass NSArray (or NSMutableArray), you need to implement the primitive methods, no more, no less.
The primitive methods are the methods declared in the #interface of the class itself. I.e.:
#interface NSArray : NSObject
- (NSUInteger)count;
- (id)objectAtIndex:(NSUInteger)index;
#end
And for NSMutableArray:
#interface NSMutableArray : NSArray
- (void)addObject:(id)anObject;
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index;
- (void)removeLastObject;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;
#end
If you subclass NSMutableArray and implement the above 7 methods (the two from NSArray, too), you will have an NSMutableArray subclass that is compatible -- assuming your methods are correctly implemented -- with all APIs that consume mutable arrays.
This is because of the way class clusters are designed. The public classes are abstract; are never directly instantiated. They provide a primitive interface that contains the class's core functionality and then concrete implementations of all the other non-primtive API (save for the initializers, see below) that are implemented in terms of the primitives. Concrete, private, subclasses then override all the primitives and some of the non-primitives to provide optimal behaviors for specific configurations.
I want to have an NSArray instance for a library I'm working on and I
want to have it working transparently for the users of my library. Ie.
for them should be no difference between using a normal NSArray and
the modified class I'll be providing. Ie. it's a storage concern,
which the end users should not be concerned with and the interface
should remain the same as NSArray - therefore loosing all init methods
is not really an option at that point.
The initialization methods are not a part of the primitive interface to NSArray. You are adding a requirement above and beyond "make a class compatible with NSArray / NSMutableArray" as defined by the documentation. Nothing wrong with that, just pointing it out.
The reason why this is the case is because it is exceptionally rare to subclass the collection classes to provide the kind of business logic you describe. Collections are very generic in their behavior whereas such business logic that conditionalizes collection behavior would be done in a class that manages the overall model layer object graph.
If you really want to do this, provide an implementation of whatever init* methods you want, calling through to your wrapped generic instance as needed. There isn't anything so special about the implementations of the initializers that you are going to lose much in doing so.
No need to implement all of them, either. Implement one or two and #throw a descriptive exception on the rest.
If you do decide to forward the ones that accept var-args, you can't directly because there are no va_list accepting methods. Instead, you'll want to convert the va_list of arguments into a language array (i.e. id[] foo = malloc(... * sizeof(id));) and pass it to initWithObjects:count:.
Some other comments:
What you are doing [provide full NS*Array interface in a subclass] seems hard because it is not a common pattern and the framework designers saw no need to create a design to support it. Custom behaviors at the primitive collection levels are almost always better implemented at a higher level within the object graph. Almost always.
method_setImplementation() and dynamic class creation is academically interesting, but pretty much never a solution. Obviously, mucking with the NSArray or NSMutableArray classes (or the concrete implementation classes) is going to blow up the rest of the frameworks that rely upon standard behavior. Beyond that it, it is a pattern of dynamic OO composition that is not really intended to be used in Objective-C; it'll be a pain in the ass to maintain.
Instead of subclassing NSArray why not create a new class based on NSObject that contains an NSArray?
Then you can use all the functions of the NSArray and add your own methods that will do custom actions with it?
Or do you NEED an NSArray?
I am in a situation where I want to dynamically generate getters and setters for a class at runtime (in a similar manner to what NSManagedObject does behind the scenes). From my understanding, this is possible using resolveInstanceMethod: on a specific class. At this point, you would have to use class_addMethod to dynamically add the method based on the selector. I understand this at a theoretical level, but I haven't delved much into the obj-c runtime, so I was curious if there were any great examples of how to do this. Most of my knowledge comes from this article:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html
Any thoughts / examples?
The only nice discussion I know is at Mike Ash's blog post. It's not that hard, actually.
I once needed to split a big NSManagedObject subclass into two, but decided to keep the fact an implementation detail so that I don't have to rewrite other parts of my app. So, I needed to synthesize getter and setter which sends [self foo] to [self.data foo], automatically.
To achieve that, I did the following:
Prepare the new method, already in my class.
- (id)_getter_
{
return objc_msgSend(self.data, _cmd);
}
- (void)_setter_:(id)value
{
objc_msgSend(self.data, _cmd,value);
}
Note that _cmd has the selector in it. So, usually, _cmd is either #selector(_getter_) or #selector(_setter_) in these methods, but I'm going to plug the implementation of _getter_ as the implementation of foo. Then, _cmd contains #selector(foo), and thus calls self.data's foo.
Write a generic synthesizing method:
+(void)synthesizeForwarder:(NSString*)getterName
{
NSString*setterName=[NSString stringWithFormat:#"set%#%#:",
[[getterName substringToIndex:1] uppercaseString],[getterName substringFromIndex:1]];
Method getter=class_getInstanceMethod(self, #selector(_getter_));
class_addMethod(self, NSSelectorFromString(getterName),
method_getImplementation(getter), method_getTypeEncoding(getter));
Method setter=class_getInstanceMethod(self, #selector(_setter_:));
class_addMethod(self, NSSelectorFromString(setterName),
method_getImplementation(setter), method_getTypeEncoding(setter));
}
Note that this is a class method. So self stands for the class. Note also that I didn't hardcode type encodings (which tells Objective-C runtime what the arguments of the particular method are). The syntax of type encodings is documented, but constructing by hand is very error-prone; I wasted a few days that way until Mike Ash told me to stop it. Generate it using an existing method.
Generate forwarders at the earliest possible time:
+(void)load
{
for(NSString*selectorName in [NSArray arrayWithObjects:#"foo", #"bar", #"baz",nil]){
[self synthesizeForwarder:selectorName];
}
}
This generates foo, setFoo:, bar, setBar:, and baz, setBaz:.
Hope this helps!
Another example is one I wrote, called DynamicStorage, available here:
https://github.com/davedelong/Demos
The primary impetus behind it was this question, which was asking how to use an NSMutableDictionary as the backing store for any object ivar. I wrote a class that will generate getters and setters for any #property, respecting things like a custom getter/setter name, the object memory management policy, etc. The neat thing about it is that it's using imp_implementationWithBlock() so that it only has to calculate the appropriate property name once (and then captures and saves it as part of the block).
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.