ZhgChg.Li

Swift Native Type Extensions|Elegant Namespace Implementation for Cleaner Code

Discover how to create elegant native type extensions in Swift by encapsulating methods with namespace functionality, improving code organization and maintainability for iOS developers.

Swift Native Type Extensions|Elegant Namespace Implementation for Cleaner Code

[Swift] An Elegant Native Type Extension Method

Independent writing, free to read — please support these ads

 

Advertise here →

Encapsulating Extension Methods to Provide Namespace Functionality

<https://www.swift.org/>

https://www.swift.org/

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

Independent writing, free to read — please support these ads

 

Advertise here →

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

Independent writing, free to read — please support these ads

 

Advertise here →

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
Improve this page
Edit on GitHub
Also published on Medium
Read the original
Share this essay
Copy link · share to socials
ZhgChgLi
Author

ZhgChgLi

An iOS, web, and automation developer from Taiwan 🇹🇼 who also loves sharing, traveling, and writing.

Comments