In use UIKit When , We will write a lot of similar code :
let imageView = UIImageView(image: image)
imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
imageView.backgroundColor = .white
imageView.alpha = 0.5
Copy code
But in SwiftUI in :
Image(uiImage: myImage)
.frame(width: 100, height: 100)
.background(Color.white)
.opacity(0.5)
Copy code
I wonder if your taste is the same as mine , I think the latter is more aesthetic than the former , More concise . The former needs to pass imageView
Object to set , It's old-fashioned . The chain style of the latter , Smooth and fast , No temporary variables are needed to manipulate this object , With one .
Ordinary thinking
If you want to achieve a similar chain style , We need to manually modify the corresponding properties .
for instance , If there is such a class :
class Scene {
var title: String?
var backgroudColor: UIColor?
}
Copy code
Scene
To achieve Chain , You need to add corresponding methods for these properties :
/// Every time the attribute is modified , The corresponding method should also be modified
extension Scene {
func title(_ title: String) -> Scene {
self.title = title
return self
}
func backgroundColor(_ color: UIColor) -> Scene {
self.backgroudColor = color
return self
}
}
Copy code
By sending back self
, Implement chain call :
Scene()
.title("Scene")
.backgroundColor(.yellow)
Copy code
This method , Quite passive , If Scene
Property changes , Then the corresponding setting method has to be changed . Proper labor costs .
But if Swift5.1 Above version , There is another option .
Dynamic Member Lookup
stay Swift4.2 At version time ,Swift Added Dynamic Member Lookup
, Dynamic member query . In the use of @dynamicMemberLookup
After marking the object ( object 、 Structure 、 enumeration 、protocol), Realized subscript(dynamicMember member: String)
Method, we can access properties that do not exist in the object . If the accessed property does not exist , Will be called to the implementation subscript(dynamicMember member: String)
Method ,key
As member
Pass in this method .
for instance , If the structure originally defined :
struct Persion {
var info: [String: Any]
}
Copy code
add to @dynamicMemberLookup
after :
@dynamicMemberLookup
struct Persion {
var info: [String: Any]
subscript(dynamicMember infoKey: String) -> Any? {
get {
return info[infoKey]
}
set {
info[infoKey] = newValue
}
}
}
Copy code
next , We can be like direct access Persion
The properties of the object are the same , access info
The content of :
var persion = Persion(info: [:])
persion.name = "Emilia"
print(persion.name)
Copy code
from Introduce User-defined "Dynamic Member Lookup" Types As can be seen from the proposal , This function is designed to work with Python And other dynamic languages .
But why with call chaining
associated , Because in Swift5.1
, This feature has been upgraded .
Key Path Member Lookup
stay Swift5.1 in , Besides strings , It can also be used. key path As a medium for dynamic member query .
Suppose we put Persion
The definition is as follows :
struct Person {
struct Info {
var name: String
}
var info: Info
}
Copy code
Then add Key path member lookup after :
@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 code
Now except through persion.info.name
To set the external , Sure :
// Syntax can highlight
var persion = Person(info: Person.Info(name: "helo"))
persion.name = "jackson"
print(persion.name)
Copy code
One of us when we knock persion.
When , There are grammatical hints .
This is because the compiler can start from Key path To query all the targets , And their types . Officially 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
}
}
}
// You can directly Wrapper<Scene> As a Scene To access properties
var scene2 = Wrapper(content: Scene())
scene2.title = "Scene"
Copy code
Chain conversion
From above Scene
Implementation of chained call , We can easily know , To achieve chaining is nothing more than after setting , return self.
@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
// hold value Assign to subject
subject[keyPath: keyPath] = value
// The type of return is Setter instead of Subject
// Because use Setter Come chain , instead of Subject In itself
return Setter(subject: subject)
}
}
}
Copy code
next , Just use any object instance with Setter
wrap up , You can set it by chain :
Setter(subject: Scene()) // packing Scene()
.title("Scene3") // Set title
.backgroudColor(.red) // Set the background
.subject // Read the last changed object
Copy code
Quickly rewrite UIKit Of UIView:
Setter(subject: UIView())
.frame(CGRect(x: 0, y: 0, width: 100, height: 100))
.backgroundColor(.white)
.alpha(0.5)
.subject
Copy code
Happily expanded UIView How to invoke , wow