[Swift] An Elegant Native Type Extension Method
Encapsulating Extension Methods to Provide Namespace Functionality

The actual source of this approach is unclear; it was learned from a skilled colleague’s code.
Native Type Extensions
In daily iOS/Swift development, we often need to extend native APIs and write our own helpers.
Below is an example of extending UIColor to convert it into a HEX Color String:
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))
}
}
}
After directly extending UIColor, the access method is as follows:
let color = UIColor.blue
color.toHexString() // #0000ff
Problem
When we have more and more custom extensions, the access interface can become messy, for example:
let color = UIColor.blue
color.getRed(...)
color.getWhite(...)
color.getHue(...)
color.getCMYK() // Custom extended method
color.toHexString() // Custom extended method
color.withAlphaComponent(...)
color.setFill(...)
color.setToBlue() // Custom extended method
// A Module
public extension UIColor {
func getCMYK() {
// ...
}
}
// B Module
// Invalid redeclaration of 'getCMYK()'
public extension UIColor {
func getCMYK() {
// ...
}
}
Our custom extension methods get mixed together with the native methods, making them hard to distinguish. Also, as the project grows larger and more packages are used, extension name conflicts may occur. For example, if two packages both extend UIColor with a method called getCMYK(), it will cause issues.
Custom Extension Namespace Container
We can use Swift Protocols, Computed Properties, and Generics to encapsulate extension methods, giving them namespace functionality.
// Declare a generic container ExtensionContainer<Base>:
public struct ExtensionContainer<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
// Define protocol for AnyObject, Class (Reference) Type implementations:
// For example, NSXXX classes in Foundation
public protocol ExtensionCompatibleObject: AnyObject {}
// Define protocol for Struct (Value) Type implementations:
public protocol ExtensionCompatible {}
// Custom Namespace Computed Property:
public extension ExtensionCompatibleObject {
var zhg: ExtensionContainer<Self> {
return ExtensionContainer(self)
}
}
public extension ExtensionCompatible {
var zhg: ExtensionContainer<Self> {
return ExtensionContainer(self)
}
}
Extending Native Types
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))
}
}
}
Usage
let color = UIColor.blue
color.zhg.toHexString() // #0000ff
Example 2. URL .queryItems Extension
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 }
}
}
let url = URL(string: "https://zhgchg.li?a=b&c=d")!
url.zhg.queryParameters // ["c": "d", "a": "b"]
Combining with Builder Pattern
Additionally, we can combine this encapsulation method with the Builder Pattern:
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



Comments