Skip to content

Instantly share code, notes, and snippets.

@robb
Last active Jan 19, 2021
Embed
What would you like to do?
A macro to convert nullable references to nonnull references while triggering an assert if the expression is actually true. Think of this as unsafe unwrap for Objective-C.
NS_ASSUME_NONNULL_BEGIN
void Log(NSString *foo) {
NSLog(@"%@", foo);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSDictionary *stuff = @{
@"a": @"Test"
};
// This will trigger a warning in Xcode 7 if
// `-Wnullable-to-nonnull-conversion` is enabled:
//
// Implicit conversion from nullable pointer 'id _Nullable' to non-
// nullable pointer type 'NSString * _Nonnull'.
Log(stuff[@"a"]);
/// This will not trigger a warning.
Log(RBBNotNil(stuff[@"a"]));
/// This will trigger the assert.
Log(RBBNotNil(stuff[@"b"]));
}
return 0;
}
NS_ASSUME_NONNULL_END
#if __has_feature(objc_generics)
/// An unimplemented class used to trick the compiler, since a cast along the
/// lines of
///
/// (__nonnull __typeof(bla))bla;
///
/// is not possible.
@interface RBBBox<__covariant Type>
- (nonnull Type)asNonNull;
@end
/// This macro allows us to cast a nullable reference to a non-nullable
/// reference that would otherwise trigger a warning if
/// `-Wnullable-to-nonnull-conversion` is enabled.
#define RBBNotNil(V) \
({ \
NSCAssert(V, @"Expected '%@' not to be nil.", @#V); \
RBBBox<__typeof(V)> *type; \
(__typeof(type.asNonNull))V; \
})
#else
/// If generics are unavailable, so is `-Wnullable-to-nonnull-conversion`.
#define RBBNotNil(V) \
({ \
NSCAssert(V, @"Expected '%@' not to be nil.", @#V); \
V; \
})
#endif
@humblehacker
Copy link

humblehacker commented Oct 29, 2015

Nice work. I'd like to borrow this code. If you're willing, could you add some kind of license declaration RBBNotNil.h?

@podkovyrin
Copy link

podkovyrin commented Sep 20, 2016

There is an issue with this macro in debug configuraiton. Argument V is calling twice in assert and right after it. Here is a possible solution:

#define RBBNotNil(V) \
    ({ \
        id rbb_nullable_object = V; \
        NSCAssert(rbb_nullable_object, @"Expected '%@' not to be nil.", @#V); \
        RBBBox<__typeof(V)> *type; \
        (__typeof(type.asNonNull))rbb_nullable_object; \
    })

#else

/// If generics are unavailable, so is `-Wnullable-to-nonnull-conversion`.
#define RBBNotNil(V) \
    ({ \
        id rbb_nullable_object = V; \
        NSCAssert(rbb_nullable_object, @"Expected '%@' not to be nil.", @#V); \
        rbb_nullable_object; \
    })

@hborders
Copy link

hborders commented Sep 8, 2017

This is really great!

I ran into some static analyzer warnings with in when archiving. I outlined details in a stack overflow answer.

// We purposefully don't have a matching @implementation.
// We don't want +asNonnull to ever actually be called
// because that will add a lot of overhead to every RBBNotNil
// and we want RBBNotNil to be very cheap.
// If there is no @implementation, then if the +asNonnull is
// actually called, we'll get a linker error complaining about
// the lack of @implementation.
@interface RBBBox <__covariant Type>

// This as a class method so you don't need to
// declare an unused lvalue just for a __typeof
+ (Type _Nonnull)asNonnull;

@end

/*!
 * @define RBBNotNil(V)
 * Converts an Objective-C object expression from _Nullable to _Nonnull. 
 * Crashes if it receives a nil! We must crash or else we'll receive
 * static analyzer warnings when archiving. I think in Release mode,
 * the compiler ignores the _Nonnull cast.
 * @param V a _Nullable Objective-C object expression
 */
#define RBBNotNil(V) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wgnu-statement-expression\"") \
({ \
__typeof__(V) __nullableV = V; \
NSCAssert(__nullableV, @"Expected '%@' not to be nil.", @#V); \
if (!__nullableV) { \
    abort(); \
} \
(__typeof([RBBNotNil<__typeof(V)> asNonnull]))__nullableV; \
}) \
_Pragma("clang diagnostic pop")

@CodingMarkus
Copy link

CodingMarkus commented Oct 23, 2020

#define assumeNotNull(_value) \
    ({ if (!_value) abort(); __auto_type const _temp = _value; _temp; })

use as

if (parameters) {
    [obj processParameters:assumeNotNull(parameters)];
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment