I want to be able to conditionally compile blocks of swift code based on the version of the swift language and the version of the target SDK used by the build system. I have no control over the user's build system. I notice on mine the following command line arguments are passed to the compiler by Xcode: -swift-version 5
and -target-sdk-version 14.2
. So I was hoping for something like the following, but I can't figure it out.
#if swift-version >= 5
...swift code...
#else
...old approach...
#endif
#if target-sdk-version >= 14.2
...swift code...
...perhaps using #available if deployment sdk is older...
#else
...old approach...
#endif
Here's a simple example. Suppose I have a SwiftUI scene that I want to apply the modifier .restorationBehavior(.disabled)
But that's only available in the SDK 15.0 and later. What should the source code look like if I want just the default behavior if one is building against an older SDK?
Here's one futile attempt to make a Scene modifier that can just be used like this .pleaseDisableRestoration()
extension Scene {
@available(swift, introduced: 6.0)
func pleaseDisableRestoration() -> some Scene {
if #available(macOS 14, *) { //run time check
return self.restorationBehavior(.disabled)
}else{
return self
}
}
@available(swift, obsoleted: 6.0)
func pleaseDisableRestoration() -> some Scene {
return self
}
}
It fails for two reasons: (1) the self.restorationBehavior
modifier will not compile if I'm using Swift 5, even though the declaration is marked for Swift 6 and later; and (2) the reuse of the declaration pleaseDisableRestoration
won't work even though there is no overlap in the Swift versions they apply to.
As DonMag points out I can use #if swift(>=6.0)
for half the problem. Unfortunately, the important case is the SDK level. But the example above rewritten using...
@available(macOS, introduced: 15.0)
...
@available(macOS, obsoleted: 15.0)
...
...fails in the same way.
I want to be able to conditionally compile blocks of swift code based on the version of the swift language and the version of the target SDK used by the build system. I have no control over the user's build system. I notice on mine the following command line arguments are passed to the compiler by Xcode: -swift-version 5
and -target-sdk-version 14.2
. So I was hoping for something like the following, but I can't figure it out.
#if swift-version >= 5
...swift code...
#else
...old approach...
#endif
#if target-sdk-version >= 14.2
...swift code...
...perhaps using #available if deployment sdk is older...
#else
...old approach...
#endif
Here's a simple example. Suppose I have a SwiftUI scene that I want to apply the modifier .restorationBehavior(.disabled)
But that's only available in the SDK 15.0 and later. What should the source code look like if I want just the default behavior if one is building against an older SDK?
Here's one futile attempt to make a Scene modifier that can just be used like this .pleaseDisableRestoration()
extension Scene {
@available(swift, introduced: 6.0)
func pleaseDisableRestoration() -> some Scene {
if #available(macOS 14, *) { //run time check
return self.restorationBehavior(.disabled)
}else{
return self
}
}
@available(swift, obsoleted: 6.0)
func pleaseDisableRestoration() -> some Scene {
return self
}
}
It fails for two reasons: (1) the self.restorationBehavior
modifier will not compile if I'm using Swift 5, even though the declaration is marked for Swift 6 and later; and (2) the reuse of the declaration pleaseDisableRestoration
won't work even though there is no overlap in the Swift versions they apply to.
As DonMag points out I can use #if swift(>=6.0)
for half the problem. Unfortunately, the important case is the SDK level. But the example above rewritten using...
@available(macOS, introduced: 15.0)
...
@available(macOS, obsoleted: 15.0)
...
...fails in the same way.
Share Improve this question edited Mar 21 at 18:06 Mustang asked Mar 21 at 15:26 MustangMustang 4273 silver badges10 bronze badges 6 | Show 1 more comment2 Answers
Reset to default 2I also developed / maintained SDK for number of years, and this was always not trivial. Short answer: there's no universal solution unfortunately. You have a few strategies and you have to apply them for different cases.
First of all for compile-time decisions like Swift version, it depends on how you distribute your SDK:
If you distribute your SDK as code, many things can be solved with compile-time conditions like
#if swift(>=6.0)
or#if compiler(>=5.5)
, since the product compiled on customer's side will have exactly the right code for their environment.If you provide your SDK as a binary, then fet about these conditions. They are useless for you, since the decision will be made at the time you build a binary, so it will be based on your versions, SDK in customer hands won't even have a code from the "else" part.
For binary distribution in cases where swift / compiler versions are involved, you usually have to compy with the lowest version you need to support (which, fortunately is not too many versions, since Apple cuts off support for older Xcode pretty quickly, and so it's usually 1-2 versions of Xcode, which also dictates only 1-2 versions of Swift.
Except... in some worst case scenarios you may need to have separate releases for different consumers. Those are very rare in my experience, but still... for example I wonder if Swift 6 will cause this issue for some frameworks.
Second factor which devices and operating systems you are expecting to be supported for the end-users. You have a few strategies to anticipate them:
Annotations like @available(iOS 15, macOS 12, *)
or if #available(iOS 15.0, *)
are good way, but not always possible to use directly. Your example is a perfect case when they are not. So you have to go a step further:
- Make a universal wrapper on top of such availability annotation. I use that a lot with Swift UI, where I want the users on latest iOS to have the best experience with new features, but also need to support lower version users somehow. The pattern for that is shown answers to this question
- Or in some cases you have to go with the factory pattern, and invoke one or another implementation of the feature, based on current version of OS. I use this with some more advanced frameworks which change a lot from version to version. For example camera capabilities always improve, no point to limit the user on, say, iOS 18 with capabilities of iOS 13, just because I have to support both. So usually it's some sort of interface, and then processing class per version, and factory that selects them based on runtime.
@timbre's answer makes many good points and is worth an up-vote. But it didn't answer exactly what I needed. So here's what I came up with.
See this article for how I came up with 6.0 corresponding to target SDK macOS 15
#if swift(>=5.0)
...swift code...
#else
...old approach...
#endif
#if canImport(SwiftUI, _version: "6.0") //hack to test for macOS 15 target sdk
...swift code...
...perhaps using #available if deployment sdk is older...
#else
...old approach...
#endif
#if swift(>=6.0)
? – DonMag Commented Mar 21 at 16:34#if canImport
to check if some module exclusive to that SDK version and above can be imported. I can't think of any off the top of my head for macOS 15, though. – Sweeper Commented Mar 21 at 18:06.framework
) or as static library (.a
file)? – timbre timbre Commented Mar 21 at 18:33