最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - How do I conditionally compile swift based on swift version and target SDK? - Stack Overflow

programmeradmin6浏览0评论

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
  • Can you use: #if swift(>=6.0)? – DonMag Commented Mar 21 at 16:34
  • @DonMag Well, yes and no. What I really care about is the SDK version as that's what determines the presence of restorationBehavior in this example. My example was just bad. But thanks for the nice tip--it works for half of my question. – Mustang Commented Mar 21 at 17:42
  • @Mustang There is no compiler control statement for checking SDK version. I suppose you can "abuse" #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
  • Do you distribute your SDK as code (compiled by consumer), as xcframework (or older .framework) or as static library (.a file)? – timbre timbre Commented Mar 21 at 18:33
  • @timbretimbre ...as code. And it's not an SDK--just sources to disperse parties who cannot modify the version of OS their using, nor the version of Xcode; nor the target SDK (though the deployment SDK is set to a minimum. – Mustang Commented Mar 21 at 19:03
 |  Show 1 more comment

2 Answers 2

Reset to default 2

I 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
发布评论

评论列表(0)

  1. 暂无评论