背景

有一些开发经验的同学们都知道,在 Swift 中我们可以通过创建 framework 或者通过 cocoapods 创建 pod 的方式新建一个Swift Module,当多个 Module 被编译到同一个工程中时,不会因为在不同 module 中拥有相同名称的类、枚举或结构体导致编译报错。因为 Swift 特有的隐式命名空间,会在最前面以<#module_name#>为前缀的方式为这个类命名,从而解决了我们自定义的类的重名问题。

问题

如果我们需要对已经定义好的公用类做扩展,例如UIButtonUIImage等,但是又担心命名与其他扩展重名

// ModuleA
extension UIColor {
  func mainColor() -> UIColor { /* ... */ }
}

// MoudleB
extension UIColor {
  func mainColor() -> UIcolor { /* ... */ }
}
  
import ModuleA
import ModuleB
// ModuleC
class C {
  // 此处该用谁?
  let color = UIColor.mainColor
}

此时我们可以通过自定义命名空间的方式来解决这个问题:

/// 此处以 KingFisher 实现的 `kf` 命名空间为例:

/**** 定义范型类 ****/

/// Wrapper for Kingfisher compatible types. This type provides an extension point for
/// convenience methods in Kingfisher.
public struct KingfisherWrapper<Base> {
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}

/**** 定义了两个范型协议,分别是 object 类型和 value 类型 ****/

/// Represents an object type that is compatible with Kingfisher. You can use `kf` property to get a
/// value in the namespace of Kingfisher.
public protocol KingfisherCompatible: AnyObject { }

/// Represents a value type that is compatible with Kingfisher. You can use `kf` property to get a
/// value in the namespace of Kingfisher.
public protocol KingfisherCompatibleValue {}


/**** 为协议提供默认实现,此处能够通过 `kf` 获得协议对象自身 ****/

extension MoudleNameCompatible {
    public var mn: ModuleNameWrapper<Self> {
        get { return ModuleNameWrapper(self) }
        set { }
    }
}

extension MoudleNameCompatibleValue {
    /// Gets a namespace holder for Kingfisher compatible types.
    public var mn: ModuleNameWrapper<Self> {
        get { return ModuleNameWrapper(self) }
        set { }
    }
}

按照类似的方式,如果我们分别为 ModuleAModuleB定义两个命名空间:mdlAmdlB,就能很好的区分两个不同对象了:

/// ModuleC
import ModuleA
import ModuleB
class C {
  let color1 = UIColor.mdlA.mainColor()
  let color2 = UIColor.mdlB.mainColor()
  // ...
}

实践

技术要用于实践才能更好地发挥其价值,正如本文所说的 Swift 命名空间,就有很多热门的三方 sdk 采用了类似的设计,例如 SnapKitRxSwiftKingFisher 等。

我们在开发的过程中也有很多可以用到的场景,例如在组件化开发时,一般会有一个项目通用组件,它一般用来放置整个工程通用的封装,例如颜色、字号等等。在实际开发中可能会出现部分组件使用的颜色或字号与通用组件的不一致,那么此时我们的通用组件如果采用了自定义命名空间,就不用担心子组件中的扩展名与其重名了,同时也能保证命名的统一,让开发人员一眼便知封装的含义。

参考链接