NSMutableArray - force the array to hold specific object type only
Is there a way to force NSMutableArray to hold one specific object type only?
I have classes definitions as follow:
@interface Wheel:NSObject
{
int size;
float diameter;
}
@end
@interface Car:NSObject
{
NSString *model;
NSString *make;
NSMutableArray *wheels;
}
@end
How can I force wheels array to hold Wheel objects only with code? (and absolutely not other objects)
Solution 1:
Update in 2015
This answer was first written in early 2011 and began:
What we really want is parametric polymorphism so you could declare, say,
NSMutableArray<NSString>
; but alas such is not available.
In 2015 Apple apparently changed this with the introduction of "lightweight generics" into Objective-C and now you can declare:
NSMutableArray<NSString *> *onlyStrings = [NSMutableArray new];
But all is not quite what it seems, notice the "lightweight"... Then notice that the initialisation part of the above declaration does not contain any generic notation. While Apple have introduced parametric collections, and adding a non-string directly to the above array, onlyStrings
, as in say:
[onlyStrings addObject:@666]; // <- Warning: Incompatible pointer types...
will illicit the warning as indicated, the type security is barely skin deep. Consider the method:
- (void) push:(id)obj onto:(NSMutableArray *)array
{
[array addObject:obj];
}
and the code fragment in another method of the same class:
NSMutableArray<NSString *> *oops = [NSMutableArray new];
[self push:@"asda" onto:oops]; // add a string, fine
[self push:@42 onto:oops]; // add a number, no warnings...
What Apple have implemented is essentially a hinting system to assist with automatic inter-operation with Swift, which does have a flavour of type-safe generics. However on the Objective-C side, while the compiler provides some extra hints the system is "lightweight" and type-integrity is still ultimately down to the programmer - as is the Objective-C way.
So which should you use? The new lightweight/pseudo generics, or devise your own patterns for your code? There really is no right answer, figure out what makes sense in your scenario and use it.
For example: If you are targeting interoperation with Swift you should use the lightweight generics! However if the type integrity of a collection is important in your scenario then you could combine the lightweight generics with your own code on the Objective-C side which enforces the type integrity that Swift will on its side.
The Remainder of the 2011 Answer
As another option here is a quick general subclass of NSMutableArray which you init with the kind of object you want in your monomorphic array. This option does not give you static type-checking (in as much as you ever get it in Obj-C), you get runtime exceptions on inserting the wrong type, just as you get runtime exceptions for index out of bounds etc.
This is not thoroughly tested and assumes the documentation on overriding NSMutableArray is correct...
@interface MonomorphicArray : NSMutableArray
{
Class elementClass;
NSMutableArray *realArray;
}
- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems;
- (id) initWithClass:(Class)element;
@end
And the implementation:
@implementation MonomorphicArray
- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems
{
elementClass = element;
realArray = [NSMutableArray arrayWithCapacity:numItems];
return self;
}
- (id) initWithClass:(Class)element
{
elementClass = element;
realArray = [NSMutableArray new];
return self;
}
// override primitive NSMutableArray methods and enforce monomorphism
- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
if ([anObject isKindOfClass:elementClass]) // allows subclasses, use isMemeberOfClass for exact match
{
[realArray insertObject:anObject atIndex:index];
}
else
{
NSException* myException = [NSException
exceptionWithName:@"InvalidAddObject"
reason:@"Added object has wrong type"
userInfo:nil];
@throw myException;
}
}
- (void) removeObjectAtIndex:(NSUInteger)index
{
[realArray removeObjectAtIndex:index];
}
// override primitive NSArray methods
- (NSUInteger) count
{
return [realArray count];
}
- (id) objectAtIndex:(NSUInteger)index
{
return [realArray objectAtIndex:index];
}
// block all the other init's (some could be supported)
static id NotSupported()
{
NSException* myException = [NSException
exceptionWithName:@"InvalidInitializer"
reason:@"Only initWithClass: and initWithClass:andCapacity: supported"
userInfo:nil];
@throw myException;
}
- (id)initWithArray:(NSArray *)anArray { return NotSupported(); }
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { return NotSupported(); }
- (id)initWithContentsOfFile:(NSString *)aPath { return NotSupported(); }
- (id)initWithContentsOfURL:(NSURL *)aURL { return NotSupported(); }
- (id)initWithObjects:(id)firstObj, ... { return NotSupported(); }
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { return NotSupported(); }
@end
Use as:
MonomorphicArray *monoString = [[MonomorphicArray alloc] initWithClass:[NSString class] andCapacity:3];
[monoString addObject:@"A string"];
[monoString addObject:[NSNumber numberWithInt:42]]; // will throw
[monoString addObject:@"Another string"];
Solution 2:
With XCode 7 generics are now available in Objective-C!
So you can declare your NSMutableArray
as:
NSMutableArray <Wheel*> *wheels = [[NSMutableArray alloc] initWithArray:@[[Wheel new],[Wheel new]];
The compiler will give you a warning if you try to put non-Wheel object in your array.
Solution 3:
I could be wrong (I'm a noob), but I think, if you create a custom protocol and make sure the objects you are adding to the array follow the same protocol, then when you declare the array you use
NSArray<Protocol Name>
That should prevent objects being added that do not follow the said protocol.
Solution 4:
as per i know.. before you added any object in wheels mutableArray, u have to add some check mark. Is the object which i am adding is class "wheel". if it is then add, other wise not.
Example:
if([id isClassOf:"Wheel"] == YES)
{
[array addObject:id)
}
Something like this. i dont remember the exact syntax.
Solution 5:
I hope this will help (and work... :P )
Wheel.h file:
@protocol Wheel
@end
@interface Wheel : NSObject
@property ...
@end
Car.h file:
#import "Wheel.h"
@interface Car:NSObject
{
NSString *model;
NSString *make;
NSMutableArray<Wheel, Optional> *wheels;
}
@end
Car.m file:
#import "Car.h"
@implementation Car
-(id)init{
if (self=[super init]){
self.wheels = (NSMutableArray<Wheel,Optional>*)[NSMutableArray alloc]init];
}
return self;
}
@end