-
-
Save smileyborg/d513754bc1cf41678054 to your computer and use it in GitHub Desktop.
/** | |
* 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: |
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)
.
@bdash Excellent points, the gist has been updated to reflect this!
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.
@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!
@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
@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.
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;
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
@ricardopereira I'm not sure what you're asking here.
I want to support NSArray *authParams
and NSArray<NSURLQueryItem *> *authParams
. Any thoughts? /cc @bdash @mickeyreiss
@ricardopereira and why doesn't this work for you?
__GENERICS(NSArray, NSURLQueryItem *) *authParams;
Oh, sorry. Didn't know.
Thanks.
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?
@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.)
@smileyborg I believe the macro breaks bridging into swift?
@samjarman They shouldn't, these are being used by projects that bridge to Swift. Are you seeing a specific issue?
Very cool, do you have a license for your code that we can use?
Thank you.
Some notes:
__TF_NULLABLE
NS_ASSUME_NONNULL_BEGIN
andNS_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
).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.