Skip to content

arasan01/swift-dependencies-extras

Repository files navigation

Dependencies Protocol Extras

Library to make swift-dependencies even more useful when using Protocol.

Table of Contents

Overview

Xcode's support for protocol-oriented declarations and implementations is very strong. However, as we understood in the Point-Free episode, we pay a price for this in exchange for some flexibility. So is there a way to move things forward while getting the benefits of both? Yes, that is macros! Macros allow us to automatically convert protocol-based to struct-based. This is an approach that works very well. By depositing the implementation in the macro, the protocol becomes an entity to be assisted by Xcode, and is internally converted into a function call of the implementation.

This is a library that extends swift-dependencies, swift-dependencies is here.

https://github.com/pointfreeco/swift-dependencies

Quick start

Look at this first. Swift The power of Macro makes it possible to cut out and rewrite a single function, even if it is implemented in a protocol.

import DependenciesExtrasMacros
import Foundation

@DependencyProtocolClient(implemented: ProtocolPersistentImpl.self)
protocol ProtocolPersistent: Sendable {
    func load(_ url: URL) throws -> Data
    func save(_ data: Data, _ url: URL) async throws -> Void
}

public final class ProtocolPersistentImpl: @unchecked Sendable, ProtocolPersistent {
    func load(_ url: URL) throws -> Data { try Data(contentsOf: url) }
    func save(_ data: Data, _ url: URL) async throws -> Void { try data.write(to: url) }
}

extension DependencyValues {
    #DependencyValueRegister(of: ProtocolPersistent.self, into: "protocolPersistent")
}

struct Runner {
    @Dependency(\.protocolPersistent) var protocolPersistent

    func run() async throws {
        do {
            let new = withDependencies {
                $0.protocolPersistent.save = { data, url in debugPrint(data, url) }
            } operation: {
                protocolPersistent
            }
            try await new.save("struct".data(using: .utf8)!, URL.documentsDirectory.appendingPathComponent(UUID().uuidString, conformingTo: .text))
        }
    }
}

The first step is to give the already existing protocol a macro that informs the world that this will be used for dependency resolution.

+ @DependencyProtocolClient(implemented: ProtocolPersistentImpl.self)
  protocol ProtocolPersistent: Sendable {
    func load(_ url: URL) throws -> Data
    func save(_ data: Data, _ url: URL) async throws -> Void
  }

Next, register a structure in DependencyValues that conforms to the DependencyKey generated by the macro

+ extension DependencyValues {
+   #DependencyValueRegister(of: ProtocolPersistent.self, into: "protocolPersistent")
+ }

This ends the need for us to be hands on! This is the only way things will start to work. If it doesn't work or has behavior you don't expect, please give us feedback. We welcome your contribution!

Documentation

The latest documentation for the Dependencies APIs is available here.

Installation

You can add Dependencies to an Xcode project by adding it to your project as a package.

https://github.com/arasan01/swift-dependencies-extras

If you want to use Dependencies in a SwiftPM project, it's as simple as adding it to your Package.swift:

dependencies: [
  .package(url: "https://github.com/arasan01/swift-dependencies-extras", from: "0.1.0")
]

And then adding the product to any target that needs access to the library:

.product(name: "DependenciesExtrasMacros", package: "swift-dependencies-extras"),

Now on provide.

Dependencies Extras Macros

Automatically rewrite the conventional design consisting of Protocol, Class, and Struct based on swift-dependencies in PointFree style, allowing rewriting of each implemented function one by one.

License

This library is released under the MIT license. See LICENSE for details.