NSMutableArray with a specific class of object to be contained - objective-c

Is there any way to specify what specific type of object can be contained within an NSMutableArray?
EDIT More specifically... Is there a way to restrict the class that the object must belong to?

Well, you could always subclass NSMutableArray but as everyone else has said, it is hard to imagine a good reason to do this....
From Subclassing Notes in the docs you would basically have to over-ride the following functions and check for the proper class:
insertObject:atIndex:
addObject:
replaceObjectAtIndex:withObject:
the primitive methods of the NSArray class

You could check the class of the object before adding it to the array.
NSMutableArray *myArray = [NSMutableArray alloc] init];
if ([someObject isKindOfClass:[ClassYouWantInArray class]]){
[myArray addObject:someObject];
}

Related

How to check assignment since addObject doesn't access setter?

I just noticed that calling addObject: on an NSMutableArray doesn't access that array's setter.
E.g., for NSMutableArray self.myArray, [self.myArray addObject:object] does not use [self setMyArray:array] to add the object.
Previously I have been using custom setters and getter to check assignment before assigning; e.g., if I wanted an array that only accepted objects of class MyClass, I would do the following:
- (void)setMyArray:(NSMutableArray *)myArray
{
for (id object in myArray)
{
if (![object isKindOfClass:[MyClass class]]) return;
}
_myArray = myArray;
}
- (NSMutableArray *)myArray
{
if (!_myArray) _myArray = [[NSMutableArray alloc] init];
_myArray = myArray;
}
How do I go about achieving this same functionality when changing the array via addObject:, removeObject:, and other similar functions that may circumvent the setter?
Generally this kind of problem is the reason why NSMutableArray is usually avoided in preference of NSArray.
This is the simple solution, use NSArray instead of NSMutableArray:
self.myArray = [self.myArray arrayByAddingObject:foo];
However, if the array is really big that will cause performance issues. Then you've got two options:
you can have your own addObjectToMyArray: method in your class and always use that
you can create an NSArrayController and use that to access your array. It will implement key value observing and bindings and all of that stuff.
NSMutableArray is designed to perform addObject: with as few CPU instructions as possible and therefore does not proved any way for external code to be notified that the object was added. You have to have some other class wrapped around it.
Do not try to subclass NSMutableArray, because it is a "class cluster" making subclasses extremely complicated.
If what you wish to do is ensure objects in the array are of a particular class then this answer to the question "NSMutableArray - force the array to hold specific object type only" provides code to do exactly that.
If you wish to do other checks on assignment then you can use the code in that answer as a starting point.

Can the NSMultableArray mention which object inside the NSMultableArray?

The NSMutableArray can store every NSObject, but can I mention the NSMutableArray can get store my item only, for example, a NSMutableArray that store NSString only?
I remember that the java array can do that, can the objective C array do the similar things? Thanks.
Objective-C does not have this kind of generic constraint on NSArray/NSMutableArray. You have therefore two solutions:
Subclass NSArray/NSMutableArray and check for element type. It is strongly discouraged as NSArray/NSMutableArray is a class "cluster" and not obvious to subclass.
Create a category with specific methods that check the right type. You will have a compile-time enforcement of the type.
You can try it like this -
NSMutableArray *arr = [[[NSMutableArray alloc] init] autorelease];
if([obj isKindOfClass:[NSString class]])
[arr addObject:obj];
This way you end up adding only NSString to your arr.
Not by default, no. NSArray and its mutable counterpart just store pointers which happen to point obj-c objects. These objects can of any type. It would be up to you to make sure that only NSString's get in your array.
You could potentially subclass NSArray and override the addObject: methods such that they throw an exception if you try to add a non-NSString object.

How to store the NSArray with customized Object?

I have an NSMutableArray, if this is store string, I can read, and write it successfully, using this method.
[array writeToFile:m_sApplicationPlistPath atomically:YES];
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:m_sApplicationPlistPath];
but if my Array add something which is not a simple string, for example, I add a special Object to the array, like this:
[array addObject:[[SpecialObject alloc] init]];
I find that I can't read back the special object, how can I solve it, thank you.
Implement the NSCoding protocol. (encodeWithCoder and initWithCoder)
If you still cannot use the method writeToFile:atomically: then you have to serialize the NSArray to get data, and then write it to an file.
NSData *data = NSKeyedArchiver archivedDataWithRootObject:theArray];
I think you can't do that because [NSArray writeToFile:atomically:] method makes use of property lists, which are only available for certain data types (NSString, NSData, NSArray or NSDictionary).
In order for you to write the array to a file you can make SpecialObject comply to NSCoding protocol and then save the array using -[NSKeyedArchiver archiveRootObject:toFile:] using the array as root object.
You can get this to work by using methods defined in the NSKeyValueCoding protocol to convert the instances of your custom class to instances of NSDictionary before you write them. You could do a similar conversion after reading the plist file back in to recreate the objects.
Here's an example that converts an array of instances of a custom Book class to an array of dictionaries, using the dictionaryWithValuesForKeys: method declared in NSKeyValueCoding (and implemented by NSObject, so all objects inherit this behavior):
+ (NSArray *)dictionariesFromBooks:(NSArray *)books
{
NSMutableArray *bookDicts = [NSMutableArray arrayWithCapacity:[books count]];
for (Book *currBook in books)
{
NSDictionary *currDict = [currBook dictionaryWithValuesForKeys:[Book keys]];
[bookDicts addObject:currDict];
}
return bookDicts;
}
Similarly, here's a method that populates instances of Book using the NSKeyValueCoding method setValuesForKeysWithDictionary:
+ (NSArray *)booksFromDictionaries:(NSArray *)bookDicts
{
NSMutableArray *books = [NSMutableArray arrayWithCapacity:[bookDicts count]];
for (NSDictionary *currDict in bookDicts)
{
Book *currBook = [[Book alloc] init];
[currBook setValuesForKeysWithDictionary:currDict];
[books addObject:currBook];
[currBook release];
}
return books;
}
With a little work, this can be made to cascade to nested custom objects. For example, if Book contained a nested Author instance, you could override NSKeyValueCoding methods such as setValue:forKey: and dictionaryWithValuesForKeys: to convert the instance on the fly.
From docs:
If the array’s contents are all property list objects (NSString, NSData, NSArray, or NSDictionary objects), the file written by this method can be used to initialize a new array with the class method arrayWithContentsOfFile: or the instance method initWithContentsOfFile:. This method recursively validates that all the contained objects are property list objects before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.

Static Variable in Cocoa Class Category

I have a cocoa 'category' for adding inflections (pluralize, singularize, etc.) to NSString. The code requires loading a set of regular expression rules and exceptions from a PLIST into dictionaries and arrays, as well as adding manual exceptions from code. I need a way to persist these data structures (as class members) between multiple calls to the inflection code (all instance methods). I attempted:
+ (NSMutableArray *)uncountables
{
static NSMutableArray *uncountables = nil;
if (uncountables == nil) uncountables = [NSMutableArray array];
return uncountables;
}
However, it appears to fail occasionally. Does a good way of doing this exist? I don't want to subclass NSString if possible. Thanks.
[NSMutableArray array];
returns an autoreleased array. Use this instead:
[[NSMutableArray alloc] init];
I think this code is OK. I use the same thing a lot for singletons. But be aware that it is not thread safe this way. Maybe you calling it from different threads?
As drawnonward already mentioned, [NSMutableArray array]; returns an autoreleased array. But I don't think, it's a good idea to return non-autoreleased array, because it contradicts with Cocoa memory management conceptions - only alloc, copy and new should be released manually. All other initializations are autoreleased.
So, you should just use
interface:
NSArray *a;
...somewhere in a code...
a = [[NSString uncountables] retain];
...
- (void)dealloc {
[a release];
}
to get properly retained/released objects.

Initialize NSMutableArray: [NSMutableArray array];

If you initialize an NSMutableArray with NSArray's convenience method as above, do you get an NSArray or an NSMutableArray?
Any consequences?
(I know that NSMutableArray has "arrayWithCapacity:, I'm just curious)
If you initialize it using:
NSMutableArray *array = [NSMutableArray array];
you get a NSMutableArray. One great feature of Objective-C is that class methods are inherited by subclasses.
So, in a class method you can do something like this:
+(id)array {
return [[[self alloc] init] autorelease];
}
and self will be referencing to the class object where the code is executing (NSArray or NSMutableArray).
Update: While my advice to "test it yourself" is generally a good idea, it was a little trigger-happy in this case. Thanks to Jim in the comments for pointing at that my suggestion below doesn't work well for these classes, because the various forms of NSArray are all implemented by a CoreFoundation toll-free bridging class.
----- Original Answer For Context Below -----
The easiest way to answer a question like this is to test it yourself. Try allocating the array in the way you were curious about, then NSLog from your code:
NSLog(#"We got a %#", NSStringFromClass([theArray class]));