Cannot add #if compiler(>=5.0) in `AppDelegate.m` file: - ios

Trying to add a block in the AppDelegate.m file
#if compiler(>=5.0)
if (#available(iOS 13.0, *)) {
// do something...
}
#endif
The error showed is:
Function-like macro 'compiler' is not defined

The following is a Swift compile-time directive:
#if compiler(>=5.0)
...
#endif
It makes no sense to use this in an Objective-C file, AppDelegate.m. In an Objective-C source, it will result in the “Function-like macro 'compiler' is not defined” error.
So, if you want a runtime check for iOS 13, just remove the #if, e.g.:
if (#available(iOS 13.0, *)) {
...
}
See Marking API Availability in Objective-C.
A couple of observations (given that you tagged this with swift):
In Swift, the compile-time pattern is:
#if swift(>=5.0)
...
#endif
Or
#if compiler(>=5.0)
...
#endif
This pattern is generally only used when writing third-party libraries where you need to offer backward support to developers who are compiling your library with earlier versions of Swift.
See The Swift Programming Language: Compiler Control Statements
The following is an Objective-C runtime check for OS version:
if (#available(iOS 13.0, *)) {
// do something...
}
The equivalent Swift pattern is:
if #available(iOS 13.0, *) {
...
}
These are useful when writing code that is targeting multiple iOS versions, but you want to conditionally run code using iOS 13+ API.
To make it confusing, Swift does have an #available construct, but it is for marking a class, extension, or method as being available only for devices running a particular OS version, but is not used in conjunction with an if test. E.g. let’s say you are defining some function that should only be available to iOS 13 and later:
#available(iOS 13.0, *)
func someMethod() {
...
}
This is compile-time construct is to tell the compiler to not complain about iOS 13 API within this function if your app happens to support earlier iOS versions. You are effectively telling the compiler “hey, I know this has code inside it that only works with iOS 13 and later, but don’t worry, because I’ll only call it when the device in question is running iOS 13 or later.”

There are many things wrong that you did in your code example:
#if is a preprocessor macro. You can only use it outside the scope of anything, just like the import statement. If you are using it the way it should be used, then you can't use that if statement there.
#available cannot be used inside of an if statement. It can only be used to represent if a particular class/struct/func is available for said OS version and up, but not fragments of code. For that, use this instead:
if #available(iOS 13.0, *) {
// Do something.
}

If you want to check the Swift compiler version specifically, you can use #if swift. To check for iOS version during runtime, you need to use #available, the # version if used to mark types/functions only available on said versions.
#if swift(>=5.0)
if #available(iOS 13,*) {
//do something
}
#endif

Related

Swift and Objc interoperability - availability not working only in objc

While here Apple states that the Swift available flag should be applied also in objc, it's not working for me. What am I doing wrong?
I've got following declarations in Swift files:
#objc protocol Readable: AnyObject {...}
#available(iOS 10.3, *)
#objc class Reader: NSObject, Readable {...}
So let's check if it produces an error when I try to initialize it in pre-ios-10 project without version check. If I write following code in Swift:
let tmp = Reader()
it returns an error:
'Reader' is only available on iOS 10.3 or newer
What is expected.
However if I write following code in objc:
// if (#available(iOS 10.3, *)) { // commeted out to test if it succeeds without version check
Reader *tmp = [[Reader alloc] init];
// }
The build is finished without any error, while I'd expect the same error as in Swift.
I've tried to mark the class with:
#available(iOS 11, *)
#available(iOS, introduced: 10.3)
Neither of these works (produces an error) in objc. Any help, please?
Objective-C has had __attribute__((availability)) for longer than it has had #available. To make it work, the Objective-C compiler weakly links symbols that are not available on the deployment target. This means that compiling always succeeds, and starting your app succeeds, but the symbol will be NULL at runtime if it is not available.
Depending on what it is, you'll get more or less graceful degradation when you try to use it:
calling a weakly-linked function that is missing is going to crash
reading or writing to a global variable that is missing is going to crash
using a class that is missing will be a no-op and all methods are going to return zero
The old way of testing whether a symbol is found at runtime is just to compare it to NULL:
NS_AVAILABLE_MAC(...) #interface Foo #end
int bar NS_AVAILABLE_MAC(...);
int baz(int frob) NS_AVAILABLE_MAC(...);
if ([Foo class]) { /* Foo is available */ }
if (&bar) { /* bar is available */ }
if (&baz) { /* baz is available */ }
In your case:
Reader *tmp = [[Reader alloc] init];
tmp will be nil, because that would be the same as [[nil alloc] init].
The #available directive has only relatively recently been added to Objective-C. It's now possible to use #available in Objective-C in the same way that you use #available in Swift. However, to preserve backwards compatibility, it possibly never will be a compile-time error (at default error levels) to try to use a symbol that might not be available on the deployment target in Objective-C.

iOS 11.1 feature in Xcode 9.0

(This is based on an issue here: https://github.com/dokun1/Lumina/issues/44)
Consider the following function:
fileprivate var discoverySession: AVCaptureDevice.DiscoverySession? {
var deviceTypes = [AVCaptureDevice.DeviceType]()
deviceTypes.append(.builtInWideAngleCamera)
if #available(iOS 10.2, *) {
deviceTypes.append(.builtInDualCamera)
}
if #available(iOS 11.1, *), self.captureDepthData == true {
deviceTypes.append(.builtInTrueDepthCamera)
}
return AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified)
}
I am running Xcode 9.0. I want to run a framework that makes use of this feature in iOS 11.1, which is only available in Xcode 9.1. The code in this function that gives an error is:
if #available(iOS 11.1, *), self.captureDepthData == true {
deviceTypes.append(.builtInTrueDepthCamera)
}
When running on Xcode 9.1 on someone else's machine, this works fine, and the application developing with this framework can set a development target of 10.0, and it compiles fine. However, I can't even build the framework on my machine. The error I get reads Type 'AVCaptureDevice.DeviceType' has no member 'builtInTrueDepthCamera' in Xcode 9.0 I thought using the #available macro would fix this, but it doesn't work that well.
I've also tried to use this:
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 111000
if #available(iOS 11.1, *), self.captureDepthData == true {
deviceTypes.append(.builtInTrueDepthCamera)
}
#endif
But this causes an error reading: Expected '&&' or '||' expression
Anyone know what to do?
#available will raise the "SDK Level" so that the compiler will allow you to use API calls above your Deployment target, but it won't prevent the compiler from compiling the lines inside the #available scope.
You need to prevent the compiler from compiling those lines because the compiler doesn't have a definition for .builtInTrueDepthCamera. You can do that using the #if build configuration statement.
In this case you want to check for swift version 4.0.2. Xcode 9.1 shipped with Swift 4.0.2.
#if swift(>=4.0.2)
if #available(iOS 11.1, *), self.captureDepthData == true {
deviceTypes.append(.builtInTrueDepthCamera)
}
#endif
source: https://www.bignerdranch.com/blog/hi-im-available/#what-it-is-not

#available - make only available on OS X

Is it possible to mark a function/computed variable as available on OS X and unavailable on iOS (and vice versa)? Something such as #available(OSX 10.0, iOS unavailable).
I know about the #if OSX statement, however, I'm trying to achieve something that Apple has done for the NSURLThumbnailKey - it's marked as NS_AVAILABLE_MAC(10_10); in ObjC and the compiler will complain that the symbol is unavailable rather than that the compiler cannot resolve the symbol.
Also, I've found that the #if statements in Swift are buggy, for example, defining func myFunc(_, arg1:) and func myFunc(_, somethingElse:) within the #if scope will result in an error saying that myFunc has already been defined. Which is why I'm trying to avoid using #if.
You should be able to do something like:
#available(OS X 10.10, *)
To force it for OS X 10.10 higher only, or:
if #available(OS X 10.10, *)
There's some more information available on Hacking With Swift.
Multiple specification:
#available(iOS, unavailable, message="nope")
#available(OS X 10.10, *)
func someFunc()

Don't compile iOS in code for OSX on a cross-platform iOS/OSX module

I'm building a module that will be used in a iOS/OSX app.
The client is insisting that the module work on both iOS and OSX.
I need to check the system version which I'm doing with UIDevice on iOS and on OSX I'm using FTWDevice.
The problem is that when I try to compile it on OSX it complains that OSX doesn't support UIDevice.
I'm wondering if there is a way I can tell the compiler to compile a line of code on iOS but not on OSX?
I know I can do this with this to compile something on production only:
#ifndef DEBUG
[Crashlytics startWithAPIKey:CRASHLYTICSKEY];
#endif
Is there a solution for this or should I tell the client they're gonna need 2 modules that are exactly the same except for one line?
Another acceptable solution would be a workaround for finding the system version on iOS that doesn't involve using UIDevice.
Look at TargetConditionals.h. Specifically:
#if TARGET_OS_MAC
// Mac-only code here
#endif
#if TARGET_OS_IPHONE
// iOS-only code here
#endif
Another acceptable solution would be a workaround for finding the
system version on iOS that doesn't involve using UIDevice.
You can do as follow to obtain system version (OS X and iOS):
func majorVersion() -> Int { return NSProcessInfo.processInfo().operatingSystemVersion.majorVersion }
func minorVersion() -> Int { return NSProcessInfo.processInfo().operatingSystemVersion.minorVersion }
func patchVersion() -> Int { return NSProcessInfo.processInfo().operatingSystemVersion.patchVersion }
func myOSVersion() -> String { return NSProcessInfo.processInfo().operatingSystemVersionString }

Swift: iOS Deployment Target Command Line Flag

How do I check the iOS deployment target in a Swift conditional compilation statement?
I've tried the following:
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
// some code here
#else
// other code here
#endif
But, the first expression causes the compile error:
Expected '&&' or '||' expression
TL;DR? > Go to 3. Solution
1. Preprocessing in Swift
According to Apple documentation on preprocessing directives:
The Swift compiler does not include a preprocessor. Instead, it takes
advantage of compile-time attributes, build configurations, and
language features to accomplish the same functionality. For this
reason, preprocessor directives are not imported in Swift.
That is why you have an error when trying to use __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 which is a C preprocessing directive. With swift you just can't use #if with operators such as <. All you can do is:
#if [build configuration]
or with conditionals:
#if [build configuration] && ![build configuration]
2. Conditional compiling
Again from the same documentation:
Build configurations include the literal true and false values,
command line flags, and the platform-testing functions listed in the
table below. You can specify command line flags using -D <#flag#>.
true and false: Won't help us
platform-testing functions: os(iOS) or arch(arm64) > won't help you, searched a bit, can't figure where they are defined. (in compiler itself maybe?)
command line flags: Here we go, that's the only option left that you can use...
3. Solution
Feels a bit like a workaround, but does the job:
Now for example, you can use #if iOSVersionMinRequired7 instead of __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0, assuming, of course that your target is iOS7.
That basically is the same than changing your iOS deployment target version in your project, just less convenient...
Of course you can to Multiple Build configurations with related schemes depending on your iOS versions targets.
Apple will surely improve this, maybe with some built in function like os()...
Tested in Swift 2.2
By saying Deployment Target you mean iOS version, or App Target? Below I'm providing the solution if you have multiple versions of the app (free app, payed app, ...), so that you use different App Targets.
You can set custom Build configurations:
1. go to your project / select your target / Build Settings / search for Custom Flags
2. for your chosen target set your custom flag using -D prefix (without white spaces), for both Debug and Release
3. do above steps for every target you have
To differentiate between targets you can do something like this:
var target: String {
var _target = ""
#if BANANA
_target = "Banana"
#elseif MELONA
_target = "Melona"
#else
_target = "Kiwi"
#endif
return _target
}
override func viewDidLoad() {
super.viewDidLoad()
print("Hello, this is target: \(target)"
}
I have come across this issue recently when building a library whose source code supports multiple iOS and macOS versions with different functionality.
My solution is using custom build flags in Xcode which derive their value from the actual deployment target:
TARGET_IOS_MAJOR = TARGET_IOS_MAJOR_$(IPHONEOS_DEPLOYMENT_TARGET:base)
TARGET_MACOS_MAJOR = TARGET_MACOS_MAJOR_$(MACOSX_DEPLOYMENT_TARGET:base)
Referring to those user defined settings in Others Swift Flags like:
OTHER_SWIFT_FLAGS = -D$(TARGET_MACOS_MAJOR) -D$(TARGET_IOS_MAJOR)
allows me to check for the actual major OS version in my Swift sources as follows:
#if os(macOS)
#if TARGET_MACOS_MAJOR_12
#warning("TARGET_MACOS_MAJOR_12")
#elseif TARGET_MACOS_MAJOR_11
// use custom implementation
#warning("TARGET_MACOS_MAJOR_11")
#endif
#elseif os(iOS)
#if TARGET_IOS_MAJOR_15
#warning("TARGET_IOS_MAJOR_15")
#elseif TARGET_IOS_MAJOR_14
#warning("TARGET_IOS_MAJOR_14")
#else
#warning("older iOS")
#endif
#endif
Currently I don't know if a similar approach would be possible in a SPM package. This is something I will try to do in a later phase.
You can't do it in a conditional compilation statement like that. "Complex macros" as Apple calls them are not supported in Swift. Generics and types do the same thing, in their mind, with better results. (Here's a link they published https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html#//apple_ref/doc/uid/TP40014216-CH8-XID_13)
Here's a function I came up with that accomplishes the same thing (and obviously just replace the string returns with whatever is useful for you like a boolean or just the option itself):
func checkVersion(ref : String) -> String {
let sys = UIDevice.currentDevice().systemVersion
switch sys.compare(ref, options: NSStringCompareOptions.NumericSearch, range: nil, locale: nil) {
case .OrderedAscending:
return ("\(ref) is greater than \(sys)")
case .OrderedDescending:
return ("\(ref) is less than \(sys)")
case .OrderedSame:
return ("\(ref) is the same as \(sys)")
}
}
// Usage
checkVersion("7.0") // Gives "7.0 is less than 8.0"
checkVersion("8.0") // Gives "8.0 is the same as 8.0"
checkVersion("8.2.5") // Gives "8.2.5 is greater than 8.0"
I know your question is been here for a while but just in case someone's still looking for an answer they should know that starting with Swift 2.0 you can do something like this:
if #available(iOS 8, *) {
// iOS 8+ code
} else {
// older iOS code
}
You can read more about it here.

Resources