-
Notifications
You must be signed in to change notification settings - Fork 127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding support for Swift / Moving away from Obj-C on Apple platforms #747
base: main
Are you sure you want to change the base?
Changes from all commits
6b50f70
ba724f9
a297a0c
9d79ee2
508dd00
9821a99
d9ba022
21e7c3f
d5c39ae
6a05439
8ecb9db
228b56d
01bc4a7
2016329
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
--- | ||
title: Adding support for Swift / Moving away from Obj-C on Apple platforms | ||
author: | ||
- Parsa Nasirimehr | ||
date: 2023-12-14 | ||
--- | ||
|
||
# RFC0000: Adding support for Swift / Moving away from Obj-C on Apple platforms | ||
|
||
## Summary | ||
|
||
React Native's codebase on Apple's platforms has been written in a mix of Obj-C, Obj-C++ and C++. This proposal discuses what path we might have to migrate the codebase to using just C++ and Swift, Apple's current standard language for developing apps on their platforms, and what areas we need to be mindful of (including tooling, various architectures that would need to be supported, documentation updates and the impact on third party libraries) | ||
|
||
## Motivation | ||
|
||
The motivation for this change is threefold: | ||
- As of Swift 5.9, Swift is now capable of direct interop with C++, Which was one of the primary reasons Obj-C and Obj-C++ were still required within the repository. While the development [is still ongoing](https://www.swift.org/documentation/cxx-interop/status/) and there are [constraints that still exist](https://github.com/apple/swift/issues/66159), Apple's end goal is to reach the needed API parity to work seamlessly between Swift and C++ | ||
- Obj-C is relatively hard to write and maintain for, and it is reflected by the [community](https://github.com/react-native-community/discussions-and-proposals/issues/104). Having a modern language that is more readable, more performant(in some cases) and [safer from certain class of errors](https://developer.apple.com/swift/#safety) means that users and library maintainers alike can produce better quality code and take advantage of the underlying platforms easier if they need it | ||
- As Swift evolves and becomes more feature rich, there is a possibility that Apple will begin to remove support for Obj-C in it's toolchain, or release new frameworks that are Swift only (We have already begun seeing this trend with frameworks like Swift Data). The documentation for the various APIs that Apple provides may also be moved to simply having Swift as the supported language. | ||
|
||
|
||
## Detailed design | ||
|
||
The general idea is that the code we have for Apple platforms should be consistent of three primary parts by the end of this migration: | ||
- The Swift API layer that is in charge of the platform specific operations that can not be shared across other platforms (the View Delegate, the Scene Delegate, anything that has to do with Apple specific frameworks like UIKit) | ||
- The Shared C++ code that is platform-agnostic and used by all React Native target platforms (Yoga, Hermes, the event handlers, the bridge (if applicable), etc.) | ||
- The optional intermediary C++ code, with Swift specific annotations for certain APIs that help Swift interact with C++ in a more efficient and correct manner (To learn more, please refer to [Swift's documentation of interop with C++](https://www.swift.org/documentation/cxx-interop/#exposing-swift-apis-to-c) under "Customizing How C++ Maps to Swift") | ||
|
||
The third step is marked as optional. The idea is that the Swift compiler makes certain assumptions when interacting directly with the C++ modules that may not necessarily be correct (C++ types being imported as value types by default, for instance). But we can override those behaviours by explicitly marking certain sections with the annotations provided to us by `<swift/bridging>` (Marking things to be imported as reference types, Mapping getters and setters, etc.) | ||
|
||
Much like our current setup with Obj-C, The Apple platform section of any new project template created will simply expose the Swift files for developers to extend and add functionalities to. The React Native APIs can be simply imported via a swift import and consumed by the Swift parts, giving us control over our API surface and hiding away any specific pieces that will not be needed on a day-to-day basis. Example: | ||
|
||
```swift | ||
import React | ||
|
||
@main | ||
class AppDelegate: RCTAppDelegate { | ||
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { | ||
self.moduleName = "HelloWorld" | ||
// You can add your custom initial props in the dictionary below. | ||
// They will be passed down to the ViewController used by React Native. | ||
self.initialProps = [:] | ||
|
||
return super.application(application, didFinishLaunchingWithOptions: launchOptions) | ||
} | ||
|
||
override func sourceURL(for bridge: RCTBridge!) -> URL! { | ||
return self.getBundleURL() | ||
} | ||
|
||
func getBundleURL() -> URL { | ||
#if DEBUG | ||
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") | ||
#else | ||
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")! | ||
#endif | ||
} | ||
|
||
/// Native bits of functionality that users may extend and add on their own | ||
} | ||
``` | ||
|
||
## Drawbacks | ||
|
||
The biggest drawback can be boiled down to certain key elements: | ||
- It is a massive undertaking, that will likely take years to fully complete | ||
- The Swift API is not yet fully compatible with everything we will need on the C++ side of things | ||
- Libraries will need to update their documentation and probably some changes to their imports as well (ideally, the API should not change, unless it is related to the Platform specific side of things and the Swift implementation simplifies the existing API for them to consume) and as [previously established](https://github.com/react-native-community/discussions-and-proposals/issues/671) not all libraries adapt and maintainers may not update | ||
- Users will need to upgrade their templates and the libraries they use to Switch over | ||
|
||
## Alternatives | ||
|
||
### Leaving things as is | ||
The overhead of this option is much lower in the short term, and while it creates a bad user experience for the smaller subsection of the user base that want to be able to do more (and library maintainers who would probably benefit from this new-found access), the cost is undeniably lower. The problem is that eventually, Apple will either push for this change directly, or will make it very hard to continue, so unless we take initiative now, We will be forced to it down the road, and in the meantime React Native will have to resort to more and more custom solutions to deal with any APIs that might become unavailable | ||
|
||
### Creating a wrapper Objective-C(++) around C++ pure code | ||
Another approach could be to create wrappers around our C++ code base using Objective-C and Objective-C++ and only exposing APIs that are Swift compatible through this wrapper. | ||
|
||
To make this work, we will need to clearly define which APIs fall under that category, which might require quite a bit of work. | ||
|
||
This approach has several pros which should be taken into consideration: | ||
- Avoid a rewrite of some internals | ||
- Allow contributors to start using Swift only, making the process much easier for everyone involved | ||
- Allow us to work around the current Swift <-> C++ interop limitations and unblock the work | ||
- Take a much shorter amount of time to do | ||
|
||
It also has one con that we would need to consider as well: | ||
- It does not address the issue of Apple removing support for Objective-C in the toolchain (Though perhaps, given how long it might take for Apple to make this change, this might end up not being too big of a problem) | ||
|
||
## Adoption strategy | ||
|
||
The core idea is to still be able to deliver feature and changes that users and library maintainers have been asking for while doing the migration. One strategy that comes to mind for achieving this result is as follows: | ||
- Isolating the OSS bits that are user facing from the internals as much as possible (Like AppDelegate and the tests) | ||
- Updating CocoaPods infra | ||
- Communicating with third party libraries to adjust | ||
- Rolling out a new version | ||
- Configuring the exposed modules as Clang modules so they can be consumed by Swift | ||
- (If configuring the modules ends up requiring some breaking change (like needing to put the public headers in an include folder)) Deal with breaking change for the libraries and setting up migration guides | ||
- Migrating the OSS parts from Obj-C -> Swift | ||
- Updating CLI to support the new files and structures | ||
- Updating React Native Helper to support people trying to migrate their templates from Obj-C to Swift | ||
- Updating the docs | ||
- Communicating with library maintainers if help is needed updating their documentation to support the setup for both Swift and Obj-C for older users | ||
- Rolling out a new version | ||
- Moving as much shared logic as possible to C++ and away from Obj-C, separating them as distinctly as possible while adding new features and capabilities and continuing to roll out new versions (**Users should not be affected, and ideally the Library maintainers should not be affected either**) | ||
- Beginning the transition from Obj-C to Swift from the tests in the internals (**No effect on users or library maintainers**) | ||
- Moving the rest of the modules one at a time from Obj-C to Swift (**Library maintainers may need to update, if they wish to take advantage of the newer APIs or if we break change an APIs signature, but ideally it should only be in the direction of making it easier to maintain libraries and better APIs**) | ||
- Updating CocoaPods infra | ||
- ? (Please add any more steps that come to mind, at the end or anywhere else in the list) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is an important step missing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, that explains quite a bit. So would we need to configure them as Clang Modules? Is it even possible or would it have some other consequences on other parts of the code base? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is for sure possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mentioned both the required action and the concern as steps we would need to take under 228b56d |
||
|
||
## How we teach this | ||
|
||
This should be a pretty welcome step for the user base (and even the maintainers, if done correctly and without too many breaking changes). The docs for creating Native module will need to be updated to add support for Swift, and we can simply mention the change in React Native EU or similar conferences. | ||
|
||
The idea is to make it very un-noticeable to the vast majority of the user base who simply want to use the React side for creating their mobile applications, and the part of the community that is capable of creating native modules should simply feel like it is just another native Apple app that they can extend using whatever it is that they need (perhaps adding a watchOS target support for their app, or using CoreML or AppClips or making a SwiftUI based widget) | ||
|
||
It should be presented as React Native keeping up with changes and lowering the barrier of entry for all devs and that we are simply making part of the work that is duplicated across platforms easy to reuse, using the same tools that our industry is using and without sacrificing the experience. | ||
|
||
## Unresolved questions | ||
|
||
- What other parts of the infra need to be changed for this transition to work? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Surely, we need to verify that
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mentioned these as questions that need to be solved under 8ecb9db. |
||
- How will this affect other targets such as React Native macOS, React Native visionOS, etc.? | ||
- Are bits like Native/TurboModule APIs or Native/Fabric Components APIs or Codegen (for the New Arch) compatible with Swift? If not, what work needs to be done to make these APIs compatible? (And in the case of Codegen, would it be possible to generate Swift types directly as well, even if its partial)? If they can not be made compatible, what can be done? | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is another alternative: we can create a wrapper of Objective-C(++) around C++ pure code exposing APIs that are compatible with Swift.
This approach is a middle-ground that should:
To achieve this, we should highlight which are all the APIs that could be useful for devs to be accessed in Swift (which is not a joke of a task)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added under d5c39ae. Could be a nice alternative. Wrote one con for it as well that comes to my mind regarding Toolchain depracation but perhaps it won't be that big of a problem