Reputation: 6032
I'm developing an API using Objective-C, this API has protocol with some fictional method:
- (NSString *)gimmeString; // Want implementations to never return nil
I'm a big fan of providing context, so I heavily use everything for that purpose including attributes like __attribute__((nonnull))
and friends. What I'm asking is if there a way to provide context and possibly add a compile time check for method implementation saying "this method never returns nil" when compiled with clang?
i.e. I'd love to have something like:
@protocol MyProtocol
- (NSString *)gimmeString __attribute__((no_I_never_really_really_return_that_weird_nil));
@end
@implementation MyProtocolAdopter
- (NSString *)gimmeString
{
return nil; // WARNING! You're returning nil, YOU PROMISED!
}
@end
instead of just:
@protocol MyProtocol
// This method should never return nil
- (NSString *)gimmeString;
@end
@implementation MyProtocolAdopter
- (NSString *)gimmeString
{
// muvahaha, I lied!
return nil;
}
@end
I understand it is impossible to fully determine that at compile time, but detecting return nil;
or functions which for sure evaluate to nil
is fine.
Idea with something like __attribute__((objc_method_family(copy)))
seems weird and unacceptable, but I didn't manage to find anything better then just adding a comment leaving my API users in a bit scarier and more unreliable world.
Upvotes: 4
Views: 397
Reputation: 6032
Since XCode 6.3 you, actually, can annotate pointers to be nullable
and nonnull
and get compile time checks.
- (nonnull NSString *)gimmeString;
After some research, which included digging into clang and gcc docs I found that there's no way to achieve what I want in Objective-C.
I believe that's because there's no way to determine that at compile time at some sophisticated level of quality. You can't tell for sure if method always returns not-nil at compile time. And determining if method could
return nil is something shaky, guess, it's possible to evaluate all
return %something_which_evaluates_to_nil_at_compile_time%;
and warn on all methods which depend on that, but, for example, you can't be sure that some -init
method does not always return nil or some web request get's you a request without additional context provided everywhere and you'll end up with false-negatives.
Even -dequeueReusableCellWithIdentifier:forIndexPath:
from UITableView
which guaranties to always return valid cell is defined as just:
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier
forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);
So, what I've came up in Objective-C is providing context without compile-time checks. I add comments like that:
@protocol MyProtocol
/**
* Creates and returns a string.
*
* @note Implementations should never return nil.
*
* @return A string, guarantied not to be nil.
*/
- (NSString *)gimmeString;
@end
Also one more thing could be done to make this more readable, we can define an empty define and append it to the end of declaration, like this:
// Marks method to never return nil
#define MD_RETURNS_NONNULL
@protocol MyProtocol
/**
* Creates and returns a string.
*
* @note Implementations should never return nil.
*
* @return A string, guarantied not to be nil.
*/
- (NSString *)gimmeString MD_RETURNS_NONNULL;
@end
So eye catches this line making your code more readable, making your code users happier because they understand what you want to emphasise easier.
And the last but not the least is suggestion to move to Swift. In Swift this is possible and built in, you just define your method to return not an optional type:
protocol MyProtocol {
func gimmeString -> String
}
And you're good to go.
If you really want checks like this in Objective-C there's no magic, you only do it at the run time, at the same time you could provide context, which is great. But oh, wait, there's also Swift option, we'll anyway migrate to it at some time, so take your time and spend some of it learning this great new language. This need is a great example where Swift's safety features are really suitable.
UPD: Played with OCLint a bit, looks like something like this could be achieved using it (or just writing a custom Clang extension).
Upvotes: 3