Swift 定义全局变量以及“带属性的全局变量对象”几种常见且符合官方推荐的最佳方式

5 阅读4分钟

一、 Swift 语言中的实现方式

Swift 没有像传统 C 语言那样真正的“全局变量对象”概念,但它通过全局常量/变量单利模式(Singleton)来实现相同的效果。Swift 的全局变量是懒加载(Lazy)的,并且在多线程下是线程安全的。

1. 全局直接定义(最简单)

直接在任何文件的最外层(不属于任何 Class 或 Struct)定义一个对象。这个对象可以拥有自己的属性。

import Foundation

// 定义一个拥有属性的类
class GlobalConfig {
    var apiEndpoint: String = "https://api.example.com"
    var timeoutInterval: TimeInterval = 30.0
}

// 方式 1:直接在文件最外层定义全局变量对象
let sharedConfig = GlobalConfig()

// 使用示例:
func checkConfig() {
    // 直接在任何地方访问和修改属性
    print(sharedConfig.apiEndpoint) 
    sharedConfig.timeoutInterval = 15.0
}

2. 结构体静态属性(Struct Static Property)

这是 Swift 中最推荐的方式,利用 static 关键字将变量绑定在命名空间(Struct 或 Class)下,避免全局命名空间污染。

struct AppTheme {
    // 静态属性:作为全局变量对象
    static var current = AppTheme()
    
    // 对象的具体属性
    var primaryColor: String = "#FFFFFF"
    var fontSize: Double = 16.0
}

// 使用示例:
func applyTheme() {
    // 通过 类型名.静态变量.属性 访问
    print(AppTheme.current.primaryColor)
    AppTheme.current.fontSize = 18.0
}

3. 标准单例模式(Singleton)

如果你希望这个全局对象只能有一个实例,不允许别人在其他地方单独 init 创建新对象,使用单例模式最严谨。

class AppManager {
    // 1. 唯一的全局访问点
    static let shared = AppManager()
    
    // 2. 全局变量对象的属性
    var isUserLoggedIn: Bool = false
    var userToken: String?
    
    // 3. 私有化初始化方法,防止外部再次 new 或 init
    private init() {}
}

// 使用示例:
func loginSuccess() {
    AppManager.shared.isUserLoggedIn = true
    AppManager.shared.userToken = "abc123xyz"
}

复习知识点 Class & Struct

1. 核心区别一:值类型 vs 引用类型(最本质的区别)

这是 structclass 最核心的差异,直接影响了全局变量的安全。

  • Struct 静态属性(值类型): 当你访问 AppTheme.current 并把它赋值给一个新变量时,Swift 会复制一份全新的副本。你在新变量上修改属性,不会影响全局的那个 AppTheme.current
  • Class 标准单例(引用类型): 无论你把 AppManager.shared 赋值给多少个变量,它们指向的都是同一个内存地址(堆内存) 。任何人在任何地方修改了属性,所有人看到的数据都会同步改变。

2. 核心区别二:唯一性的绝对限制(防呆机制)

  • Struct 静态属性: 虽然它有一个静态属性 static var current,但它无法阻止别人在项目的其他地方通过 let anotherTheme = AppTheme() 随意创建成百上千个新的主体对象。
  • Class 标准单例: 单例模式有一个关键动作——private init()(私有化初始化方法) 。这意味着,除了它自己,项目里任何其他地方写 let newManager = AppManager() 都会直接报错无法编译。它在语法层面上强制保证了整个 App 运行期间有且仅有一个实例

3. 核心区别三:多线程安全与数据竞争(Data Race)

  • Class 单例(更需注意线程安全): 因为所有人共享同一个实例,如果线程 A 正在写入 userToken,线程 B 同时在读取 userToken,就会发生数据竞争(Data Race)导致程序崩溃。在多线程高并发环境下,单例内部通常需要加锁(如使用 DispatchQueue 或 Swift 新特性的 actor)来保证安全。
  • Struct 静态属性(天然避免部分多线程问题): 由于结构体是值类型,在传递时会被复制,每个线程如果操作的是自己的副本,就天然避免了共享内存的冲突。但如果你直接并发去修改 AppTheme.current 这个全局静态变量本身,依然需要注意安全。
特性 / 需求Struct 静态属性 方案Class 标准单例 方案
底层类型值类型 (Value Type)引用类型 (Reference Type)
外部能否再次 new 新对象可以,无法限制绝对不行 (被 private init 封死)
赋值给新变量时的行为复制一份新数据 (Copy)传递指针,指向同一个内存 (Share)
最适合的场景只读的全局配置、UI主题颜色、字体大小等只读数据集状态管理、网络请求封装(NetworkManager)、数据库管理(DatabaseManager)等需要全局同步状态的管理器