When we use UIKit, we’ll write a lot of code like this:
let imageView = UIImageView(image: image)
imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
imageView.backgroundColor = .white
imageView.alpha = 0.5
Copy the code
But in SwiftUI:
Image(uiImage: myImage)
.frame(width: 100, height: 100)
.background(Color.white)
.opacity(0.5)
Copy the code
Do not know whether your grade is the same as me, feel latter has aesthetic feeling more than former, more concise. The former needs to be constantly set up through the imageView object, which is old-fashioned. The chain style of the latter is smooth and fast, without the need for temporary variables to manipulate the object, in one go.
The common thinking
To implement a similar chain style, we need to manually modify the corresponding properties.
For example, if you have a class like this:
class Scene {
var title: String?
var backgroudColor: UIColor?
}
Copy the code
Scene To implement the chain, we need to add corresponding methods for these attributes:
// every time a property is modified, the corresponding method is also modified
extension Scene {
func title(_ title: String) -> Scene {
self.title = title
return self
}
func backgroundColor(_ color: UIColor) -> Scene {
self.backgroudColor = color
return self}}Copy the code
By passing back self, the chain call is implemented:
Scene()
.title("Scene")
.backgroundColor(.yellow)
Copy the code
If the Scene property changes, then the corresponding setting method should also change. No problem with labor costs.
But if it’s Swift5.1 or above, there’s another option.
Dynamic Member Lookup
In Swift4.2, Dynamic Member Lookup was added to Swift. DynamicMemberLookup = dynamicMemberLookup = dynamicMemberLookup = dynamicMemberLookup = dynamicMemberLookup = dynamicMemberLookup = dynamicMemberLookup = dynamicMemberLookup = dynamicMemberLookup = dynamicMemberLookup If the accessed property does not exist, the implemented subscript(dynamicMember member: String) method is called, and the key is passed in as member.
For example, if the structure originally defined:
struct Persion {
var info: [String: Any]}Copy the code
Add @dynamicMemberLookup:
@dynamicMemberLookup
struct Persion {
var info: [String: Any]
subscript(dynamicMember infoKey: String) -> Any? {
get {
return info[infoKey]
}
set {
info[infoKey] = newValue
}
}
}
Copy the code
We can then access the contents of info as if we were accessing the properties of the Persion object directly:
var persion = Persion(info: [:])
persion.name = "Emilia"
print(persion.name)
Copy the code
Introduce user-defined “Dynamic Member Lookup” Types proposal this feature is designed to interact with Dynamic languages such as Python.
But why chained calls are involved is because in Swift5.1, this feature was updated.
Key Path Member Lookup
In Swift5.1, you can also use a key path as a medium for dynamic member queries in addition to strings.
Suppose we define Persion as follows:
struct Person {
struct Info {
var name: String
}
var info: Info
}
Copy the code
Add Key path Member lookup
@dynamicMemberLookup
struct Person {
struct Info {
var name: String
}
var info: Info
subscript<Value> (dynamicMember keyPath: WritableKeyPath<Info.Value>) -> Value {
get {
return info[keyPath: keyPath]
}
set {
info[keyPath: keyPath] = newValue
}
}
}
Copy the code
In addition to being set via persion.info.name, you can now:
// Syntax can be highlighted
var persion = Person(info: Person.Info(name: "helo"))
persion.name = "jackson"
print(persion.name)
Copy the code
Where we have syntax prompts when we type persion.
This is because the compiler can query all the targets and their types from the Key path. Because of this, it is very suitable for packaging types:
@dynamicMemberLookup
struct Wrapper<Content> {
var content: Content
subscript<Value> (dynamicMember keyPath: WritableKeyPath<Content.Value>) -> Value {
get {
return content[keyPath: keyPath]
}
set {
content[keyPath: keyPath] = newValue
}
}
}
// We can access properties directly from Wrapper
as Scene
var scene2 = Wrapper(content: Scene())
scene2.title = "Scene"
Copy the code
Chain transformation
From the implementation of the Scene chain call above, we can easily see that all we need to do to implement the chain is return self when we’re done.
@dynamicMemberLookup
struct Setter<Subject> {
let subject: Subject
subscript<Value> (dynamicMember keyPath: WritableKeyPath<Subject.Value>)- > ((Value) - >Setter<Subject>) {
// Get the real object
var subject = self.subject
return { value in
// Assign value to subject
subject[keyPath: keyPath] = value
// The type of return is Setter instead of Subject
// Because setters are used to chain, not Subject itself
return Setter(subject: subject)
}
}
}
Copy the code
Then, any object instance can be set chaining by wrapping it in a Setter:
Setter(subject: Scene()) / / packaging Scene ()
.title("Scene3") // Set the title
.backgroudColor(.red) // Set the background
.subject // Read the last changed object
Copy the code
Quickly rewrite UIView for UIKit:
Setter(subject: UIView())
.frame(CGRect(x: 0, y: 0, width: 100, height: 100))
.backgroundColor(.white)
.alpha(0.5)
.subject
Copy the code
Nice extension of UIView call method, 🐱 wow