编程高级iOS开发者

Swift中static和class属性是如何工作的,它们使用时有哪些潜在问题,以及如何实现线程安全的静态属性?

用 Hintsage AI 助手通过面试

回答。

Static和class属性是属于类型而不是实例的属性。它们的历史是为了存储所有实例共享的信息(例如,计数器、配置、工厂)。在Swift中,static可以用于所有类型(class、struct、enum),而class仅可用于类,并且仅适用于可计算的属性,从而允许在继承中重写。

**问题:**错误使用静态属性可能导致竞争条件和来自不同线程的访问错误。static和class容易混淆,可能错误地选择继承中的重写机制。

解决办法:

  • 对于不可变或明确线程安全的属性使用static。
  • 对于继承和重写,定义class属性。
  • 为了线程安全,将数据存储在私有静态存储中,并通过同步逻辑访问它们(例如,通过DispatchQueue)。

代码示例:

class Counter { static var count = 0 static let queue = DispatchQueue(label: "counter.queue") static func increment() { queue.sync { count += 1 } } class var typeDescription: String { return "Generic Counter" } } class NamedCounter: Counter { override class var typeDescription: String { return "Named Counter" } }

关键特点:

  • static不能被重写,class var可以。
  • static适用于所有类型,class仅适用于类。
  • 静态属性是每种类型的一份拷贝,可以在代码中的任何位置访问。

令人困惑的问题。

问题1:能否将static let属性声明为计算属性(computed property)并带有get?

是的,static属性可以是存储的也可以是计算的。对于let属性,这通常是常量,但static var可以是计算的:

struct Math { static var pi: Double { return 3.1415926 } }

问题2:static var默认线程安全吗?

不,如果static var从不同线程修改,可能会出现竞争状态。读取/写入需要手动同步。

问题3:可以使用class var用于存储属性吗?

不,class var必须始终是计算属性(带有get/可选set),存储属性仅允许用于static。

常见错误和反模式

  • 在多线程环境中将static var留为空的同步。
  • 尝试声明存储的class var。
  • 混淆static和class,在选择继承机制时出错。

生活中的示例

负面案例

在应用程序中,用户登录计数器存储在static var中,并在不同线程中未同步地增量。

优点:

  • 实现简单。

缺点:

  • 结果计数器有时提供不正确的值,增加了调试和缺陷跟踪的复杂性。

正面案例

对于全局配置对象,使用static let,并且对它的访问仅限于读取或使用DispatchQueue进行写入。

优点:

  • 没有竞争和可预测的行为。
  • 保持线程安全。

缺点:

  • 由于封装在队列中,代码体积稍微增加。