@_spi 修饰符作用

在翻阅 Stripe 和 Adyen 的 iOS SDK 时,发现他们有很多 public 方法却搜不到调用。仔细看发现它们被 @_spi(InternalName) 标记了。


这些支付 SDK 都是模块化的(比如 StripeCore、StripeCard)。 痛点: StripeCard 必须调用 StripeCore 里的某些工具类。如果设为 internal,跨模块调不到;如果设为 public,集成 SDK 的商户就会在代码补全里看到一堆乱七八糟的内部方法,既不安全也显得 SDK 很乱。@_spi可以用来解决这个问题。

@_spi 是一个非常强大但属于半私有(Semi-private)的 Swift 特性。它的全称是 Service Provider Interface

简单来说,@_spi 允许你将某些 API 标记为“私有”,但又允许特定的外部模块访问它们。这在管理 SDK 内部模块间通信,或者在分发二进制 SDK 时非常有用。

1. 核心作用:精细化访问控制

在 Swift 标准的访问控制中,我们只有 open, public, internal, fileprivate, private

  • 如果你设为 internal,宿主 App 看不见,但你自己的其他库(如果是多组件架构)也看不见。
  • 如果你设为 public,你自己的其他库能用了,但宿主 App 的开发者也能看见并调用它,这会破坏你的 API 纯洁性。

@_spi 解决了这个问题: 它创建了一个“命名的隐私圈”。

代码示例

在你的 SDK 核心模块中:

public struct MySDKConfig {
    public init() {}

    // 使用 @_spi 标记内部调试工具,指定组名为 "InternalStuff"
    @_spi(InternalStuff)
    public func resetInternalState() {
        print("只有内部人员能调这个!")
    }
}

在你的另一个内部模块(或测试模块)中:

// 普通 import 看不到 resetInternalState
import MySDK 

// 必须使用 @_spi 导入才能解锁对应的 API
@_spi(InternalStuff) import MySDK

let config = MySDKConfig()
config.resetInternalState() // 现在可以调用了

2. 为什么 SDK 开发者需要它?

A. 跨组件通信

如果你的 SDK 拆分成了多个二进制库(例如 MySDKCoreMySDKAdvanced),有些核心方法需要暴露给 Advanced 模块,但不希望普通用户在代码补全中看到它们。

B. 单元测试

你可能不想把某些内部逻辑设为 public,但又想在测试 Target 中访问它们。使用 @testable import 只能在非二进制(源码)情况下工作,对于 XCFramework@_spi 是更好的选择。

C. 隐藏“实验性”API

你可以发布一些处于 Beta 阶段的接口,标记为 @_spi(Experimental)。只告知特定的合作伙伴使用,普通用户不会误触。


3. 在 XCFramework 中的表现

当你使用 Build Libraries for Distribution 编译 XCFramework 时,Swift 会生成 .swiftinterface 文件。

  • @_spi 标记的函数依然会出现在 .swiftinterface 中。
  • 但是,除非调用方显式写了 @_spi(GroupName) import ...,否则编译器会当这些函数不存在。
  • 这有效地减少了 SDK 的 API 污染

4. 注意事项与风险

  1. 非正式标准:下划线前缀 _ 意味着这是 Swift 的一个非正式、演进中的特性。虽然苹果自己在 Foundation 和 SwiftUI 中大量使用,但理论上未来可能发生语法变化。
  2. 安全性有限:这并不是一种加密手段。如果开发者通过查看你的 .swiftinterface 发现了组名,他们依然可以强行通过 @_spi(组名) 来调用。所以它主要是为了 “防止误用” 而不是 “防黑客”

总结

作为 SDK 开发者,把 @_spi 看作是一个 “带暗号的公共接口”。它能帮你完美平衡“多模块协作”与“保持对外接口整洁”之间的矛盾。

本文链接:

https://www.devorz.com/index.php/archives/spi.html
1 + 2 =
快来做第一个评论的人吧~