1

I know Apple has cautioned against using it. But given their reasoning, the results are far from relevant and expected.

Here is my debug output - the results aren't different in code - below is just for brevity:

(lldb) po [@"Hello" isKindOfClass:[NSMutableString class]]
true => A mutable string?

(lldb) po [[@"Hello" mutableCopy] isKindOfClass:[NSMutableString class]]
0x00000001019f3201   => What's that?

(lldb) po [[@"Hello" mutableCopy] isMemberOfClass:[NSMutableString class]]
0x000000010214e400   => What's that?

(lldb) po [@"Hello" isMemberOfClass:[NSMutableString class]]
false  => Once again?

Further to that, I removed all the string literal code and tested the following:

NSMutableString * m = [[NSMutableString alloc] initWithString:@"Hello"];

bool b = [m isKindOfClass:[NSMutableString class]];
NSLog(@"%d", b); --> 1 Expected.
b = [m isKindOfClass:[NSString class]];
NSLog(@"%d", b); --> 1 Expected.
b = [m isMemberOfClass:[NSString class]];
NSLog(@"%d", b); --> 0 Expected.
b = [m isMemberOfClass:[NSMutableString class]];
NSLog(@"%d", b); --> 0 Not Expected.

Is there an enlightenment?

UPDATE:

Apple's own take:

Be careful when using this method on objects represented by a class cluster. Because of the nature of class clusters, the object you get back may not always be the type you expected. If you call a method that returns a class cluster, the exact type returned by the method is the best indicator of what you can do with that object.

Why not simply say do not employ isKindOfClass and isMemberOfClass with cluster classes?

The explanation prevents use from the perspective such as:

You might end up modifying something that you are not supposed to.

instead of stating:

These methods do not work with class clusters. (in the examples, I have shown above - I am clearly passing correct objects and still not getting expected results.)

UPDATE 2:

Filed with Apple Radar.

Nirav Bhatt
  • 6,940
  • 5
  • 45
  • 89
  • 1
    Add `NSLog(@"m class is %@", [m class]);`. You will probably find it is not exactly `NSMutableString` which is why your last test fails. – rmaddy Jan 01 '16 at 23:33
  • 3
    BTW - `isMemberOfClass:` and `isKindOfClass:` return `BOOL` so use `p`, not `po` in the debugger to print the value of the result. – rmaddy Jan 01 '16 at 23:34
  • I know its class cluster - it happens with NSNumber and NSArray too because of the same reason. But I want to know what's Apple's reasoning behind handing methods that mislead. Reading a property (class) can mislead at times. And it's definitely out-of-the-way thing to replace isKindOfClass for just some sort of objects. I highly doubt if respondsToSelector is reliable, considering above results? – Nirav Bhatt Jan 01 '16 at 23:37
  • `isMemberOfClass:` checks to see if it is the exact same class. Due to the class cluster, it's not which is why the last check fails. But why don't you think `respondsToSelector:` wouldn't be reliable in this case? It would. – rmaddy Jan 01 '16 at 23:39
  • [@"hello" respondsToSelector:@selector(appendString:)] --> returns TRUE in XCode 6.1. – Nirav Bhatt Jan 01 '16 at 23:58
  • [Why does NSString respond to appendString:?](http://stackoverflow.com/q/5894910) – jscs Jan 02 '16 at 00:39
  • @JoshCaswell good info - which confirms that respondsToSelector tightly couples with whatever decides isMemberOfClass / isKindOfClass. – Nirav Bhatt Jan 02 '16 at 07:50
  • Had the original docs given any details of "class clusters", this topic would have been much easier to understand: https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/ClassClusters/ClassClusters.html – benc Apr 03 '23 at 21:50

2 Answers2

5

The methods don't "mislead" as you claim in the comments. Because NSString and NSMutableString are class clusters, they can return an instance of any concrete subclass that is-a NSString or NSMutableString, respectively.

As it happens, most concrete subclasses in the NSString cluster are also subclasses of NSMutableString. Instead of using the actual class to control mutability, they use a flag or something like that. All perfectly valid and complying with the design contract.

So, that's why [@"Hello" isKindOfClass:[NSMutableString class]] returns true. You ask "A mutable string?" No. That expression is not a valid test of mutability. As documented, there is no valid test of mutability. This is at the core of your misunderstanding. You must not attempt to interrogate the class of an object to determine if it's mutable. You must respect the static type of the pointer in the API.


Edit: This is documented in Concepts in Objective-C Programming: Object Mutability – Receiving Mutable Objects:

Use Return Type, Not Introspection

To determine whether it can change a received object, the receiver of a message must rely on the formal type of the return value. If it receives, for example, an array object typed as immutable, it should not attempt to mutate it. It is not an acceptable programming practice to determine if an object is mutable based on its class membership—for example:

if ( [anArray isKindOfClass:[NSMutableArray class]] ) {

    // add, remove objects from anArray

}

For reasons related to implementation, what isKindOfClass: returns in this case may not be accurate. But for reasons other than this, you should not make assumptions about whether an object is mutable based on class membership. Your decision should be guided solely by what the signature of the method vending the object says about its mutability. If you are not sure whether an object is mutable or immutable, assume it’s immutable.

A couple of examples might help clarify why this guideline is important:

  • You read a property list from a file. When the Foundation framework processes the list, it notices that various subsets of the property list are identical, so it creates a set of objects that it shares among all those subsets. Afterward you look at the created property list objects and decide to mutate one subset. Suddenly, and without being aware of it, you’ve changed the tree in multiple places.

  • You ask NSView for its subviews (with the subviews method) and it returns an object that is declared to be an NSArray but which could be an NSMutableArray internally. Then you pass that array to some other code that, through introspection, determines it to be mutable and changes it. By changing this array, the code is mutating internal data structures of the NSView class.

So don’t make an assumption about object mutability based on what introspection tells you about an object. Treat objects as mutable or not based on what you are handed at the API boundaries (that is, based on the return type). If you need to unambiguously mark an object as mutable or immutable when you pass it to clients, pass that information as a flag along with the object.


As others have mentioned, -isMemberOfClass: tests for being an instance of that exact class and not any subclass. For a class cluster, that will always return false, because the public class is abstract and will never have instances.

The other weird results are probably because you're using po (short for "print object") for non-object values. Use the p command for boolean expressions.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • 1
    +1 Good explanation. So cluster classes must not be checked with these methods. I edited my question with Apple's own docs remark - if it is anything other than confusing, I invite 1000 downvotes. – Nirav Bhatt Jan 02 '16 at 00:05
  • I have updated my answer with a link and quote of the relevant documentation, which is not what you were looking it. Note that the second example doesn't really have to do with class clusters or something which was apparently allocated as immutable being mutable. It's just that you're not allowed to mutate something that was given to you with an immutable static type. – Ken Thomases Jan 02 '16 at 00:59
  • Good point. However one thing that still remain unanswered is - I may not use reflection to decide on mutability but the object type - in this case an array. Can't I have a function returning id and then wanting to convert it back to array using reflection? Off course I could use NSArray methods + respondsToSelector to decide if its an array (at the very least, if mutable/not mutable) but it's still a hack - alternative to reflection. Apple needs to update their implementation of isMemberOfClass / isKindOfClass or simply disable the methods for clusters. – Nirav Bhatt Jan 02 '16 at 07:56
  • 1
    I don't understand what you're asking. First, if you're working with an API that gives you an `id`, you'd better have a design contract that tells you what you can do with that object. Or for, say, getting the elements of a collection, you better have a contract with with whatever provided that collection. That said, `-isKindOfClass:` works just fine to determine if an object is an `NSArray`. You just can't use it to determine what kind of array more specifically than that. – Ken Thomases Jan 02 '16 at 08:29
  • I just verified and isKindOfClass works to decide if its NSArray. Sending isMemberOfClass to NSArray instance keeps giving false, and it is still counterintuitive to think of NSArray as abstract when all the time I have alloc-initiated it with plain NSArray class specifier. – Nirav Bhatt Jan 02 '16 at 10:01
  • 1
    In general, you will almost never have a good reason to use `-isMemberOfClass:`. I don't know that I ever have. Regarding it being counterintuitive, you need to remember that `-init` is a message to invoke a method and you're using its return value. There's no guarantee that the return value is the object you sent the message to. Just like any other method that returns an object. – Ken Thomases Jan 02 '16 at 10:46
  • But it still is counter-intuitive to think init is initializing instance of a different class. – Nirav Bhatt Jan 02 '16 at 10:51
  • @NiravBhatt In general, you should **not use introspection of any kind** unless it cannot be avoided. Even "is this an array?" leads to dangerously fragile and difficult to maintain code. `isKindOfClass:`/`isMemberOfClass:` are, effectively, runtime generic functionality that is interrogating implementation details that, as the documentation implies, are likely to not only be irrelevant to your app, but may be downright misleading. – bbum Jan 03 '16 at 18:59
2

tl;dr Don't use introspection in your code to determine mutability or to vary behavior (outside of extremely limited situations). Do use static types and strongly defined data structures (including strongly defined plist structures).

The introspection functionality offered by the Objective-C runtime, of which the introspection methods on NSObject are implemented against, are neither misleading nor are they returning incorrect results.

They are revealing specific details of how the various objects are implemented. This may appear quite different from what is declared in the header files.

Or, to put it another way: compile time vs. run time may be very different.

That is both correct and OK. And confusing.

Objective-C implements a strong notion of duck typing. That is, if you have a reference to something declared as NSString*, it really doesn't matter what that reference really points to as long is it responds to the API contract declared for the class NSString.

The confusion comes from trying to treat Objective-C as a fully dynamic, introspection driven, language. It wasn't designed for that (well, it kinda was, but that notion was dropped by about 1990) and, over time, strong typing has become more and more the norm. I.e. let the compiler figure out if something is valid and don't try to second guess at runtime.

bbum
  • 162,346
  • 23
  • 271
  • 359