When compiling with -Wnullable-to-nonnull-conversion
, we get a proper warning with the following code:
NSString * _Nullable maybeFoo = @"foo";
^(NSString * _Nonnull bar) {
}(maybeFoo);
Tests.m:32:7: error: implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' [-Werror,-Wnullable-to-nonnull-conversion]
}(maybeFoo);
^
1 error generated.
How do I safely cast foo
from an NSString * _Nullable
to an NSString * _Nonnull
?
The best solution I have so far
The best I've come up with is this macro:
#define ForceUnwrap(type, nullableExpression) ^type _Nonnull () { \
type _Nullable maybeValue___ = nullableExpression; \
if (maybeValue___) { \
return (type _Nonnull) maybeValue___; \
} else { \
NSLog(@"Attempted to force unwrap a null: " #nullableExpression); \
abort(); \
} \
}()
Which is used like:
NSString * _Nullable maybeFoo = @"foo";
if (maybeFoo) {
NSString * _Nonnull foo = ForceUnwrap(NSString *, maybeFoo);
^(NSString * _Nonnull bar) {
}(foo);
}
And which produces an error if assigned to a wrongly-typed variable:
NSString * _Nullable maybeFoo = @"foo";
if (maybeFoo) {
NSNumber * _Nonnull foo = ForceUnwrap(NSString *, maybeFoo);
^(NSNumber * _Nonnull bar) {
}(foo);
}
Tests.m:40:29: error: incompatible pointer types initializing 'NSNumber * _Nonnull' with an expression of type 'NSString * _Nonnull' [-Werror,-Wincompatible-pointer-types]
NSNumber * _Nonnull foo = ForceUnwrap(NSString *, maybeFoo);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
And which produces an error if cast to the wrong type:
NSString * _Nullable maybeFoo = @"foo";
if (maybeFoo) {
NSNumber * _Nonnull foo = ForceUnwrap(NSNumber *, maybeFoo);
^(NSNumber * _Nonnull bar) {
}(foo);
}
Tests.m:40:35: error: incompatible pointer types initializing 'NSNumber * _Nullable' with an expression of type 'NSString * _Nullable' [-Werror,-Wincompatible-pointer-types]
NSNumber * _Nonnull foo = ForceUnwrap(NSNumber *, maybeFoo);
^ ~~~~~~~~
Tests.m:27:16: note: expanded from macro 'ForceUnwrap'
type _Nullable maybeValue___ = nullableExpression; \
^ ~~~~~~~~~~~~~~~~~~
1 error generated.
Unfortunately, if you need to cast to a generic type with multiple arguments, you have to resort to preprocessor hacks:
NSDictionary<NSString *, NSString *> * _Nullable maybeFoo =
[NSDictionary<NSString *, NSString *> new];
if (maybeFoo) {
NSDictionary<NSString *, NSString *> * _Nonnull foo =
#define COMMA ,
ForceUnwrap(NSDictionary<NSString * COMMMA NSString *>, maybeFoo);
#undef COMMA
^(NSDictionary<NSString *, NSString *> * _Nonnull bar) {
}(foo);
}
Things I've tried that don't work
Assigning maybeFoo
directly to an NSString * _Nonnull
doesn't work. It produces the same error as before:
NSString * _Nullable maybeFoo = @"foo";
if (maybeFoo) {
NSString * _Nonnull foo = maybeFoo;
^(NSString * _Nonnull bar) {
}(foo);
}
Tests.m:30:35: error: implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' [-Werror,-Wnullable-to-nonnull-conversion]
NSString * _Nonnull foo = maybeFoo;
^
1 error generated.
And casting to maybeFoo
to NSString * _Nonnull
isn't safe because if maybeFoo
's type changes, the compiler won't break:
NSNumber * _Nullable maybeFoo = @"foo";
if (maybeFoo) {
NSString * _Nonnull foo = (NSString * _Nonnull) maybeFoo;
^(NSString * _Nonnull bar) {
}(foo);
}
// no errors!
I also tried using __typeof__
, when casting, but __typeof__
carries the nullability specifier, so when you try to cast to __typeof__(maybeFoo) _Nonnull
you get a nullability conflict:
NSString * _Nullable maybeFoo = @"foo";
if (maybeFoo) {
NSString * _Nonnull foo = (__typeof__(maybeFoo) _Nonnull) maybeFoo;
^(NSString * _Nonnull bar) {
}(foo);
}
Tests.m:30:57: error: nullability specifier '_Nonnull' conflicts with existing specifier '_Nullable'
NSString * _Nonnull foo = (__typeof__(maybeFoo) _Nonnull) maybeFoo;
^
Tests.m:30:35: error: implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' [-Werror,-Wnullable-to-nonnull-conversion]
NSString * _Nonnull foo = (__typeof__(maybeFoo) _Nonnull) maybeFoo;
^
2 errors generated.
Everything was run with the deep static analyzer and compiled with Xcode 8.2.1
with the following flags:
-Wnon-modular-include-in-framework-module
-Werror=non-modular-include-in-framework-module
-Wno-trigraphs
-Werror
-Wno-missing-field-initializers
-Wno-missing-prototypes
-Wunreachable-code
-Wno-implicit-atomic-properties
-Wno-arc-repeated-use-of-weak
-Wduplicate-method-match
-Wno-missing-braces
-Wparentheses
-Wswitch
-Wunused-function
-Wno-unused-label
-Wno-unused-parameter
-Wunused-variable
-Wunused-value
-Wempty-body
-Wuninitialized
-Wno-unknown-pragmas
-Wno-shadow
-Wno-four-char-constants
-Wno-conversion
-Wconstant-conversion
-Wint-conversion
-Wbool-conversion
-Wenum-conversion
-Wshorten-64-to-32
-Wpointer-sign
-Wno-newline-eof
-Wno-selector
-Wno-strict-selector-match
-Wundeclared-selector
-Wno-deprecated-implementations
-Wno-sign-conversion
-Wno-infinite-recursion
-Weverything
-Wno-auto-import
-Wno-objc-missing-property-synthesis
-Wno-cstring-format-directive
-Wno-direct-ivar-access
-Wno-double-promotion
In Objective-C, any character , numeric or boolean literal prefixed with the '@' character will evaluate to a pointer to an NSNumber object (In this case), initialized with that value. C's type suffixes may be used to control the size of numeric literals. '@' is used a lot in the objective-C world.
Annotate Nullability of Individual Declarations You can use nullability annotations in your Objective-C code to designate whether a parameter type, property type, or return type is nullable.
if (title == (id)[NSNull null] || title.
“id” is a data type of object identifiers in Objective-C, which can be use for an object of any type no matter what class does it have. “id” is the final supertype of all objects.
I use this macro:
#define assumeNotNull(_value) \
({ if (!_value) abort(); __auto_type const _temp = _value; _temp; })
Of course, only after an appropriate test in code:
if (parameters) {
[obj processParameters:assumeNotNull(parameters)];
}
Leaving out the macro the compiler would tell me that parameters might be NULL
but processParameters
requires a non-NULL argument. In my case that is even configured to be an error, not just a warning.
Leaving out the if
check, the code will compile but if I ever feed in NULL
, the application will crash on spot. So one should only use the macro after a test or if one is absolutely sure that the value cannot be NULL
for some reason and you are so sure about that, that you are willing to bet your app stability on it.
If in doubt, always test and keep in mind, that if a test is clearly unnecessary (e.g. the condition was tested before and the code will never be reached if the value was NULL
), the compiler will detect that during the optimization phase and remove the test for you. Unnecessary testing is hardly ever a performance problem, especially not with a test that cheap.
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