Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Backwards compatible macros for Objective-C nullability annotations and generics
/**
* The following preprocessor macros can be used to adopt the new nullability annotations and generics
* features available in Xcode 7, while maintaining backwards compatibility with earlier versions of
* Xcode that do not support these features.
*/
#if __has_feature(nullability)
# define __ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
# define __ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
# define __NULLABLE nullable
#else
# define __ASSUME_NONNULL_BEGIN
# define __ASSUME_NONNULL_END
# define __NULLABLE
#endif
#if __has_feature(objc_generics)
# define __GENERICS(class, ...) class<__VA_ARGS__>
# define __GENERICS_TYPE(type) type
#else
# define __GENERICS(class, ...) class
# define __GENERICS_TYPE(type) id
#endif
/**
* Here are some examples of the above macros being used.
* The comments below each line represent the output of the preprocessor (e.g. what the compiler will see)
* when using both Xcode 7 and Xcode 6.
*/
#import "Xcode7Macros.h"
__ASSUME_NONNULL_BEGIN
// Xcode 7: NS_ASSUME_NONNULL_BEGIN
// Xcode 6:
@interface __GENERICS(MyClass, GenericType1, GenericType2) : NSObject
// Xcode 7: @interface MyClass<GenericType1, GenericType2> : NSObject
// Xcode 6: @interface MyClass : NSObject
@property (nonatomic, strong) __GENERICS(NSArray, __GENERICS(NSDictionary, GenericType1, GenericType2) *) *arrayOfDictionaries;
// Xcode 7: @property (nonatomic, strong) NSArray<NSDictionary<GenericType1, GenericType2> *> *arrayOfDictionaries;
// Xcode 6: @property (nonatomic, strong) NSArray *arrayOfDictionaries;
- (__NULLABLE __GENERICS_TYPE(GenericType2))someMethodWithAParameter:(__NULLABLE NSString *)param;
// Xcode 7: - (nullable GenericType2)someMethodWithAParameter:(nullable NSString *)param;
// Xcode 6: - (id)someMethodWithAParameter:(NSString *)param;
@end
__ASSUME_NONNULL_END
// Xcode 7: NS_ASSUME_NONNULL_END
// Xcode 6:
@smileyborg

This comment has been minimized.

Copy link
Owner Author

commented Jun 13, 2015

Some notes:

  • You may want to add your own prefix to the names of these macros to avoid any namespace conflicts, e.g. __TF_NULLABLE
  • If you're using audited regions (NS_ASSUME_NONNULL_BEGIN and NS_ASSUME_NONNULL_END) you shouldn't need to use the __nonnull annotation, so it's not included in the above macros. However, if you find a need for it, it's trivial to add a macro (following the same pattern as __NULLABLE).
  • Yes, code that uses these macros isn't the most beautiful thing in the world, but it gets the job done!

If you're interested in seeing a more extensive example of how this looks and works in real code, see this commit to the INTUGroupedArray project on GitHub.

@bdash

This comment has been minimized.

Copy link

commented Jun 13, 2015

  • NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END are in the OS X 10.10.3 SDK that ships with Xcode 6.3, so you can benefit from them prior to Xcode 7's release.
  • It's preferable to use Clang's __has_feature tests to determine whether these new features can be used rather than inferring based on the SDK version being used. For instance, nullability can be detected using #if __has_feature(nullability), and generics with #if __has_feature(objc_generics).
@smileyborg

This comment has been minimized.

Copy link
Owner Author

commented Jun 13, 2015

@bdash Excellent points, the gist has been updated to reflect this!

@e28eta

This comment has been minimized.

Copy link

commented Jun 17, 2015

Why introduce your own symbols for assume nonnull and nullable? Why not simply provide a shim that defines the Apple symbols as noops if they're not available?

#if !(__has_feature(nullability))
#define NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_END
#define nullable
#endif

It doesn't solve for generics, but it makes your code forward compatible too, and readable by anyone familiar with the standard SDK.

@smileyborg

This comment has been minimized.

Copy link
Owner Author

commented Jun 17, 2015

@e28eta That is one approach, but as you mentioned that wouldn't work for the backwards-compatible generics. Aside from being consistent with both of these features, I think using new, unique macro names feels a little safer and less invasive than redefining Apple's existing macros. It's also very easy to find-replace these custom macros with the real counterparts once backwards compatibility is no longer a concern. Ultimately, it's up to you to decide which approach to use though!

@mickeyreiss

This comment has been minimized.

Copy link

commented Jun 26, 2015

@smileyborg I think this might still be missing a shim for __nullable, which is necessary in some declarations with block parameters:

- (void)doBlock:(void (^)(NSString __nullable* arg))completionBlock;

I ended up adapting your snippet like this:

#if __has_feature(nullability)
#   define BT_ASSUME_NONNULL_BEGIN      NS_ASSUME_NONNULL_BEGIN
#   define BT_ASSUME_NONNULL_END        NS_ASSUME_NONNULL_END
#   define BT_NULLABLE                  nullable
#   define __BT_NULLABLE                __nullable
#else
#   define BT_ASSUME_NONNULL_BEGIN
#   define BT_ASSUME_NONNULL_END
#   define BT_NULLABLE
#   define __BT_NULLABLE
#endif

:octocat:

@smileyborg

This comment has been minimized.

Copy link
Owner Author

commented Jun 26, 2015

@mickeyreiss Yes, __nullable is necessary for some C-like APIs, so defining another macro for it like you did may be required.
Also, the reason for the __ prefix in front of all these macros is to protect them a bit, since these are going to have to live in a public header that gets imported by clients of your library, but your clients probably don't want these macros showing up in the global (autocomplete) namespace.

@ricardopereira

This comment has been minimized.

Copy link

commented Oct 13, 2015

What about value type definitions for NSArray or key value types for NSDictionary that are only available for Xcode 7? Do you have any idea?

e.g.:

// Xcode 6
NSArray *authParams; //<-- will produce AnyObject

// Xcode 7
NSArray<NSURLQueryItem *> *authParams;
@ricardopereira

This comment has been minimized.

Copy link

commented Oct 13, 2015

Using __apple_build_version__ for the check? What do you think?

// http://en.wikipedia.org/wiki/Xcode#Toolchain_Versions
if defined(__apple_build_version__)
     if __apple_build_version__ >= 700059
@smileyborg

This comment has been minimized.

Copy link
Owner Author

commented Oct 13, 2015

@ricardopereira I'm not sure what you're asking here.

@ricardopereira

This comment has been minimized.

Copy link

commented Oct 15, 2015

I want to support NSArray *authParams and NSArray<NSURLQueryItem *> *authParams. Any thoughts? /cc @bdash @mickeyreiss

@smileyborg

This comment has been minimized.

Copy link
Owner Author

commented Oct 15, 2015

@ricardopereira and why doesn't this work for you?

__GENERICS(NSArray, NSURLQueryItem *) *authParams;
@ricardopereira

This comment has been minimized.

Copy link

commented Oct 15, 2015

Oh, sorry. Didn't know.
Thanks.

@colasbd

This comment has been minimized.

Copy link

commented Oct 20, 2015

Xcode inserts automatically code such as

saveOperation.modifyRecordsCompletionBlock = 
^(NSArray <CKRecord *> * _Nullable savedRecords,
  NSArray <CKRecordID *> * _Nullable deletedRecordIDs,
  NSError * _Nullable operationErro) {...};

which fails at being compiled with Xcode 6, because of NSArray <CKRecord *> *. Is it possible to help Xcode 6 compiling?

@smileyborg

This comment has been minimized.

Copy link
Owner Author

commented Oct 21, 2015

@colasjojo You can probably just omit (delete) the _Nullable and <CKRecord *>/<CKRecordID *> entirely and it should continue to compile fine on Xcode 7 with no change in behavior. If you want to preserve the type information then you should try converting the syntax to use the macros in this gist, something like:

saveOperation.modifyRecordsCompletionBlock = 
^(__GENERICS(NSArray, CKRecord *) * _Nullable savedRecords,
  __GENERICS(NSArray, CKRecordID *) * _Nullable deletedRecordIDs,
  NSError * _Nullable operationErro) {...};

(You would need to also define a new macro for _Nullable and substitute that if you're trying to support Xcode 6.2 and earlier.)

@samjarman

This comment has been minimized.

Copy link

commented Jan 6, 2016

@smileyborg I believe the macro breaks bridging into swift?

@smileyborg

This comment has been minimized.

Copy link
Owner Author

commented Jan 7, 2016

@samjarman They shouldn't, these are being used by projects that bridge to Swift. Are you seeing a specific issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.