玩转 Swift 的 namespace

5,453 阅读3分钟

前置文档/参考资料

download.swift.org/docs/assets…

前言

我们通常希望对项目里的资源文件(如颜色/图片/字体)有一些函数能提供静态访问支持,以此获得 IDE 的上下文自动补全支持。

最简单的方式是在 UIColor/UIImage/UIFont 等资源类型上直接使用 extension 进行扩展实现,但是这种方法往往会带来命名冲突,导致业务方同时 import 两个基础组件时被迫使用full qualified name来解决,或者有时无法解决,成为编译时错误。

// Module A
extension UIImage { public static var like: UIImage { ... } }
struct Foo {}
// Module B
extension UIImage { public static var like: UIImage { ... } }
struct Foo {}
// Business Module C
import A
import B
UIImage.like // ❌
let a = A.Foo()
let b = B.Foo()
  • ObjectiveC 的思路是通过在命名上加入 namespace 来减少冲突的可能

  • Swift不鼓励在命名上加入namespace,因此我们可以通过 namespace 占位Wrapper类型来实现

下面介绍在 西瓜视频 Swift 中常见的几种 namespace 处理方法实践

实践

非 Protocol 类型

✅ 基于协议实现的Wrapper

如果需要在非Protocol类型上添加namespace是可以通过协议继承实现的

public struct XIGWrapper<Base> {
  public let base: Base
  public init(_ base: Base) {
    self.base = base
  }
}
public protocol XIGProtocol {}
public extension XIGProtocol {
  var xig: XIGWrapper<Self> {
    return XIGWrapper(self)
  }
  static var xig: XIGWrapper<Self>.Type {
    return XIGWrapper.self
  }
}

然后比如希望使用 Color.xig.red 和 xxColor.xig.opacity(xx) 就可以写出如下代码

extension Color: XIGProtocol {}
extension XIGWrapper where Base == Color {
  public func opacity(_ value: Double): Color { ... }
  public static var red: Color { ... }
}
// Usage
Color.xig.red
xxColor.xig.opacity(xx)

Protocol 类型

UIKit 框架采用 ObjectiveC + OOP 设计,大部分类型为 NSObject + class

SwiftUI 框架采用 Swift + POP 设计,大部分类型为 struct + protocol

❌ 基于协议实现的Wrapper

但是如果是带 Protocol 类型,如 View 和 ButtonStyle,事情就会有些不太一样

首先我们无法通过上面的extension方法来完成

extension View: XIGProtocol {} // ❌ Extension of protocol 'View' cannot have an inheritance clause

✅ 基于结构扩展的Wrapper

我们可以采用另一种方式来进行Wrapper

public struct XIG<Content> {
  let content: Content
  public init(_ content: Content) {
    self.content = content
  }
}
Instance 方法/属性

然后比如希望使用 Text("xx").xig.shadowStyle1 就可以写出如下代码

extension View {
  public var xig: XIG<Self> { XIG(self) }
  public static var xig: XIG<Self>.Type { XIG<Self>.self }
}

extension XIG where Content: View {
  public func shddowStyle1() -> some View{
    content.shadow(xx)
  }
}

// Usage
Text("xx").xig.shadowStyle1
Static 方法/属性

但是对于 static 方法推导目前没有找到合适的方法

传统上不用命名空间我们是这样编写

extension ButtonStyle where Self == BorderedButtonStyle {
  static var hello: some ButtonStyle { HelloButtonStyle() }
}

struct HelloButtonStyle: ButtonStyle { xx }
// Usage
Text("xx").buttonStyle(.hello)

但是这里我们仍然会报错

extension XIG where Content == BorderedButtonStyle {
  static func hello: some ButtonStyle { HelloButtonStyle() }
}
struct HelloButtonStyle: ButtonStyle { xx }
// Usage
Text("xx").buttonStyle(.xig.hello) // ❌ Contextual member reference to static property 'xig' requires 'Self' constraint in the protocol extension
Text("xx").buttonStyle(BorderedButtonStyle.xig.hello)
extension XIG where Content: ButtonStyle {
  static func hello: some ButtonStyle { HelloButtonStyle() }
}

struct HelloButtonStyle: ButtonStyle { xx }
// Usage
Text("xx").buttonStyle(.xig.hello) // ❌ Contextual member reference to static property 'xig' requires 'Self' constraint in the protocol extension
Text("xx").buttonStyle(BorderedButtonStyle.xig.hello)

一种临时解决方案

public struct XIGStatic<Content> {
    public init(_ content: Content.Type) {}
}
extension SwiftUI.ButtonStyle {
    public static var xig: XIGStatic<Self> { XIGStatic(Self.self) }
}

// Usage
public struct AnyButtonStyle: ButtonStyle {
    public func makeBody(configuration: Configuration) -> some View { ... } // Unimplement
}
extension ButtonStyle where Self == AnyButtonStyle {
    public static var xig: XIGStatic<AnyButtonStyle> { XIGStatic(AnyButtonStyle.self) }
}
struct HelloButtonStyle: ButtonStyle { ... }
extension XIGStatic where Content == AnyButtonStyle {
    var hello: some ButtonStyle { HelloButtonStyle() }
}
// Then we could final use the following code in business code 😂
Text("Hello").buttonStyle(.xig.hello) // 🎉

论坛讨论贴: forums.swift.org/t/namespace…