Code organization question: supporting both iOS 16 and iOS 17 and using new language features on the latter

As I used to develop my app only for latest iOS release (the app is a personal project and has few external users), I have no practical experience with organizing code for multiple iOS support. However, I'm thinking to support both iOS 16 and iOS 17. And for iOS 17 I'd like to use the new Swift features (macros, new observation API, etc) and new SwiftUI APIs (the new State and Bindable property wrappers). I'm still trying to understand those new features and haven't given them a try. But my concern is these new Swift and SwiftUI features are so fundamental changes that it would be a mess to combine the old code (for iOS 16) and the new code (for iOS 17) in the same project. For example, each view model will need two separate versions, one using the old ObservableObject protocol, one using the new observation API. I wonder if this is a new challenge for this specific release, or is it normal (for example, I guess Swift concurrency on iOS 15 might cause the same situation)? Will it be difficult to maintain the code? Any suggestion would be appreciated.

(BTW, I'm aware code developed for iOS 16 will work fine on iOS 17. I'd like to use the new language features for learning purpose.)


Maybe the approach outlined here is an option.

Thanks. Two comments:

  1. That approach assumes macros will be backported to iOS16 (I don't find information on this in the forum).

  2. @ObserveObject and @Observable have other differences which the approach can't encapsulate. For example, when using @ObserveObject we use Publisher API to process property changes; when using @Observable we use withMutation.

I have been thinking about this and I have decided to give up because it's not worth the effort. I'm going to use the old APIs to support iOS 16 and iOS 17 for a while and switch to new APIs sometime next year.

BTW, I'm considering to use the following approach to support iOS 16 when I switch to iOS 17 API:

  • When I change code to use iOS 17 API, I won't remove the old iOS 16 code. I'll keep it in the app.

  • As a result, my app (it's a physical app) will contain two complete apps (they are logical apps): one for iOS 16 and one for iOS 17. When the physical app starts up, it determines which logical app to run based on API availability check.

  • I'll need some approach to avoid name conflict between the two logical apps' code. Since each app are implemented as a set of packages. I think I can achieve it by using different package names (e.g., adding a suffix indicating iOS release) for each app.

  • The new development will occur in iOS 17 app only. So, for users using iOS 16 logical app, they won't see new features any more (I'll add a message in the app to explain it to users). Note: there is no reason why new features can't be added to iOS 16 app. It's just an effort issue.

I think this should work, right? (I don't read about the approach on the net. I think it out myself. So your comments are appreciated.). The app size will double. But the approach is simple: it supports multiple iOS without extra effort because we include iOS 16 code but freeze it (note the initial code change to iOS 17 API will need effort, but it's inevitable).

EDIT: I just realized my above approach is probably unnecessary, because it effectively implements App Store's "Last Compatible Version" feature. I'm not very sure about the feature's exact behavior yet (I googled but the information are inconsistent), but it seems to allows user running an older iOS not supported by latest version of an app installs an older version of the app (However, some posts suggested it only works for update, not fresh install). If it supports fresh install, that's exactly the purpose of my above design. Note: my design is a bit more powerful because it's possible to add new feature to app versions supporting older iOS release, but given the rapid change in SwiftUi it's unlikely to do it in practice.

I'd appreciate it if anyone can confirm if my above understanding of App Store's "Last Compatible Version" is correct or not.

Macros are purely a compile-time thing and don't have a deployment target.


why not just have a new feature only available for ios 17?
existing features can just be updated for ios 16 until you drop it.

having double the app size seems a bit difficult to maintain imo, especially if you are going to basically freeze development for ios16...

An, that makes sense. So, if I develop an app whose minimal is, say, iOS 15 using Xcode 15 (the newest one with macros support), I can use macros in the app. I didn't realize this because I used to think by mistake that the binary user downloads are compiled by App Store so they have to use the Xcode bundled on the minimal iOS version. It's actually generated by developers and App Store have no access to the source code. I have no idea how I completely misunderstood it.

It's not a new feature of my app. It's new feature of Swift and SwiftUI. My concern is that it's difficult to mix the code using old Swift/SwiftUI API and the code using new API.

This could probably be encapsulated in the if/guard #available(iOS 17, *) brackets.



so after couple months of this discussion, what did you end up doing in your app ? I'm starting up the development for a new app, and thinking to support iOS 17 only, but its adoption rate gets me to hesitate about taking this decision.

Just FYI. I'm modifying my app to support iOS 17 only. I didn't consider adding an abstraction layer (as tera suggested), because I'm afraid even if I managed to get it working it would make the code obscure and hard to maintain.

1 Like