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…
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.
An object representing a static ordered collection, for use instead of an Array constant in cases that require reference semantics.
arrays can't contain nil.
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.
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With