22

How can i check if a variable is an NSArray or an NSMutableArray?

Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
Alexander
  • 1,121
  • 2
  • 10
  • 6
  • Yes you can (see my answer below) - but that's generally bad practice, avoid if you can. Usually, you create the collections yourself, and you should know in advance if a collection is mutable. Try to solve your issue by design. – Motti Shneor Jan 30 '22 at 12:14

8 Answers8

32

*Note that the implementation of NS{,Mutable}Array has changed since this answer was written. As a result, isKindOfClass: now works. On what platforms where and when, I don't know.

Until it is documented as safe to do so, I would strongly recommend NOT writing code that tries to detect whether a collection is mutable or immutable. Even if it were safe, such a design pattern is almost always indicative of a serious design flaw.

(For those with access) Filed rdar://10355515 asking for clarification.


Consider:

int main (int argc, const char * argv[]) {
    NSArray *array = [NSArray arrayWithObjects: [NSObject new], nil];
    NSMutableArray *mutableArray = [NSMutableArray arrayWithObjects: [NSObject new], nil];

    NSLog(@"array's class: %@", NSStringFromClass([array class]));
    NSLog(@"mutableArray's class: %@", NSStringFromClass([mutableArray class]));

    NSLog(@"array responds to addObject: %@", 
          [array respondsToSelector: @selector(addObject:)] ? @"YES" : @"NO");

    return 0;
}

(I'm using non-empty arrays because an empty NSArray is common enough that Cocoa offers a single shared instance as an optimization.)

array's class: NSCFArray
mutableArray's class: NSCFArray
array responds to addObject: YES

I.e. neither -isKindOfClass: nor checking for implementation of addObject: will work.

In short, you can't tell the difference between an NSArray and an NSMutableArray. This is by design and very much the intended behavior. It also holds true for NSString, NSDictionary and NSSet (all of which have a mutable subclass).

That may come as a surprise. The reality, though, is that design patterns that require checking for mutability are confusing to use and incur significant overhead.

For example, if test-for-mutability were a common pattern than all of the methods in Cocoa that return NSArray instances would have to actually return NSArray instances and never return a reference to the internal NSMutableArray that might be being used.

bbum
  • 162,346
  • 23
  • 271
  • 359
  • Well, thats bad. At least there should happen something if you try to add an object to an immutable array. – nils Nov 24 '09 at 17:46
  • If you try to add an object to an immutable array, an exception will be tossed. – bbum Nov 24 '09 at 22:28
  • @bbum, I'm sure I'm not getting the point here (nor why you don't show `isKindOfClass` in your example). `isKindOfClass:[NSMutableArray class]` will perfectly well distinguish between an `NSArray` and an `NSMutableArray`, or at least it does when I test it. What am I missing? – Dan Rosenstark Oct 27 '11 at 00:52
  • Read the answer; I show the class of both the array and the mutable array. They are the same; a private subclass of NSMutableArray. Post the code you claim to be showing that they are different. – bbum Oct 27 '11 at 02:40
  • @bbum, I just tacked these lines onto your code: ` NSLog(@"NSArray is mutablearray? %d", [array isKindOfClass:[NSMutableArray class]]); NSLog(@"NSMutableArray is mutablearray? %d", [mutableArray isKindOfClass:[NSMutableArray class]]);` I get 0 and 1. [Please use @yar so it shows up in my responses.] – Dan Rosenstark Oct 27 '11 at 05:34
  • That would only be the case if the array classes implementation has changed. The behavior I describe has been the rule for a long time. I'll retest and update the answer, if so (and read the source). – bbum Oct 27 '11 at 15:04
  • @yar Yup -- the implementation changed and the behavior you are seeing is fallout. However, the change was an implementation detail for other reasons and this happened to be a coincidental result. – bbum Oct 27 '11 at 17:25
  • @bbum, thanks, I followed up on another thread below in this same question. No fear, though, I understand that EVEN IF THIS WORKS it's a bad idea and cannot be depended on to work :) – Dan Rosenstark Oct 27 '11 at 18:10
  • I am very sorry, but your answer is outright incorrect. See my answer below with same tests - just nicer output format, and for both arrays and dictionaries. – Motti Shneor Jan 25 '22 at 12:31
  • @MottiShneor Did you read the update I put at the beginning (several years ago)? The implementation changed (quite a few years ago). Relying on `-isKindOfClass:` is still relying on an implementation detail. Until the documentation is updated to specifically state that doing so is part of the API contract, it is not safe and no amount of code that works *right now* will guarantee that it will work *three years from now*. – bbum Jan 29 '22 at 05:44
  • I have read it, but these Cocoa classes predate iOS and even MacOS-X by 20 years. Mutability tests are NOT an implementation detail, but a deep design consideration in Cocoa. While writing conditional code for mutability doesn't make much sense (since you create collections yourself and should know their mutability by design) there ARE situations (e.g. implementing real deep-mutable-copy or writing custom serializing mechanisms) when you do need such API. being dynamic language, Obj-C encourages probing objects, classes, protocols and selectors as part of normal and good-behaved code. – Motti Shneor Jan 30 '22 at 12:29
6

Bad but technically accurate advice...

The only way to do it is to invoke [unknownArray addObject:someObject] inside a @try/@catch block and catch the NSInternalInconsistencyException that will be thrown if unknownArray is immutable (the actual exception could be a method not implemented or a class is immutable exception).

Good advice...

The short answer though is never try to peer inside an immutable object to see if it is internally mutable.

The reason peering at the mutability of immutable objects is prevented, is to support methods on classes that work like this:

- (NSArray *)internalObjects
{
    return myInternalObjects;
}

the object myInternalObjects could be mutable but this method on this class is saying: don't mutate what I return to you. There may be serious dangers with doing so. If the class allows you to change the array, it will have a different accessor or mutator method.

If you have a friend class that needs mutable access to the myInternalObjects variable, then declare a special adapter category that only the friend class imports with a method like

- (NSMutableArray *)mutableInternalObjectsArray;

This will allow the friend (which you are assuming is smart enough to not violate special rules) to have the access it needs but without exposing mutability in a broader sense.

Matt Gallagher
  • 14,858
  • 2
  • 41
  • 43
6

I would do the following-

if([unkownArray isKindOfClass:[NSMutableArray class]]){
  // This is a nsmutable array
}
else if ([unkownArray isKindOfClass:[NSArray class]]){
 // This is a nsarray
}
Nitesh
  • 1,389
  • 1
  • 15
  • 24
1

Use ...

[Array isKindOfClass:[NSMutableArray class]]

[Array isKindOfClass:[NSArray class]]

This will work fine.

swathy krishnan
  • 916
  • 9
  • 22
1

If you want a mutable array, make it yourself:

NSArray *someArray = /* obtain from somewhere, could be mutable, could be immutable */
NSMutableArray *mutableVersion = [someArray mutableCopy]; // definitely mutable

// later

[mutableVersion release];

There's very few cases where checking the internal type is a good idea (other answers have already covered those). If you want a mutable array, don't check whether an existing array is mutable, just make your own so that you know it is mutable.

dreamlax
  • 93,976
  • 29
  • 161
  • 209
  • But the point one would think of checking on the mutability of the array, would be that then changes made would be found by other people holding the same array... creating your own mutable copy means you can see changes, but no-one else. However I agree with bbum that any design that has to ask this question, has something deeply wrong... your data flows should be expecting either mutable or non-mutable data, not a mixture that sometimes is modified. – Kendall Helmstetter Gelner Jun 04 '12 at 05:00
  • Yes! You must know the mutability of an object when calling `NSJSONSerialization writeJSONObject:toStream:options:error:` . If you pass a mutable dict/array to this without specifying the `NSJSONReadingMutableContainers` option, this disagreement can cause a `*** Collection <__NSDictionaryM: 0xabcdef> was mutated while being enumerated` exception. In my case, we had a generic `writeDictionary:(NSDictionary *)dict asJSONToFile:(NSString *)filePath` utility method. The only way to guarantee agreement was *always* to create a mutable copy, and *always* to set `NSJSONReadingMutableContainers`. – Goffredo Oct 03 '12 at 23:13
  • @Goffredo I think you are wrong. Your exception was probably caused by other code, running in another thread, that tried to mutate the collection while you were serializing it. NSJSONSerialization will never mutate an object it received for serializing-out. Only when reading JSON data string into an NS collection, will it mutate anything. – Motti Shneor Jan 30 '22 at 12:18
0

See NSObject's -class method:

NSLog(@"myUnknownArray is of type: %@", [myUnknownArray class]);

You can also check more directly with the +class method:

BOOL isMutableArray = [myUnknownArray isKindOfClass:[NSMutableArray class]];
Alex Reynolds
  • 95,983
  • 54
  • 240
  • 345
  • 3
    Or, it wouldn't work in past releases and may work in some current releases due to an implementation change in the Foundation. – bbum Oct 27 '11 at 17:29
  • Read the docs - Apple explicitly says to NOT do this (NSObject class description, under isKindOfClass). – David H Nov 06 '13 at 21:25
  • @Alexander @Alex If the object is of type `NSMutableArray`, `[object isKindOfClass:[NSArray class]]` would also return `YES` – Burhanuddin Sunelwala Mar 31 '15 at 07:59
0

Here's my little test code, with its results below. It demonstrates the technique of verifying mutability of Foundation collection objects.

Take note that the mutable versions (e.g. NSMutableArray) inherit from the immutable ones (NSArray) and so testing for the class needs attention.

Also the test for implementation of mutable-only methods seems to work find, but may be a little slower than the test of class.

In any way - AVOID using @try and @catch to attempt mutating immutable objects, according to Apple docs these should not be used in deployment code- since exceptions cause stack unrolling which is specifically costly and also prevents and voids lots of optimizations.

id arr1 = [NSArray arrayWithObjects:@"Motti",@"name",@55, @"age", nil];
id arr2 = [NSMutableArray arrayWithObjects:@"Motti",@"name",@55, @"age", nil];

NSLog (@"arr1 isKindOfClass NSArray:%@, NSMutableArray:%@", [arr1 isKindOfClass:[NSArray class]] ? @"YES" : @"NO", [arr1 isKindOfClass:[NSMutableArray class]] ? @"YES" : @"NO");
NSLog (@"arr2 isKindOfClass NSArray:%@, NSMutableArray:%@", [arr2 isKindOfClass:[NSArray class]] ? @"YES" : @"NO", [arr2 isKindOfClass:[NSMutableArray class]] ? @"YES" : @"NO");

NSLog (@"arr1 implements insertObject:atIndex:%@", [arr1 respondsToSelector: @selector(insertObject:atIndex:)] ? @"YES" : @"NO");
NSLog (@"arr2 implements insertObject:atIndex:%@", [arr2 respondsToSelector: @selector(insertObject:atIndex:)] ? @"YES" : @"NO");

id dict1 = [NSDictionary dictionaryWithObjectsAndKeys:@"Motti",@"name",@55, @"age", nil];
id dict2 = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"Motti",@"name",@55, @"age", nil];

NSLog (@"dict1 isKindOfClass NSDictoinary:%@, NSMutableDictionary:%@", [dict1 isKindOfClass:[NSDictionary class]] ? @"YES" : @"NO", [dict1 isKindOfClass:[NSMutableDictionary class]] ? @"YES" : @"NO");
NSLog (@"dict2 isKindOfClass NSDictoinary:%@, NSMutableDictionary:%@", [dict2 isKindOfClass:[NSDictionary class]] ? @"YES" : @"NO", [dict2 isKindOfClass:[NSMutableDictionary class]] ? @"YES" : @"NO");

NSLog (@"dict1 implements setObject:forKey:%@", [dict1 respondsToSelector: @selector(setObject:forKey:)] ? @"YES" : @"NO");
NSLog (@"dict2 implements setObject:forKey:%@", [dict2 respondsToSelector: @selector(setObject:forKey:)] ? @"YES" : @"NO");

and the results printout:

14:30:42.2683 Test[381:286] arr1 isKindOfClass NSArray:YES, NSMutableArray:NO
14:30:42.2683 Test[381:286] arr2 isKindOfClass NSArray:YES, NSMutableArray:YES
14:30:42.2684 Test[381:286] arr1 implements insertObject:atIndex:NO
14:30:42.2684 Test[381:286] arr2 implements insertObject:atIndex:YES
14:30:42.2684 Test[381:286] dict1 isKindOfClass NSDictoinary:YES, NSMutableDictionary:NO
14:30:42.2685 Test[381:286] dict2 isKindOfClass NSDictoinary:YES, NSMutableDictionary:YES
14:30:42.2686 Test[381:286] dict1 implements setObject:forKey:NO
14:30:42.2687 Test[381:286] dict2 implements setObject:forKey:YES
Motti Shneor
  • 2,095
  • 1
  • 18
  • 24
-1

You have to use:

[yourArray isKindOf: [NSArray class]]

since a simple comparization of the classes may fail because your array is probably not a NSArray but of some other low level type. You may also check if your array responds to the methods you need.

[yourArray respondsToSelector: @selector(addObject:)]
nils
  • 4,649
  • 3
  • 19
  • 8
  • @bbum, under what empirical circumstances (on iOS, since the question was tagged iPhone originally) will isKindOfClass NOT work? – Dan Rosenstark Oct 27 '11 at 13:49
  • It wouldn't work when this question was answered and may work in certain releases currently as a side effect of implementation changes in the Foundation collection classes. – bbum Oct 27 '11 at 17:29
  • @bbum What do the Foundation array classes come with? The SDK I compile with, or the device I'm running my compiled code on? In other words, if it works now compiling with a particular SDK, will it break only if the SDK changes? Or can it break at any time in between? – Dan Rosenstark Oct 27 '11 at 18:08
  • 2
    @yar The implementation is delivered with the system. I.e. it can break if you rely on the behavior and an update changes said behavior. Hence the documentation request. – bbum Oct 27 '11 at 18:18
  • Thanks @bbum, always enlightening. – Dan Rosenstark Oct 27 '11 at 18:36