قالب وردپرس درنا توس
Home / Apple / Summarizes one thing in Swift – Ole Begemann

Summarizes one thing in Swift – Ole Begemann



New in Swift 4.2, the compiler can generate a collection of one-man's cases, and relieve you of the misunderstood task to maintain such a list yourself. The Swift Evolution proposal that introduced this feature is SE-0194.

For simple enumerations without associated values, all you need to do is comply with your enum for the new CaseIterable protocol. The compiler will then generate a static allCases property for you. This is the example from the standard library documentation:

  enum    CompassDirection :    CaseIterable    {
      case    north 
      case    south 
      case    case    east 
      case    west 
] 

  CompassDirection .   allCases    // → [north, south, east, west]
  CompassDirection .   allCases .    count    // → 4 

allCases is a collection so it has all the common features and attributes, such as count map and

 

     ] [19659033]   " ] 
    .   joined  (  separator :  ", ") 
 " north south , East, West "

Let's look at another example. When you work with iOS table views that have multiple sections, it's a common pattern to introduce an enum to represent the sections:

  enum    TableSection :    Int    {
      /// Section for Search Box 
      Scissors    Search    =    0 
      /// Selected Content 
      Item [19659000]]    =    1 
      / // Ordinary Content Cells 
      Case    Standard    =    2 
} 

In the overview of in the table view source and delegate methods and perform the appropriate action for that particular section.

(Page Description: I do not want to make this article a debate about the pros and cons of this approach. It definitely has its limits as the table gets more complicated and your swapping statements grows but it works well

Now, we find that we must return the number of items in your enum as the number of sections in the table view:

  func    numberOfSections  (  ] [19659000]]    
      
      return    ...    // number of cases.   :   ] UITableView )    ->    Int    How? 
} 

The traditional solution was to either encode the census or add another instance named sectionCount (or similar) to the end of the one. This raw material's final value will then be magically equal to the number of "real" things. No approach is satisfactory.

CaseIterable makes this much cleaner. Only decision protocol, no implementation required:

  extension    TableSection :    CaseIterable    {} 

Now we can return TableSection.allCases.count in Data Source Method.

Cases appear in the declaration order

SE 0194 does not prescribe a particular order of the values ​​in the collection allCases but the documentation for CaseIterable warrants it:

The collective allCases The collection gives the cases according to their statement.

This means that we can safely remove the valuation from the commodity from TableSection enum and use a case's position in the allCases sectional mapping index index and single case:
override [19659000] ] func tableView ( _ tableView : UITableView
     cell Proor indole exPath . IndexPath ) -> UITableViewCell
{
Med Parts = TableSection allCases [[19659019] indexPath section .]
break ~~ POS = HEADCOMP section {
case search search . [19659030] ...
case discussed . : ... [19659149]
}

Like other compiler synthesized conformations ( Equatable Hashable Encodable Decodeable – The list continues to grow) Automatically works code generation only when declaring the match in the same file where the type is defined (either on the actual capture definition or new in Swift 4.2, in the same file extension) . [19659152] The automatic code synthesis is really a feature for owners of one type, in order not to make retroactive conformities easier.

Of course, you can always declare the feedback feedback and provide a manual implementation, but it also means that you are responsible for maintaining it if new issues are added.

As a rule of thumb, think twice before you adopt CaseIterable (and other system protocols) for types you do not own, especially when it comes to types from Apple frames. Your code may break in one of two ways in the future:

  • when the original owner of the type addresses itself in a future release,
  • or when the original type changes in a way that violates your match, f instance. when a new case is added to the type. If you're lucky, the break will manifest itself in a compilation error on your side, but it's quite possible that it will break quiet and you find it difficult to find the error.

We have found that automatic synthesis works only for enums without associated values. This makes sense because adding ancillary values ​​to an enum makes the number of possible values ​​that one may have potentially infinite.

Ignore the use of having an infinite sequence of one type of possible values ​​for a moment, an infinite list is not anyway: the protocol requires allCases to return a collection and collections must be limited (although this may change in the future .

But as long as the list of all possible values ​​is final, we can always manually implement the protocol for Enums with associated values.

Example
Case
Case Case Case Case Medium
Case
Case
Case
Case
Case
Case
Case
Case
Case case
19659009] hard
]

enum

exercise {
case rest
case running ( Intensity )
case cycling ( Intensity tet )
}

(I know "resting" is not a true training type, but carry with me. I would have an example where not all cases have the same associated value.)

The compiler can synthesize a CaseIterable compliance for Intensity but not for Workout . Let's write one. We build a number of possible exercise values ​​starting with .resting and use every possible associated value for the other two cases:

  extension    Training : [19659004] CaseIterable    {
      Static    var    allCases :    [  training ]    {
          return    [[19659018].   rest ] 
              +    Intensity .   allCases .     exercise .   run ) 
              +    Intensity .   allCases . 
    training .   cycling ) [19659149]} 
} 

If you find map calls difficult to analyze, notice that a "nude" one thing without the corresponding values ​​corresponds to a constructor's function for the particular case. In other words, the type Workout.running (Intensity) -> Workout is a feature that gives a Workout value if you give it the desired Intensity . We send these designer features to map and get the arrays of Workout back.

This is the result:

  Training .   allCases .   count    // → 7 
  Training .   allCases 
  // → [runningrunning(running)running(medium)running(hard)
  // cycling (light), cycling (medium), cycling (hard)] 

manual maintenance required [19659090] The manual implementation is simple enough to write, but it has a big disadvantage compared to compiler generated code, remember to keep it updated. If we later add another training type to our enum, the compiler will not notify us that the implementation of allCases is no longer correct. It will only reset the wrong result.

The only solution I can think of is a bit ugly, but it solves this problem in a very interesting way. Let's add a local dummy feature inside the allCases body. The sole purpose of this feature is to replace exhaustively over our enum, so that the compiler will appeal to this source when we add another training type later. We can mark the feature as inaccessible and give it a parameter of the Never to prove we will never call it (since you can not create a value of the type Never a feature that takes one Never can not be called.)

  extension    Training :    CaseIterable    {
      static    var    allCases : [19659007] [  Training ]    {
          /// Dummyfunction whose sole purpose is to produce 
          /// an error when a new case is added to the training. Never call! 
          @ available (*, unavailable, message: "Out of control only, do not call") 
          func    _assertExhaustiveness  (  of    workout :    19659005]    Never .    Never )    
              Breaker    Workout    
              Case    [19659019] Rest  
                  ].   runs  (.   light ),  .   runs  (. [19659019] medium ),  .   runs  (.   difficult ), 
                 .   cycling  (. [19659019] light ),  .   cycling  (.   medium ),  .   cycling  (. [19659019] difficult ): 
                  shift 
            } 
        } 

          return    [.   rest ] 
              +    Intensity . [19659019] allCases . [19659000]]    exercise .   runs ) 
              +    Intensity .   allCases  ] 
]  [Obsesshvasomjernernårvileggertiletannetsaki Workout  enum: 

  Compilation time error at almost desired source location after adding a new enum case
Swift shows a compilation time error at almost the required source location after adding a new one case.

It's like an inline device test going on compilation time! You still need to remember to fix the actual allCases implementation, but not just the dummy switch sentence.

The names CaseIterable and allCases implies that this feature is specifically intended for enums. However, this does not mean that other types can not accept the protocol. In fact, such documentation describes CaseIterable :

A type that provides a collection of all its values.

This clearly indicates that any type that has a limited number of values ​​can adhere to. During the discussion of SE-0194 on Swift Evolution, many people favored name as ValueEnumerable and allValues ​​ to reflect this broader scope of the name. Finally, it was decided to derive the name from the primary use case:

The core team felt that these names reflect the primary use of this protocol, which provides better clarity for most code that deters over all cases.

Do Other Types CaseIterable

Nevertheless, we agree with some types of non-enum to CaseIterable . The simplest example is Bool :

  extension    Bool :    CaseIterable    {
      public    static    var    allCases  ]   ]       [  Boolean ]    {
          Return    [  false    true ] [19659353]} 
} 

  Bool .   allCases    // → [false, true] 

Some integer types are also a good match. Note that the return type of allCases does not have to be an array - there may be some Collection . It would be quite wasteful to generate a number of all possible integers when a range is enough:

  extension    UInt8 :    CaseIterable    {
      public    static    ]] was    allCases .    ClosedRange  <  uint8 >    {
          return      min    ... [19659030]    ] 
] 

  UInt8 .   allCases .   count    // → 256 

If your custom type has a Final number of values, but the values ​​are expensive to generate, consider returning a lazy collection in order not to perform unnecessary work in advance.

Potential Collection.count overflow

If we continue to customize integer types to CaseIterable we will eventually reach the boundaries of Swift's Collection ] protocol. The Collection is defined as an Int which overflows when we come to types with more than Int.max Citizens Int / UInt and larger).

You can still create collections that have nominally more items, so this is fine:

  la    hugeRange   ] [19659000]] .   min    ...  .   max 
  // → ClosedRange  
  // {nedreBound 0, upperBound 9223372036854775807} 

But as soon as you ask this collection for count (or more generally when You calculate a distance between two indices going over Int.max ) things will not end well.

Applications

You can ask when it is useful to have a collection of a type of residents? We have seen a classic usage case above in the table view example: The type represents a nice list of alternatives and we will represent these options in another domain, for example. like buttons in the user interface.

Another application is the need to generate arbitrary value of a particular type. Property-based test frames generate test cases by feeding "random" data into your code and claiming certain conditions. (Frequently, the data is not actually random, but machine-generated according to specific rules.)

The SwiftCheck test frame is based on a protocol called Random which defines an API for types to generate random data. In principle, this is very similar to CaseIterable although Arbitrary has different requirements: it also works for infinite types of strings, and it defines how a value may be shrunk ]. Once SwiftCheck has found an error test, it tries to reduce the value to the simplest form that still fails the test by shrinking it repeatedly.

Many generic types can adopt CaseIterable using conditional compliance, ie the ability to customize one type to a protocol when its generic parameters meet certain requirements (introduced in Swift 4.1).

For example, Optional may be CaseIterable as long as the optional generic type is by itself CaseIterable . In code, it looks like this (the explicit types are not required, but without this it is not compiled in current Swift 4.2 beta):

  extension    Optional :    CaseIterable    CaseIterable    CaseIterable    ]] where?      :    CaseIterable    
      public    typealias    AllCases    =    [  ] 
      public    ]                  :    AllCases    {
          Return    [  nil ]    +    Packed [19659018].   allCases . [19659000]]    {19659070] $ 0  } 
    } 
} 

The implementation of allCases starts with nil and adds Wrapped.allCases Collection to it (The mapping step is required to convert the items from Wrapped to Wrapped? ). I decided to put nil on the front page to follow the CaseIterable Convention on Listing Values ​​in Notification Order - . None comes before . Some in the default library source.

Optional adds a more value (namely .no or null ) to the underlying type of residents. In other words, Optional .allCases.count will always be similar to Wrapped.allCases.count + 1 :

  CompassDirection .   allCases [19659020] // → [north, south, east, west]
  CompassDirection .   allCases .   count    // → 4 
  CompassDirection ? .   allCases            [→19659020] // → 5 

Note that although CompassDirection?. allCases looks like optional chaining syntax, it's not! We can access a static member of the type called CompassDirection? . Alternatively we may have written Optional .allCases .

How useful is it to expand Optional like this? I'm not sure. It's probably not bad, but I can not think of any situation where I need this. The Swift core team seems to agree :

The core team discussed this when CaseIterable was introduced and concluded that it was not clearly useful.




Source link