前置文档/参考资料
前言
我们通常希望对项目里的资源文件(如颜色/图片/字体)有一些函数能提供静态访问支持,以此获得 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) // 🎉