Skip to content

A Swift Guide

Richard Robinson edited this page Apr 10, 2026 · 4 revisions

A Swift guide

This is currently an unofficial draft version of the guide

Table of Contents

Overview

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:

Availability

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.

Using Swift in WebKit

Adding a Swift file to WebKit

This section of the guide applies to Cocoa platforms only.

To add a new Swift file to one of the supported targets in WebKit:

  1. In Xcode, go to the new file dialog, and choose "Swift File"
  2. Xcode should automatically add it to the proper target
  3. That's it! The new Swift file should now build with its target

Using Swift from Swift

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 B

See Access Modifiers to determine which is the correct import access modifier to use.

Using Objective-C from Swift

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_Internal

Note 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_Private

Note 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.

Using Swift from Objective-C

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.

Using C++ from Swift

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++.

Using Swift from C++

This is currently a work in progress.

The Swift Language

Strict Safety

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.

If you need to write unsafe or @safe

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:

  1. Avoid using unsafe or @safe at all. You may be able to:
  2. If you find the unsafe or @safe is still necessary,
    • Identify what abstraction is missing from the Swift standard library (or some other dependency) which would have allowed the unsafe or @safe to 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.

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.

Access Modifiers

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.

Access-level modifiers on import 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 SomeFramework

In 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.

Access-level modifiers on symbol declarations

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:

  1. Intentionally adding new public API to a public framework to be consumed by Swift.
  2. Exposing SPI from a public framework to be consumed by Swift, in which case the @_spi(NameOfSPICategory modifier must be attached.
  3. 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.

Concurrency

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:

  1. Always prefer structured concurrency (async and await, @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.

  2. Do not use completion handlers. Always use async and await instead, including when calling an Objective-C method that has a completion handler, which becomes an async function when imported into Swift.

  3. Do not use explicit actor annotations or methods such as Task { @MainActor in or MainActor.run. These are always unnecessary, since the correct isolation will be automatically switched to as needed.

  4. Use Task.immediate instead of Task, and do not use Task.detached. See SE-0472 and SE-0317 for the rationales behind these.

  5. 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.

Best Practices

  • 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 @available in 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 canImport unless you absolutely have to.

Modules

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).

Swift modules

TODO.

Clang modules

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.

Public modules

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

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.

Style and Formatting

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, and return)

The WebKit check-webkit-style script includes performing swift-format. To run swift-format directly, use

swift format lint --recursive Source/ --strict

Do 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>

Document Revision History

This section lists the recent changes made to this guide.

2025-03-03

  • Published the initial version of this guide.

Clone this wiki locally