-
Notifications
You must be signed in to change notification settings - Fork 1.9k
A Swift Guide
This is currently an unofficial draft version of the guide
This document outlines best practices and includes guides for how to use Swift, but does not go into detail about Swift language basics. The following resources may also be helpful:
- The Swift Programming Language (TSPL) book
- swift.org, the official Swift website
- The Swift Framework on Apple Developer Docs
- Mixing Swift and C++ article
- The Swift Evolution proposals
Swift is supported in these WebKit targets:
| Target | Swift Support |
|---|---|
| bmalloc | ✅ |
| wtf | ❌ |
| JavaScriptCore | ❌ |
| PAL | ✅ |
| WebGPU | ✅ |
| WebCore | ❌ |
| WebKit | ✅ |
| WebKitSwift | |
| TestWebKitAPI | ✅ |
| _WebKit_SwiftUI | ✅ |
Notes:
-
Targets marked with ✅ fully support Swift, and need no additional configuration. These targets use Swift 6 and full strict concurrency.
-
Targets marked with ❌ require additional configuration to be able to support Swift. Note that they can still be used from within the targets that do support Swift.
-
Targets marked with
⚠️ have historically been used to write Swift code in WebKit, but should no longer be used to do so.- WebKitSwift should never be used for any new code now that WebKit properly supports Swift code. If a dependency cycle between two Swift modules occurs, WebKitSwift may be used temporarily to avoid the cycle, but a bug should be filed to track properly fixing the cycle and then migrating the code to WebKit proper as soon as possible.
This section of the guide applies to Cocoa platforms only.
To add a new Swift file to one of the supported targets in WebKit:
- In Xcode, go to the new file dialog, and choose "Swift File"
- Xcode should automatically add it to the proper target
- That's it! The new Swift file should now build with its target
It is recommended to always consume Swift from Swift.
- To use Swift from Swift within the same target, no additional work is needed!
- To use Swift in target A from Swift in target B, you first need to import B:
private import BSee Access Modifiers to determine which is the correct import access modifier to use.
Swift natively supports Objective-C interoperability by leveraging Clang modules.
Frameworks such as WebKit.framework typically have three Clang modules:
- A public module, named
WebKit. This contains all public headers belonging to the target, and represents API. - A private module, named
WebKit_Private. This contains all private headers belonging to the target. - An internal module, named
WebKit_Internal(this naming is not enforced, but is convention). This may contain all internal (project) headers belonging to the target, but is typically only added to on an as-needed basis as it is internal to the project and does not affect the SDK.
and one Swift module, named WebKit.
Private frameworks such as WebCore.framework also have a public module, but it is empty.
To use Objective-C from Swift within the same target, import the respective clang module.
private import WebKit_InternalNote that if the Clang module and Swift module share the same name, no import is needed. To access a project-level header from Swift, it may first need to be added to the internal module map if it is not already present.
To use Objective-C in target A from Swift in target B, you first need to import one of B's public or private clang modules:
private import WebCore_PrivateNote that you cannot import an internal clang module from a different target. See [Modules] for more detailed information about how Clang and Swift modules interact.
Swift is not directly exposed to Objective-C, and should ideally be consumed by other Swift code.
If this is not possible, the @objc @implementation compiler feature should be used to expose an Objective-C interface with a Swift implementation. More information can be found in SE-0436.
Note: Do not use @objc public class ... under any circumstances; always use @objc @implementation. The former is not properly supported for frameworks and can lead to runtime crashes.
Swift in WebKit supports Swift-Cxx interoperability, which functions much like how Objective-C may be used from Swift. Both pure C++ and Objective-C++ can be accessed from Swift.
WebKit uses Strictly Safe Swift. See Strict Safety for specific guidelines on this.
More information can be found in Mixing Swift and C++.
This is currently a work in progress.
Don't worry about memory safety unless you need to type unsafe.
The point of using Strictly Safe Swift is that the compiler proves all your code is safe against memory safety errors. You don't need to think about it. Go and have a nice day instead.
We are aiming to have a Swift codebase entirely free of unsafe and @safe. Our goal is to use Swift as the programming environment which verifies safety without exception. This is still work-in-progress.
If you find you need to write unsafe or @safe, do this:
- Avoid using
unsafeor@safeat all. You may be able to:- Restructure the code.
- Use an existing type which encapsulates the unsafety and already exposes a safe API for you to use.
- Annotate the C++ type such that its lifetime and bounds are understood by Swift.
- If you find the
unsafeor@safeis still necessary,- Identify what abstraction is missing from the Swift standard library (or some other dependency) which would have allowed the
unsafeor@safeto be avoided. - File a bug to ask for that improvement.
- Attempt to locally build an abstraction which guarantees runtime safety, even if internally it includes unsafety.
- Add a comment explaining the safety and referencing the bug you filed. Such comments should explain the safety invariants and reasoning which guarantee that they are true. These comments should not require global knowledge of the subsystem but should be auditable with reference just to obvious local truths.
- Identify what abstraction is missing from the Swift standard library (or some other dependency) which would have allowed the
Reviewers of unsafe code should:
- Reject unsafe code without comments explaining the safety.
- Fully understand the safety invariants which need to be upheld, and why they are always upheld.
- Reject the code if it requires anything beyond local reasoning. (That is, you can become confident it's safe without having to read code beyond the function or data type you're reviewing).
- Ensure that the author has followed the steps above.
For details on this different kinds of standard access control levels, see Access Control in the TSPL book. Note that WebKit does not currently support the package access level.
Swift supports access level modifiers on both imports and symbol declarations.
Details of how to use access-level modifiers on import declarations can be found on the Swift Evolution proposal SE-0409.
All imports in Swift should have an access level specified on them unless they are internal (the default level).
The current default access level on import declarations is internal. As with access level modifiers in general, the most restrictive level should be used wherever possible. This is usually private or internal.
Note that adding new public imports in the public WebKit frameworks must always be avoided, unless new API is intentionally being added that depends on the imported module or the module already is publicly imported in WebKit.
To access Swift SPI from a different framework or module, the @_spi() modifier must be used:
@_spi(NameOfSPICategory) internal import SomeFrameworkIn this example, any SPI written in SomeFramework with the @_spi(ProposedAPI) modifier attached can now be accessed from the import site. Note that this is only supported with the Apple Internal SDK.
All Swift symbols have an associated access level. If there is not one explicitly specified, the default is internal.
The public and open access modifiers must always be avoided, except in the following circumstances:
- Intentionally adding new public API to a public framework to be consumed by Swift.
- Exposing SPI from a public framework to be consumed by Swift, in which case the
@_spi(NameOfSPICategorymodifier must be attached. - Exposing SPI from a private framework to be consumed by Swift.
All public and open symbols must have proper documentation comments written for them, and proper availability annotations applied. This is enforced by the swift-format rule AllPublicDeclarationsHaveDocumentation.
Since internal is the default, it does not have to be specified.
Swift in WebKit uses strict concurrency, and a default isolation mode of nonisolated.
TSPL has the authoritative documentation on Concurrency, and the Apple Developer documentation provides the Concurrency API Collection.
When using Concurrency, these guidelines should be followed:
-
Always prefer structured concurrency (
asyncandawait,@concurrent) over unstructured concurrency (Task). The latter should mainly only be used when dealing with Objective-C interoperability. This makes concurrent code easier to reason about and provides stronger guarantees and expectations. -
Do not use completion handlers. Always use
asyncandawaitinstead, including when calling an Objective-C method that has a completion handler, which becomes anasyncfunction when imported into Swift. -
Do not use explicit actor annotations or methods such as
Task { @MainActor inorMainActor.run. These are always unnecessary, since the correct isolation will be automatically switched to as needed. -
Use
Task.immediateinstead ofTask, and do not useTask.detached. See SE-0472 and SE-0317 for the rationales behind these. -
Use actors in Swift instead of Grand-Central Dispatch (GCD) and dispatch queues. The former was designed for Swift and has compile-time concurrency safety.
-
Follow Swift conventions and write idiomatic Swift as much as possible. Like Objective-C, different languages have different conventions and best practices that should be followed. Some basic conventions can be found here
-
Always use opaque types (
some) instead of boxed protocol types (any). See Opaque and Boxed Protocol Types for more details. -
When using Swift from Swift, use pure Swift types, and prefer structs by default.
-
Do not use
if #available() { … }or@availablein order to access new API.- These language features are for checking OS version at runtime, for software that can run on multiple OS versions. But WebKit is always building for a specific OS version, so this adds a runtime check where a static check would suffice.
- Prefer
<wtf/Platform.h>flags:#if ENABLE_MY_FEATURE ... #endif - Avoid
canImportunless you absolutely have to.
Swift interacts with other Swift code and with C-family languages through the use of Swift modules and clang modules, respectively.
A module can only access other modules that are as or less restrictive than that module (just like how public headers cannot include private headers).
TODO.
Details about Clang modules can be found at Modules on the Clang LLVM documentation site.
There are several different ways a module map can be structured.
The standard structure for a public module map is
framework module ABC [system] {
umbrella "Headers" // or `umbrella header "ABC.h"`
module * { export * }
export *
}
Clients of ABC can then do import ABC to access all headers in the framework's Headers directory.
Private modules do not have such a standard structure; however, some common ones include
framework module ABC_Private [system] {
umbrella "PrivateHeaders"
module * { export * }
export *
}
or
framework module ABC_Private [system] {
explicit module A {
header "A.h"
export *
}
explicit module B {
header "B.h"
export *
}
// etc.
}
Clients of ABC_Private can then do import ABC_Private to access all headers in the framework's PrivateHeaders directory in the first case, or import ABC_Private.A to access explicit sub-modules as in the second case.
Note that some private module maps use a combination of module map structure styles and explicit/implicit sub-modules. This is bad practice because it is inconsistent and typically leads to confusion about how to import a specific module.
Swift code in WebKit uses the standard swift-format tool for formatting and linting, including enabling all checks and rules. At any given time, there should be zero violations in the Source directory of WebKit.
Additionally, the following style rules are recommended for consistency, but not enforced nor required:
- Omit any keywords that are not necessary (for example, in most cases these are usually keywords such as
internal,nonisolated, andreturn)
The WebKit check-webkit-style script includes performing swift-format. To run swift-format directly, use
swift format lint --recursive Source/ --strictDo not ignore any of the formatting or linting errors produced by swift-format; these rules enforce consistency and improved correctness.
If, for any reason, a swift-format violation needs to be suppressed, the following comment can be written above the line that contains the violation:
// FIXME: <bug tracking why this violation is ignored and plans to re-enable it>
// swift-format-ignore: <name of swift-format rule>This section lists the recent changes made to this guide.
2025-03-03
- Published the initial version of this guide.