Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Objective C, why am I allowed to assign an NSArray to an NSMutableArray without error or warning?

I'm disturbed by a weird behavior, illustrated by the following example:

NSMutableArray *a1 = [[NSMutableArray alloc] init]; // fine
NSMutableArray *a2 = [NSMutableArray array];        // fine, too

// compiler reports incompatible pointer types; good:
NSMutableArray *a3 = [[NSArray alloc] init]; 

// compiler says nothing and is happy to assign this!
NSMutableArray *a4 = [NSArray array]; 

Both init and array method of both the NSArray and NSMutableArray classes return id. However, the behavior when I call these methods is simply not the same, and clang lets me happily assign an empty NSArray to an NSMutableArray variable!

It turns out that clang will automatically change the return type of some methods, including the init family, to instancetype, and thus be able to determine at compile time that [[NSArray alloc] init] returns an NSArray * and not an NSMutableArray *. But this check simply doesn't work with the array method.

Why? Shouldn't lines like my last example generate at least a warning? Why aren't all these methods declared as returning instancetype? Will it change in the future?


Update

Good news: as of iOS 7, [NSArray array] returns instancetype, so the assignment to a4 above also yields a warning. Other methods like arrayWithContentsOfFile: or arrayWithContentsOfURL still return id, though…

like image 720
Jean-Philippe Pellet Avatar asked Mar 06 '13 13:03

Jean-Philippe Pellet


People also ask

What is NSArray Objective C?

The NSArray class contains a number of methods specifically designed to ease the creation and manipulation of arrays within Objective-C programs. Unlike some object oriented programming languages (C# being one example), the objects contained in an array do not all have to be of the same type.

What is an NSArray?

An object representing a static ordered collection, for use instead of an Array constant in cases that require reference semantics.

Can NSArray contain nil?

arrays can't contain nil.


2 Answers

But this check simply doesn't work with the array method. Why?

As the document you have linked describes, it is because -array does not yield a recognized Related Result Type. ObjC is very dynamic -- the compiler cannot guarantee the result type of +array. It does make that assumption with some methods because the naming conventions are well defined (e.g. +alloc, -init, +new, -self, etc.). So this implementation simply resorts to naming conventions.

The compiler also validates some naming conventions in areas you may not expect:

@implementation NSArray (DEMO)

- (id)initStr
{
    return [NSString new]; // << warning. RE: init prefix
}

@end

Shouldn't lines like my last example generate at least a warning? Why aren't all these methods declared as returning instancetype? Will it change in the future?

instancetype was introduced about one year ago (from the looks of it). Some of the APIs were written decades ago. I suspect it will happen -- in time -- because (if used correctly) it can point out a lot of issues in existing code. Of course, those changes would break existing builds (again, typically good corrections if declared in the right places).

So file bugs and give the tools and libraries a few years to update. Assuming the changes are made, it will probably happen at a major OS update.

It would probably be best if it were enabled as an optional warning for some time (in the case of the system headers). Of course, they could still employ it with backwards compatibility for older compilers for new APIs.

Also, this change could be retrofitted quite easily (not that earlier compilers would make sense of the semantic difference between id and instancetype) by a simple typedef. One problem with a typedef is that it is a global declaration -- a compiler could restrict a word/modifier/attribute to a given scope, without causing all the pain of simulating a keyword by adding a global typedef. Apple's GCC may never support instancetype, so the logical way to introduce it for Apple's GCC may be a global typedef of id, which could cause problems for some people (with no semantic benefit, if that route were taken). Note that similar breaking changes have been made by Apple in the past.

like image 155
justin Avatar answered Oct 13 '22 05:10

justin


As it turns out, you're not just allowed to use the wrong array type, you're allowed to use the wrong type of any object with a convenience initializer that returns id. For example, this compiles without a warning in sight:

NSMutableArray *a4 = [NSDictionary dictionary]; 

This is a side effect of using id to opt out of type safety, and as you note, it should be deprecated behavior and replaced with instancetype (which does throw an incompatible type warning when used in the manner above).

Unfortunately, it's not a bug. instancetype being a fairly new keyword, it's adoption is not widespread yet, and it would be a bold move to start using it throughout Apple's frameworks. You never know, there's always hope for the next SDK!

like image 45
CodaFi Avatar answered Oct 13 '22 07:10

CodaFi