This is a question on how to gracefully circumvent the nullability of init
in NSObject
class.
So here is a classic objective-c implementation:
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
static id sharedInstance;
dispatch_once(&onceToken, ^
{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
But now I want to declare it as nonnull
, if possible:
+ (nonnull instancetype)sharedInstance;
Unfortunately, init
returns a nullable instancetype
value. Should I add an NSAssert
or something after calling init
?
I noticed that some people even document nonnull
values as being in reality nullable
. Does that make sense?
Should I go bold and simply add NS_ASSUME_NONNULL_BEGIN
everywhere, without truly ensuring values are nonnull
?
There seem to be two questions here:
How do I ensure that the value produced within my singleton sharedInstance
method and returned by that method is actually not nil at run time?
How do I satisfy the system of nullabilty annotations, compiler warnings, and Swift bridging that I'm returning a nonnull
pointer?
nonnull
At some point, every API contract breaks down to a human contract. The compiler can help ensure that, for example, you can't take the result of a nullable
call and return it from a method whose return type is nonnull
... but somewhere there's usually an original call whose return type is nonnull
simply because the programmer who wrote it said, "I promise to never return null, cross my heart, etc."
If you're familiar with Swift, this is similar to the situation with Implicitly Unwrapped Optionals — you use these when you "know" that a value cannot be nil, but can't prove that knowledge to the compiler because that knowledge is external to the source code (something from a storyboard or bundle resource, for example).
That's the situation here — you "know" that init
will never return nil, either because you wrote / have source for the initializer in question or because it's just NSObject
's init
which is documented to return self
without doing anything. The only situation where a call to that initializer will fail is because the preceding alloc
call failed (and hence you're calling a method on nil
, which always returns nil
). If alloc
is returning nil, you're already in dire straits and your process is not long for this world — this isn't a failure case to be designing API around.
(Nullability annotations are in general for describing intended use of an API, not the more extreme edge and corner cases. If an API call fails only because of a universal error it's not meaningful to annotate it as nullable; likewise, if an API fails only on input that can be ruled out via nonnull parameter annotations, the return value can be assumed nonnull.)
So, long story short: yes, just put NS_ASSUME_NONNULL
around your header, and ship your sharedInstance
implementation as is.
nonnull
nullableIt's not the case here, but suppose you have a value that's annotated as nullable
, but you know (or "know") that it can never be nil and want to return it from your nonnull
-annotated method. And you're in a situation where you get a compiler warning for trying.
There's a syntax for that — just cast the value to the expected return type, annotations and all:
return (NSWhatever *_Nonnull)whatever;
In your case this shouldn't be needed — because you're dealing with the id
and instancetype
special types the compiler is more forgiving about nullability conversion and probably won't warn to begin with.
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