In the WooCommerce module, we generally work with immutable objects. Mutation only happens within Yosemite and Storage. This is an intentional design and promotes clarity of where and when those objects will be updated.
But in order to update something, we still need to pass an updated object to Yosemite. For example, to use the ProductAction.updateProduct
action, we'd probably have to create a new Product
object:
// An existing Product instance given by Yosemite
let currentProduct: Product
// Update the Product instance with a new `name`
let updatedProduct = Product(
productID: currentProduct.productID,
name: "A new name", // The only updated property
slug: currentProduct.slug,
permalink: currentProduct.permalink,
dateCreated: currentProduct.dateCreated,
dateModified: currentProduct.dateModified,
dateOnSaleStart: currentProduct.dateOnSaleStart,
// And so on...
)
let action = ProductAction.updateProduct(product: updatedProduct, ...)
store.dispatch(action)
This is quite cumbersome, especially since Product
has more than 50 properties.
To help with this, we generate copy()
methods for these objects. These copy()
methods follow a specific pattern and will make use of the CopiableProp
and NullableCopiableProp
typealiases.
Here is an example implementation on a Person
struct
:
struct Person {
let id: Int
let name: String
let address: String?
}
/// This will be automatically generated
extension Person {
func copy(
id: CopiableProp<Int> = .copy,
name: CopiableProp<String> = .copy,
address: NullableCopiableProp<String> = .copy
) -> Person {
// Create local variables to reduce Swift compilation complexity.
let id = id ?? self.id
let name = name ?? self.name
let address = address ?? self.address
return Person(
id: id
name: name
address: address
)
}
}
The copy()
arguments match the Person
's properties. For the Optional
properties like address
, the NullableCopiableProp
typealias is used.
By default, not passing any argument would only create a copy of the Person
instance. Passing an argument would replace that property's value:
let luke = Person(id: 1, name: "Luke", address: "Jakku")
let leia = luke.copy(name: "Leia")
In the above, leia
would have the same id
and address
as luke
because those arguments were not given.
{ id: 1, name: "Leia", address: "Jakku" }
The address
property, declared as NullableCopiableProp<String>
has an additional functionality. Because it is Optional
, we should be able to set its value to nil
. We can do that by passing an Optional
variable as the argument:
let luke = Person(id: 1, name: "Luke", address: "Jakku")
let address: String? = nil
let lukeWithNoAddress = luke.copy(address: address)
The lukeWithNoAddress
variable will have a nil
address as expected:
{ id: 1, name: "Luke", address: nil }
If we want to directly set the address
to nil
, we should not pass just nil
. This is because nil
is just the same as .copy
in this context. Instead, we should pass .some(nil)
instead.
let luke = Person(id: 1, name: "Luke", address: "Jakku")
// DO NOT
// Result will be incorrect: { id: 1, name: "Luke", address: "Jakku" }
let lukeWithNoAddress = luke.copy(address: nil)
// DO
// Result will be { id: 1, name: "Luke", address: nil }
let lukeWithNoAddress = luke.copy(address: .some(nil))
The copy()
methods are generated using Sourcery. For now, only the classes or structs in the WooCommerce, Yosemite, Networking, and Storage modules are supported.
To generate a copy()
method for a class
or struct
:
-
Make it conform to
GeneratedCopiable
.import Codegen struct ProductSettings: GeneratedCopiable { ... }
-
In terminal, navigate to the project's root folder and run
rake generate
.$ cd /path/to/root $ rake generate
This will generate separate files for every module. For example:
WooCommerce/Classes/Copiable/Models+Copiable.generated.swift Yosemite/Yosemite/Model/Copiable/Models+Copiable.generated.swift
-
Add the generated files to the appropriate project if they're not added yet.
-
Compile the project.
The rake generate
command executes the Sourcery configuration files located in the CodeGeneration
folder. There are different configuration files for every module:
Hardware module → Hardware-Copiable.sourcery.yaml
Networking module → Networking-Copiable.sourcery.yaml
Storage module → Storage-Copiable.sourcery.yaml
WooCommerce module → WooCommerce-Copiable.sourcery.yaml
Yosemite module → Yosemite-Copiable.sourcery.yaml
All of them use a single template, Models+Copiable.swifttemplate
, to generate the code. It's written using Swift templates.
Please refer to the Sourcery reference for more info about how to write templates.
- In Xcode target settings, add Codegen to the Xcode framework in General > Frameworks and Libraries.
- Add a new file
{{FrameworkName}}-Copiable.sourcery.yaml
underCodeGeneration/Sourcery/Copiable
similar to other yaml files in the same folder. - In
Rakefile
which includes the script forrake generate
command, add the new framework to the list of frameworks for Copiable generation similar to other frameworks. - In the new Xcode framework, add a new folder
Model/Copiable
in the file hierarchy. - Now you can try generating copy methods as instructed in an earlier section.
- In the new Xcode framework, add the newly generated file
Models+Copiable.generated.swift
under the new folderModel/Copiable
.