# System Programming Interfaces (SPI) in Swift Explained

In this blog post, I will explain the experimental Swift feature named System Programming interfaces and why this should interest library developers. 

## Defintion

Apple gives the following definition.

|API|SPI|
|---|---|
|An `entity` in a library that a `client` may use, or the collection of all such entities in a library. Marked ``public`` or ``open`` in Swift. Stands for **Application Programming Interface**.|A subset of `API` that is *only available to certain clients*. Stands for **System Programming Interface**.|

I rather like to think that SPI stands for **Secret** Programming Interface ;)

As a library developer, you can ship experimental features to dedicated clients (e.g. in-house teams) while hiding those features to other clients and all within the same build artifact.

Or maybe you (ab)use `@testable` and you are looking for an alternative...

%[https://twitter.com/dgregor79/status/1275805785247739904]

## SPI is experimental

Warning: Apple discourages using underscored attributes because those semantics are subject to change and most likely need to go through the Swift evolution process before being stabilized.

Nevertheless, plenty of projects do use underscored attributes. 

## Declare SPI

Swift 5.3 introduced an experimental attribute [`@_spi(spiName)`](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_spispiname) to mark a declaration as SPI.

```swift
// Module "Shopping"

public struct ShoppingCart {
  public init() {}

    public func payCash() {}
    
    @_spi(PayPal) public func payWithPayPal() {}
}
```

%[https://forums.swift.org/t/implement-a-private-api-in-swift/47920/3]

## Consume SPI

Clients that import `Shopping` will have access to `ShoppingCart`, its initializer, and `ShoppingCart.payCash`, but they won't see `payWithPayPal` function.

Clients can access SPI by declaring the import as `@_spi(spiName) import Module`.   

Therefore library developers have to share the spiName with those clients.

```swift
@_spi(PayPal) import Shopping

let s = Shopping()
s.payWithPayPal()
```

## Multiple SPI Names

As a library developer, you can use whatever SPI name you'd like and use as many as you'd like within the module.

```swift
// Module "Shopping"

public struct ShoppingCart {
  public init() {}

    public func payCash() {}
    
    @_spi(PayPal) public func payWithPayPal() {}
    @_spi(Bitcoin) public func payWithBitcoin() {}
}
```

```swift
@_spi(PayPal) @_spi(Bitcoin) import Shopping

let s = ShoppingCart()
s.payWithPayPal()
s.payWithBitcoin()
```

## Impact to `.swiftinterface`

Modules exposing SPI and using library evolution generate an additional `.private.swiftinterface` file in addition to the usual `.swiftinterface` file. This private interface exposes both API and SPI.

`Shopping.swiftinterface`

```
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.7 (swiftlang-5.7.0.127.4 clang-1400.0.29.50)
// swift-module-flags: -target arm64-apple-macosx10.13 -enable-objc-interop -enable-library-evolution -swift-version 5 -Onone -module-name Shopping
import Swift
import _Concurrency
public struct ShoppingCartItem {
  public init()
}
public struct ShoppingCart {
  public init()
  public func payCash()
}
```

`Shopping.private.swiftinterface`

```
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.7 (swiftlang-5.7.0.127.4 clang-1400.0.29.50)
// swift-module-flags: -target arm64-apple-macosx10.13 -enable-objc-interop -enable-library-evolution -swift-version 5 -Onone -module-name Shopping
import Swift
import _Concurrency
public struct ShoppingCartItem {
  public init()
}
public struct ShoppingCart {
  public init()
  public func payCash()
  @_spi(PayPal) public func payWithPayPal()
  @_spi(Bitcoin) public func payWithBitcoin()
}
@_spi(PayPal) public struct PayPalProvider {
  @_spi(PayPal) public init()
}
```

## SPI specific to a platform and/or version

The original implementation ...

%[https://github.com/apple/swift/pull/29810]

... was enhanced in Swift 5.7 by an additional, experimental attribute [`@_spi_available(platform, version)`](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_spi_availableplatform-version)

Like `@available`, this attribute indicates a declaration is available only as an SPI. This implies several behavioral changes compared to regular `@available`:

- Type checker diagnoses when a client accidentally exposes such a symbol in library APIs.
- When emitting public interfaces, `@_spi_available` is printed as `@available(platform, unavailable)`.
- ClangImporter imports ObjC macros `SPI_AVAILABLE` and `__SPI_AVAILABLE` to this attribute.

```swift
// Module "Shopping"

public struct ShoppingCart {
  public init() {}

    @_spi_available(watchOS 9, *)
    @available(tvOS, unavailable)
    public private(set) var items = [ShoppingCartItem]()

    public func payCash() {}
    
    @_spi(PayPal) public func payWithPayPal() {}
    @_spi(Bitcoin) public func payWithBitcoin() {}
}
```

`Shopping.swiftinterface`

```
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.7 (swiftlang-5.7.0.127.4 clang-1400.0.29.50)
// swift-module-flags: -target arm64-apple-macosx10.13 -enable-objc-interop -enable-library-evolution -swift-version 5 -Onone -module-name Shopping
import Swift
import _Concurrency
public struct ShoppingCartItem {
  public init()
}
public struct ShoppingCart {
  public init()
  @available(watchOS, unavailable)
  @available(tvOS, unavailable)
  public var items: [Shopping.ShoppingCartItem] {
    get
  }
  public func payCash()
}
```

## `@_spiOnly`

And support for SPI gets even more enhanced in the upcoming Swift 5.8.

In the development snapshot, you can see a new, experimental attribute [`@_spiOnly`](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_spionly).

It marks an import to be used in SPI and implementation details only.

![Screen Shot 2022-10-01 at 5.13.57 PM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1664669747082/sw7Cl7prd.png align="left")

To use `@_spiOnly` you are required to set the frontend flag `-experimental-spi-only-imports`. Below is an example of how to specify it in a `Package.swift` file.

```swift
    targets: [
        .target(
            name: "Shopping",
            dependencies: [],
            swiftSettings: [
                .unsafeFlags(["-enable-library-evolution"]),
                .unsafeFlags(["-Xfrontend", "-experimental-spi-only-imports"]) // requires Swift 5.8
            ]
        ),
    ]
```

The import statement will only be printed in the private .`swiftinterface` and skipped in the public .`swiftinterface`. Any use of imported types and declarations in API will be diagnosed.

So when you are using `@_spiOnly import CryptoKit` in module `Shopping` then `Shopping.swiftinterface` won't include that information.

```
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.8-dev (LLVM 338153f292479fa, Swift b752d7883c26cea)
// swift-module-flags: -target arm64-apple-macosx10.13 -enable-objc-interop -enable-library-evolution -swift-version 5 -Onone -module-name Shopping
import Swift
import _Concurrency
import _StringProcessing
```

But `Shopping.private.swiftinterface` will do.

```
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.8-dev (LLVM 338153f292479fa, Swift b752d7883c26cea)
// swift-module-flags: -target arm64-apple-macosx10.13 -enable-objc-interop -enable-library-evolution -swift-version 5 -Onone -module-name Shopping
/*@_spiOnly*/ import CryptoKit
import Swift
import _Concurrency
import _StringProcessing
public struct ShoppingCartItem {
```

## Summary

Swift, starting with 5.3, allows library developers to hide declarations and make them available for specific clients only. Those experimental capabilities, known as System Programming Interfaces, were and do currently get enhanced in later Swift versions.

![SPI](https://cdn.hashnode.com/res/hashnode/image/upload/v1664763209567/fJioC09xN.jpeg align="left")

%[https://github.com/MarcoEidinger/swift-spi-demo]


