Skip to content

Copy functionality for immutable Swift structs using code generation

License

Notifications You must be signed in to change notification settings

davidscheutz/SwiftCopy

Repository files navigation

Supports iOS Supports macOS Supports watchOS SwiftPM Compatible License

SwiftCopy

A Swift Package that provides convenient copy functionality for immutable struct's in Swift using code generation.

Immutability - Why bother?

Thread-safety, as they can't be modified after creation.

Predictability, makes debugging and reasoning about state changes easier.

Essential for unidirectional data flow architecture and Functional Programming.

Usage

Simply conform your structs to the Copyable protocol.

struct Example: Copyable {
    let id: Int
    let input: String
    let isLoading: Bool
}

- Copy

Build your project and you a copy method will be generated.

let intial = Example(id: "123", input: "", isLoading: false)
// id: "123", input: "", isLoading: false

let updated = intial.copy(input: "hello")
// id: "123", input: "hello", isLoading: false 

let loading = updated.copy(isLoading: true)
// id: "123", input: "hello", isLoading: true

Optional Properties Support

SwiftCopy wraps optional properties using the OptionalCopy enum providing control over how to update or reset it's value.

Available options are:

update(T)
use(T?)
reset
noChange

Example:

struct User: Copyable {
    let id: Int
    let name: String
    let profilePicture: String?
}
let myUser = User(id: "123", name: "David", profilePicture: nil)

// Assign a value to `profilePicture`
_ = myUser.copy(profilePicture: .update("https://dave.com/pictures/214381"))

// Reset `profilePicture`
_ = myUser.copy(profilePicture: .reset)

// Copy the instance by using an optional type
let optionalValue: String? = "Value"
_ = myUser.copy(profilePicture: .use(optionalValue))

- Builder

Used to collect values required to instantiate your struct. Especially useful for object being created using user input.

struct OnboardingUser: Copyable {
    let firstName: String
    let lastName: String
    let username: String
    let dateOfBirth: Date
}

Compile your project and a Builder type will be generated for every type that confirms to Copyable.

Each builder is nested within it's associated type and can be used as followed:

Builder pattern

let builder = OnboardingUser.Builder()

let user = builder
    .with(firstName: "David")
    .with(lastName: "Scheutz")
    // ...
    .build()

Assign directly

let builder = OnboardingUser.Builder()

builder.firstName = "David"
builder.lastName = "Scheutz"
// ...
let user = builder.build()

Note: The build() method force unwraps optional types and therefore will crash if a required value is missing. To avoid this you can either use readyToBuild() to check first or use buildSafely(), which will throw an exception.

- Updater

Similar to the Builder, this object can be used to mutate an existing struct.

// Instantiate directly from Copyable instance
let updater = existingUser.updater()

// Instantiate using constructor
let updater = OnboardingUser.Updater(onboardingUser: existingUser)
// Usage via Builder pattern
let updatedUser = updater
    .with(firstName: "David New")
    ...
    .build()

// Usage via direct assignment
updater.firstName = "David"
...
let updatedUser = updater.build()

Note: The build() method is safe to use, as all values are present at all times, due to the Updated being instantiated with an existing object.

Observable

Both the Builder and the Updater implement ObservableObject. Additionally to the objectWillChange there is a objectDidChange subject, making it ideal to use within SwiftUI views as well as outside views.

Installation

1. Add Swift Package

You can use the Swift Package Manager to install SwiftCopy by adding it as a dependency to your Package.swift file:

dependencies: [
    .package(url: "git@github.com:davidscheutz/SwiftCopy.git", from: "1.2.0")
]

Make sure to add SwiftCopy as a dependency to your Target.

2. Add SwiftCopy CodeGeneratorPlugin as Build Tool Plugin

Select your Project -> Your Target -> Build Phases -> Add SwiftCopyCodeGeneratorPlugin (SwiftCopy)

Screenshot 2023-10-17 at 21 29 47

Demo Project

Feel free to take a look at the SwiftCopyDemo.xcodeproj to see the library in action. The usage is demonstrated using tests, which can be found in SwiftCopyDemoTests.swift file.

Contributing

Contributions to SwiftCopy are welcomed and encouraged!

It is easy to get involved. Open an issue to discuss a new feature, write clean code, show some love using unit tests and open a Pull Request.

A list of contributors will be available through GitHub.

PS: Check the open issues and pull requests for existing discussions.

License

SwiftCopy is available under the MIT license. See LICENSE for more information.

Credit

This project uses Sourcery for the code generation.