1

I'm trying to "polyfill" NSManagedObjects init(context:) method for below iOS9. Is there a way to do an preprocessor availability check for below iOS10?

Does this even make sense or would there be a linking collision issue b/c we don't know what iOS version a user will be running?

Note this is not the same is the @available(iOS 8, *) macros. I want the opposite: if NOT available. Or more accurately I want something like @available(iOS <10.0, *), "available if iOS is less than 10.0"

Hari Honor
  • 8,677
  • 8
  • 51
  • 54

3 Answers3

1

Try this syntax to show a warning:

@available(iOS, introduced: 8.0, deprecated: 10.0)

Or this one to prevent using it in the current iOS target:

@available(iOS, introduced: 8.0, obsoleted: 10.0)

You may even provide a custom message:

@available(iOS, introduced: 8.0, obsoleted: 10.0, message: "This is obsoleted")
ielyamani
  • 17,807
  • 10
  • 55
  • 90
1

I don't think that this is possible just relying on compile-time operations in Swift. If you care, by writing an extension in Objective-C that defines the method, your implementation will override the platform's when it exists, so that may be a viable and simple solution.

If you only want your implementation to kick in only if there is no native one, you should be able to do that somewhat easily in Objective-C, too. First, I have two warnings:

  1. You are modifying CoreData, which itself is already quite dynamic, and I'm not sure how it will react.
  2. I can't verify any of what I'm saying on this computer, so comment if it doesn't work :)

In your bridging header, ensure that the compiler knows that init(context:) is available regardless of the iOS version:

@interface NSManagedObject ()
+(void)initialize;
-(instancetype)initWithContext:(NSManagedObjectContext* _Nonnull)ctx;
@end

+initialize methods declared in categories are executed independently of whether the class has a +initialize method itself or if any other category has one.

The implementation will then look like this:

@implementation NSManagedObject ()

static id initWithContext(NSManagedObject* self, SEL sel, NSManagedObjectContext* context) {
    // your implementation goes here
}

+(void)initialize {
    SEL init = @selector(initWithContext:);
    if (![self instancesRespondToSelector:init]) {
        class_addMethod(self, init, (IMP)initWithContext, "@:@");
    }
}

@end

Which is rather straightforward in my opinion: check if NSManagedObject supports initWithContext:, and if not, add your own implementation of it. You don't need to provide an explicit implementation of initWithContext:.

zneak
  • 134,922
  • 42
  • 253
  • 328
  • Thanks for the detailed answer. I think you are right that Objc is the only way. But then might it be better to use one of those constants named something like `#if !IPHONE_IOS_10` - can't remember offhand exactly...? – Hari Honor Oct 09 '18 at 12:09
  • It probably doesn’t change anything to the end result. – zneak Oct 09 '18 at 15:50
  • Actually, it *does* change something: the preprocessor constant will ensure that it always either happens or never happens, without a runtime check, so it's probably better to leave it as a runtime check. – zneak Oct 11 '18 at 19:58
0

Is this what you are looking for?

@available(iOS, deprecated: 10.0)

Though, it's called Attributes in Swift. No preprocessor.

OOPer
  • 47,149
  • 6
  • 107
  • 142
  • I tried that and `obsolete` as well. It still occupies the namespace and collides with the method name in iOS10. Thanks for keyword tip though! – Hari Honor Oct 04 '18 at 14:56