NSMutableArray - force the array to hold specific object type only

Objective CCocoaNsmutablearray

Objective C Problem Overview


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)

Objective C Solutions


Solution 1 - Objective C

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 - Objective C

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 - Objective C

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 - Objective C

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 - Objective C

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

Solution 6 - Objective C

Xcode 7 allows you to define Arrays, Dictionaries, and even your own Classes as having generics. The array syntax is as follows:

NSArray<NSString*>* array = @[@"hello world"];

Solution 7 - Objective C

I don't believe there's any way to do it with NSMutableArray out of the box. You could probably enforce this by subclassing and overriding all the constructors and insertion methods, but it's probably not worth it. What are you hoping to achieve with this?

Solution 8 - Objective C

That's not possible; an NSArray (whether mutable or not) will hold any object type. What you can do is to create your own custom subclasses as already suggested by Jim. Alternatively, if you wanted to filter an array to remove objects that weren't of the type you want, then you could do:

- (void)removeObjectsFromArray:(NSMutableArray *)array otherThanOfType:(Class)type
{
    int c = 0;
    while(c < [array length])
    {
        NSObject *object = [array objectAtIndex:c];
        if([object isKindOfClass:type])
          c++;
        else
          [array removeObjectAtIndex:c];
    }
}

...
[self removeObjectsFromArray:array otherThanOfType:[Car class]];

Or make other judgments based on the result of isKindOfClass:, e.g. to divide an array containing a mixture of Cars and Wheels into two arrays, each containing only one kind of object.

Solution 9 - Objective C

You can use the nsexception if you dont have the specific object.

for (int i = 0; i<items.count;i++) {
 if([[items objectAtIndex:i] isKindOfClass:[Wheel class]])
 {
  // do something..!
 }else{
  [NSException raise:@"Invalid value" format:@"Format of %@ is invalid", items];
  // do whatever to handle or raise your exception.
 }
}

Solution 10 - Objective C

Here's something I've done to avoid subclassing NSMutableArray: use a category. This way you can have the argument and return types you want. Note the naming convention: replace the word "object" in each of the methods you will use with the name of the element class. "objectAtIndex" becomes "wheelAtIndex" and so on. This way there's no name conflict. Very tidy.

typedef NSMutableArray WheelList;
@interface NSMutableArray (WheelList) 
- (wheel *) wheelAtIndex: (NSUInteger) index;
- (void) addWheel: (wheel *) w;
@end

@implementation NSMutableArray (WheelList)

- (wheel *) wheelAtIndex: (NSUInteger) index 
{  
    return (wheel *) [self objectAtIndex: index];  
}

- (void) addWheel: (wheel *) w 
{  
    [self addObject: w];  
} 
@end


@interface Car : NSObject
@property WheelList *wheels;
@end;


@implementation Car
@synthesize wheels;

- (id) init 
{
    if (self = [super init]) {
        wheels = [[WheelList alloc] initWithCapacity: 4];
    }
    return self;
}

@end

Solution 11 - Objective C

protocol maybe a good idea:

@protocol Person <NSObject>
@end

@interface Person : NSObject <Person>
@end

to use:

NSArray<Person>*  personArray;

Solution 12 - Objective C

There is one-header file project which allows this: Objective-C-Generics

Usage:

Copy ObjectiveCGenerics.h to your project. When defining a new class use the GENERICSABLE macro.

#import "ObjectiveCGenerics.h"

GENERICSABLE(MyClass)

@interface MyClass : NSObject<MyClass>

@property (nonatomic, strong) NSString* name;

@end

Now you can use generics with arrays and sets just as you normally do in Java, C#, etc.

Code: enter image description here

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTuyen NguyenView Question on Stackoverflow
Solution 1 - Objective CCRDView Answer on Stackoverflow
Solution 2 - Objective CandreaciprianiView Answer on Stackoverflow
Solution 3 - Objective CGravediggaView Answer on Stackoverflow
Solution 4 - Objective Charshit2811View Answer on Stackoverflow
Solution 5 - Objective CAviram NetanelView Answer on Stackoverflow
Solution 6 - Objective CBrian TrzupekView Answer on Stackoverflow
Solution 7 - Objective CJimView Answer on Stackoverflow
Solution 8 - Objective CTommyView Answer on Stackoverflow
Solution 9 - Objective CLalith BView Answer on Stackoverflow
Solution 10 - Objective CCharles GillinghamView Answer on Stackoverflow
Solution 11 - Objective ClbsweekView Answer on Stackoverflow
Solution 12 - Objective CMishaView Answer on Stackoverflow