Swift 一個優雅的原生類型擴展方式
自行封裝擴充方法,使其有 Namespace 的功能
Swift 一個優雅的原生類型擴展方式
[Swift] 一個優雅的 原生類型擴展方式
自行封裝擴充方法,使其有 Namespace 的功能
做法實際出處不詳,是從厲害同事的 Code 上學到的。
原生類型擴展
在日常的 iOS/Swift 開發中,我們常常需要對原生 API 進行擴充、撰寫自己的 Helper。
以下以擴充 UIColor 為例,我們希望擴充 UIColor 使其能轉換成 HEX Color String:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
extension UIColor {
/// Convert a UIColor to a hex string representation.
/// - Returns: A hex string (e.g., "#RRGGBB" or "#RRGGBBAA").
func toHexString(includeAlpha: Bool = false) -> String? {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
guard self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else {
return nil // Color could not be represented in RGB space
}
if includeAlpha {
return String(format: "#%02X%02X%02X%02X",
Int(red * 255),
Int(green * 255),
Int(blue * 255),
Int(alpha * 255))
} else {
return String(format: "#%02X%02X%02X",
Int(red * 255),
Int(green * 255),
Int(blue * 255))
}
}
}
直接對 UIColor 進行擴充 (Extension) 後的存取方式如下:
1
2
let color = UIColor.blue
color.toHexString() // #0000ff
問題
當我們自定義的擴充方式越來越多會讓存取介面開始變得混亂,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let color = UIColor.blue
color.getRed(...)
color.getWhite(...)
color.getHue(...)
color.getCMYK() // 自行擴充的方法
color.toHexString() // 自行擴充的方法
color.withAlphaComponent(...)
color.setFill(...)
color.setToBlue() // 自行擴充的方法
// A Module
public extension UIColor {
func getCMYK() {
// ...
}
}
// B Module
// Invalid redeclaration of 'getCMYK()'
public extension UIColor {
func getCMYK() {
// ...
}
}
我們自行擴充的方法跟原生提供的方法全部混合在一起,難以區分;另外專案規模越大、引用的套件越多也有可能遇到 Extension 命名衝突,例如兩個套件都對 UIColor Extension 也都叫 getCMYK()
就會有問題。
自訂擴展 Namespace 容器
我們可以運用 Swift Protocol, Computed Property 與 泛型的特型來自行封裝擴充方法,使其有 Namespace 的功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 宣告一個泛型容器 ExtensionContainer<Base>:
public struct ExtensionContainer<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
// 定義 for AnyObject, Class(Reference) Type 實現的接口:
// 例如 Foundation 中的 NSXXX 類別
public protocol ExtensionCompatibleObject: AnyObject {}
// 定義 for Struct(Value) Type 實現的接口:
public protocol ExtensionCompatible {}
// 自訂義 Namespace Computed Property:
public extension ExtensionCompatibleObject {
var zhg: ExtensionContainer<Self> {
return ExtensionContainer(self)
}
}
public extension ExtensionCompatible {
var zhg: ExtensionContainer<Self> {
return ExtensionContainer(self)
}
}
擴展原生類型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
extension UIColor: ExtensionCompatibleObject {}
extension ExtensionContainer where Base: UIColor {
/// Convert a UIColor to a hex string representation.
/// - Returns: A hex string (e.g., "#RRGGBB" or "#RRGGBBAA").
func toHexString(includeAlpha: Bool = false) -> String? {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
guard self.base.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else {
return nil // Color could not be represented in RGB space
}
if includeAlpha {
return String(format: "#%02X%02X%02X%02X",
Int(red * 255),
Int(green * 255),
Int(blue * 255),
Int(alpha * 255))
} else {
return String(format: "#%02X%02X%02X",
Int(red * 255),
Int(green * 255),
Int(blue * 255))
}
}
}
使用
1
2
let color = UIColor.blue
color.zhg.toHexString() // #0000ff
範例 2. URL .queryItems 擴充
1
2
3
4
5
6
7
8
9
10
extension URL: ExtensionCompatible {}
extension ExtensionContainer where Base == URL {
var queryParameters: [String: String]? {
URLComponents(url: base, resolvingAgainstBaseURL: true)?
.queryItems?
.reduce(into: [String: String]()) { $0[$1.name] = $1.value }
}
}
結合 Builder Pattern
另外我們也可以將此封裝方式結合 Builder Pattern 操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
final class URLBuilder {
private var components: URLComponents
init(base: URL) {
self.components = URLComponents(url: base, resolvingAgainstBaseURL: true)!
}
func setQueryParameters(_ parameters: [String: String]) -> URLBuilder {
components.queryItems = parameters.map { .init(name: $0.key, value: $0.value) }
return self
}
func setScheme(_ scheme: String) -> URLBuilder {
components.scheme = scheme
return self
}
func build() -> URL? {
return components.url
}
}
extension URL: ExtensionCompatible {}
extension ExtensionContainer where Base == URL {
func builder() -> URLBuilder {
return URLBuilder(base: base)
}
}
let url = URL(string: "https://zhgchg.li")!.zhg.builder().setQueryParameters(["a": "b", "c": "d"]).setScheme("ssh").build()
// ssh://zhgchg.li?a=b&c=d
有任何問題及指教歡迎 與我聯絡 。
===
本文首次發表於 Medium ➡️ 前往查看
This post is licensed under CC BY 4.0 by the author.