Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to customize operators in Swift language

2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/01 Report--

This article mainly introduces how to customize the operator in the Swift language, which has a certain reference value, interested friends can refer to, I hope you can learn a lot after reading this article, the following let the editor take you to understand it.

In the Swift language, the common operators are +, -, *, /, >, = =, & &, | |, and so on. If you don't like it, you can define your favorite operator.

Digital container

Sometimes we define value types that are essentially containers that hold more primitive values. For example, in a strategy game, players can collect two kinds of resources-wood and gold coins. To model these resources in the code, I use the Resource structure as a container for wood and gold values, as follows:

Struct Resources {var gold: Int var wood: Int}

Whenever I refer to a set of resources, I use this structure-for example, to track the resources currently available to the player:

Struct Player {var resources: Resources}

One of the things you can spend resources on the game is to train new units for your army. When performing this kind of action, I only need to subtract the gold coin and wood cost of the unit from the current player's resources:

Func trainUnit (ofKind kind: Unit.Kind) {let unit = Unit (kind: kind) board.add (unit) currentPlayer.resources.gold-= kind.cost.gold currentPlayer.resources.wood-= kind.cost.wood}

The above is completely effective, but because there are many actions in the game that affect the player's resources, there are many places in the code that have to repeat the two subtractions of gold coins and wood.

Not only does this make it easy to forget to reduce one of the values, but it also makes it harder to introduce a new resource type (for example, silver coins) because I have to look at the entire code and update all the places where the resource is processed.

Operator overload

Let's try to solve the above problem by using operator overloading. When using operators in most languages, including Swift, you have two options: overload an existing operator or create a new one. Overloading works like method overloading, and you can create a new version of the operator with new input or output.

In this case, we will define the overload of the-= operator, which applies to two Resources values, as follows:

Extension Resources {static func-= (lhs: inout Resources, rhs: Resources) {lhs.gold-= rhs.gold lhs.wood-= rhs.wood}}

As with the Equatable protocol, operator overloading in Swift is just a normal static function that can be declared on a type. Here in-=, the left side of the operator is an inoiut parameter, which is the value we want to modify.

Through our operator overloading, we can now simply call-= directly on the current player's resources, just as we put it on any original value:

CurrentPlayer.resources-= kind.cost

Not only is this easy to read, it also helps us eliminate code duplication. Since we always want all external logic to modify the complete Resource instance, we can expose the gold coin gold and wood wood properties to other external classes as read-only properties:

Struct Resources {private (set) var gold: Int private (set) var wood: Int init (gold: Int, wood: Int) {self.gold = gold self.wood = wood}} another implementation method-variable function

Another way we can solve the Resources problem above is to use variable functions instead of operator overloading. We can add a function to reduce the property of the Resources value through another instance, as follows:

Extension Resources {mutating func reduce (by resources: Resources) {gold-= resources.gold wood-= resources.wood}}

Both solutions have their advantages, and you can argue that the variable function approach is more explicit. However, you don't want the standard subtraction API in mathematics to become: 5.reduce (by: 3), so maybe this is a perfect place for operator overloading.

Layout calculation

Let's take a look at another scenario where using operator overloading might be very good. Although we have automatic layout and a powerful layout API, we sometimes find ourselves needing manual layout calculations in some cases.

In such cases, it is very common and must perform mathematical operations on two-dimensional values-- such as CGPoint,CGSize and CGVector. For example, we might need to calculate the origin of the label by using the size of the image view and some extra margins, as follows:

Label.frame.origin = CGPoint (x: imageView.bounds.width + 10, y: imageView.bounds.height + 20)

Would it be nice if we could simply add them instead of having to always expand point and size to use their underlying components (as we did with Resources above)?

To be able to do this, we can overload the + operator to accept two CGSize instances as input and output the CGPoint value:

Extension CGSize {static func + (lhs: CGSize, rhs: CGSize)-> CGPoint {return CGPoint (x: lhs.width + rhs.width, y: lhs.height + rhs.height)}}

With the above code, we can now write down our layout calculation:

Label.frame.origin = imageView.bounds.size + CGSize (width: 10, height: 20)

It's cool, but it's a little weird to have to create a CGSize for our location. One way to make this a little better would be to define another + overload that accepts tuples containing two CGFloat values, as follows:

Extension CGSize {static func + (lhs: CGSize, rhs: (X: CGFloat, y: CGFloat))-> CGPoint {return CGPoint (x: lhs.width + rhs.x, y: lhs.height + rhs.y)}}

This allows us to write down our layout calculations in either of these two ways:

/ / use the tuple tag: label.frame.origin = imageView.bounds.size + (x: 10, y: 20) / / or leave it unwritten: label.frame.origin = imageView.bounds.size + (10,20)

That's very compact. Good! But now we are approaching the core issue that led to the operator debate-balancing redundancy and readability. Because we still deal with numbers, I think most people will find the above easy to read and understand, but as we continue to customize the use of operators, it becomes more complex, especially when we start introducing entirely new operators.

Custom operators that handle errors

So far, we have simply overloaded the operators that already exist in the system. However, if we want to start using operators that cannot really map to existing functionality, we need to define our own.

Let's look at another example. Swift's do,try,catch error handling mechanism is superb when dealing with synchronization operations that cannot be used. It allows us to easily and safely exit the function after an error occurs. For example, when loading a data model saved on disk:

Class NoteManager {func loadNote (fromFileNamed fileName: String) throws-> Note {let file = try fileLoader.loadFile (named: fileName) let data = try file.read () let note = try Note (data: data) return note}}

The only major drawback to making things like the above is that we throw any potential errors directly to the callers of our functionality, and we need to reduce the number of errors that API can throw, otherwise meaningful error handling and testing becomes very difficult.

Ideally, what we want is a limited number of errors that can be thrown by a given API, so that we can easily handle each case individually. Let's say we also want to catch all the potential mistakes and let us have all the good things at the same time. Therefore, we use explicit cases to define an error enumeration, and each error enumeration uses the associated value of the underlying error, as follows:

Extension NoteManager {enum LoadingError: Error {case invalidFile (Error) case invalidData (Error) case decodingFailed (Error)}}

However, it is tricky to catch potential errors and convert them to your own type. We must write down a similar standard error handling mechanism:

Class NoteManager {func loadNote (fromFileNamed fileName: String) throws-> Note {do {let file = try fileLoader.loadFile (named: fileName) do {let data = try file.read () do {return try Note (data: data)} catch {throw LoadingError.decodingFailed (error) } catch {throw LoadingError.invalidData (error)}} catch {throw LoadingError.invalidFile (error)}

I don't think anyone wants to read code like the one above. One option is to introduce a perform function that we can use to convert one error to another:

Class NoteManager {func loadNote (fromFileNamed fileName: String) throws-> Note {let file = try perform (fileLoader.loadFile (named: fileName), orThrow: LoadingError.invalidFile) let data = try perform (file.read (), orThrow: LoadingError.invalidData) let note = try perform (Note (data: data) OrThrow: LoadingError.decodingFailed) return note}} func perform (_ expression: @ autoclosure () throws-> T, errorTransform: (Error)-> Error) throws-> T {do {return try _ expression ()} catch {throw errorTransform (error)}}

It's a little better, but we still have a lot of errors in the conversion code that can confuse our actual logic. Let's see if introducing a new operator can help us clean up this code.

Add a new operator

Let's first define our new operator. In this case, we will choose ~ > as the symbol (there is an incentive to replace the return type, so we are looking for something similar to->). Since this is an operator that will work on both sides, we define it as infix, as follows:

Infix operator ~ >

What makes operators so powerful is that they can automatically capture the context on both sides of them. Combining this with Swift's @ autoclosure feature, we can create something really cool.

Let's implement ~ > as an operator that passes expressions and conversion errors, throwing or returning the same type as the original expression:

Func ~ > (expression: @ autoclosure () throws-> T, errorTransform: (Error)-> Error) throws-> T {do {return try _ expression ()} catch {throw errorTransform (error)}}

So what does the above operator allow us to do? Since enumerating static functions with associated values are also static functions in Swift, we can simply add the ~ > operator between our throwing expression and the error case, and we want to convert any underlying errors to the following form:

Class NoteManager {func loadNote (fromFileNamed fileName: String) throws-> Note {let file = try fileLoader.loadFile (named: fileName) ~ > LoadingError.invalidFile let data = try file.read () ~ > LoadingError.invalidData let note = try Note (data: data) ~ > LoadingError.decodingFailed return note}}

This is cool! By using operators, we have removed a lot of tedious code and syntax from our logic to make our code more focused. However, the disadvantage is that we have introduced a new error handling syntax that may be completely unfamiliar to any new developer who may join our project in the future.

Conclusion

Custom operators and operator overloading is a very powerful feature that allows us to build very interesting solutions. It allows us to reduce the verbosity of rendered function calls, which may give us clean code. However, it can also be a slide that can lead us to write secret and difficult-to-read code, which becomes very frightening and confusing for other developers.

Just like when using the first type of function in a more advanced way, I think you need to think twice before introducing new operators or creating additional overloads. Getting feedback from other developers can also be super valuable, and as a new operator, it feels completely different to you than to others. As with so many things, understand the tradeoff and try to choose the most appropriate tool for each situation.

Thank you for reading this article carefully. I hope the article "how to customize operators in Swift language" shared by the editor will be helpful to you. At the same time, I also hope you will support us and pay attention to the industry information channel. More related knowledge is waiting for you to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report