Can we ensure nullability of `+ (nonnull instancetype)sharedInstance;`?

流过昼夜 提交于 2019-12-07 03:34:03

问题


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?


回答1:


There seem to be two questions here:

  1. 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?

  2. How do I satisfy the system of nullabilty annotations, compiler warnings, and Swift bridging that I'm returning a nonnull pointer?

Ensuring / enforcing 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.

Returning a nonnull nullable

It'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.



来源:https://stackoverflow.com/questions/34195149/can-we-ensure-nullability-of-nonnull-instancetypesharedinstance

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!