Swift means fast. Fast means comprehensible, simple. But to achieve simplicity and clarity is not easy: now in Swift the compilation speed is so-so, and even some moments of the language raise questions. Nevertheless, the possibility of enumerations (enum), about which I will tell (associated values), is one of the coolest. It allows you to reduce the code, make it clearer and more reliable.
Imagine that you need to describe the structure of payment options: in cash, by card, by gift certificate, by paper. Each payment method may have its own parameters. Implementation options under such weakly defined conditions - an infinite number, something can be grouped, something to redefine. You should not find fault, this structure lives here for example.
class PaymentWithClass { // true, , false , init() var isCash: Bool = false // .some, , -, - var cardNumber: String? var cardHolderName: String? var cardExpirationDate: String? // .some, var giftCertificateNumber: String? // - ( ) var paypalTransactionId: String? var paypalTransactionAuthCode: String? }
(Something a lot of opshnalov happened)
Working with such a class is hard. Swift interferes, because unlike many other languages, it will force all opshnals to be processed (for example, if let
or guard let
). There will be a lot of extra code that will hide business logic.
It is unpleasant and verified. It’s still possible to imagine a code that checks the validity of this class, but I don’t feel like writing anything like that.
Recall that in Swift there are value-types, which are called structures. They say they are good to use for this kind of model descriptions. We will try at the same time to break the previous code into several structures corresponding to different types of payment.
struct PaymentWithStructs { struct Cash {} struct Card { var number: String var holderName: String var expirationDate: String } struct GiftCertificate { var number: String } struct PayPal { var transactionId: String var transactionAuthCode: String? } var cash: Cash? var card: Card? var giftCertificate: GiftCertificate? var payPal: PayPal? }
It turned out better. The correctness of individual payment types is verified by the language itself (for example, it is clear that the PayPal authorization code may be missing, but all fields are required for the card), we only need to validate one (and only one) of the cash
, card
, giftCertificate
, payPal
- not null.
Conveniently? Full In this place in many languages you can put an end and consider the work done. But not in Swift.
Swift, like any modern language, provides the ability to create typed enums (enums). It is convenient to use them when you need to describe that a field can be one of several predefined values or inserted into a switch
to check that all possible options have been enumerated. In theory, in our example, the maximum that can be done is to insert a type of payment in order to more precisely validate the structure:
struct PaymentWithStructs { enum Kind { case cash case card case giftCertificate case payPal } struct Card { var number: String var holderName: String var expirationDate: String } struct GiftCertificate { var number: String } struct PayPal { var transactionId: String var transactionAuthCode: String? } var kind: Kind var card: Card? var giftCertificate: GiftCertificate? var payPal: PayPal? }
Notice that the Cash
structure is missing, it is empty and hung only to indicate the type that we have now explicitly entered. Got better? Yes, it became easier to check what type of payment we use. It is written explicitly, no need to analyze which field is not nil
.
The next step is to try to somehow get rid of the need for separate opshnals for each type. It would be cool if there was an opportunity to bind the appropriate structures to the type. To the card - the number and owner, to PayPal - the transaction identifier and so on.
For this purpose, Swift has associated values (associated values, not to be confused with associated types, which are used in protocols and not at all about that). They are recorded like this:
enum PaymentWithEnums { case cash case card( number: String, holderName: String, expirationDate: String ) case giftCertificate( number: String ) case payPal( transactionId: String, transactionAuthCode: String? ) }
The most important point is the exact type. It is immediately obvious that PaymentWithEnums
can take exactly one of four values, and each value may or may not have certain parameters as a date or transactionAuthCode
. It is physically impossible to put down the parameters immediately for both the card and the gift certificate, as this could be done in the variant with classes or structures.
It turns out that the previous versions require additional checks. It was also possible to forget to process any of the payment options, especially if there are new ones. Enums eliminate all these problems. If a new case is added, the next recompilation will require adding it to all switches. No opshnals except really necessary.
You can use such complex enums as usual:
if case .cash = payment { // - }
Parameters are obtained using pattern-matching:
if case .giftCertificate(let number) = payment { print("O_o ! : \(number)") }
And you can switch over all the options with a switch:
switch payment { case .cash: print(" !") case .card(let number, let holderName, let expirationDate): let last4 = String(number.characters.suffix(4)) print(", ! \(last4), : \(holderName)!") case .giftCertificate(let number): print("O_o ! : \(number)") case .payPal(let transactionId, let transactionAuthCode): print(" ! : \(transactionId)") }
As an exercise, you can write similar code for a variant with a class / structure. To complete the exercise, you need not be lazy and handle all the necessary options, including the erroneous ones.
Kayf. Swift. Another would be to compile quickly, and in general there will be happiness. :-)
Enums are not a panacea. Here are a few things you can meet.
First, enums cannot contain stored fields. If we want to add to PaymentWithEnums
, for example, the date of payment (the same field for all payment options), we get the error: enums may not contain stored properties
. How to be? You can put a date in each enum case. You can make the structure and put there enum and date.
Secondly, if ordinary enums can be compared using the ==
operator (it is synthesized automatically), then as soon as the associated values appear, the possibility of comparing “disappears”. This can be Equatable
easily by supporting Equatable
protocol. However, even after that, it will be inconvenient to compare, since it is impossible to simply write payment == PaymentWithEnums.giftCertificate
, you will need to create the right-hand part entirely: PaymentWithEnums.giftCertificate(number: "")
. It is much more convenient in this case to create special methods that return Bool ( isGiftCertificate(_ payment: PaymentWithEnums) -> Bool
), and transfer there if case
. If you need to compare multiple values, then perhaps the switch
will be more convenient.
Source: https://habr.com/ru/post/314156/
All Articles