Thumbs up comments, feel useful friends can pay attention to the author public number iOS growth refers to north, continue to update

This is the 14th day of the Swift 100 Days series, which is a record of my Swift learning.

In our past discussions, we have mentioned the concept of generics many times. Generics are one of the most powerful features of the Swift programming language.

As a type-safe language, generics are a core feature of Swift — including its standard library, which also makes heavy use of generics. You should have a basic understanding of Swift’s types, classes, and protocols before learning about them. Array, Dictionary, and Set, mentioned in the complex data types in Swift, all use generics.

Why use generics

Automatic type generation is the problem that generics solve!

Generics are especially useful when we write code that can be applied to many different types.

In the section of Agreement and Development, we use agreement to deal with the sale operation of different types of products in the shopping mall. This is the implementation of the idea of generics.

protocol Purchaseable {
    // Product name
    var name: String { get set }
    / / discount
    var discount: Double { get}}struct Customer {
    var shoppingList = Array<Purchaseable> ()/ / purchase
    mutating func buy(_ product: Purchaseable) {
        self.shoppingList.append(product)
    }
}
Copy the code

If we do not follow the Purchaseable Agreement, we need to add for the user

struct Customer {
  var shoppingBookList = Array<Book> ()var shoppingClothesList = Array<clothes> ().
  // Buy books
  mutating func buyBook(_ book: Book) {
    self.shoppingBookList.append(book)
  }
  // Buy clothes
  mutating func buyclothes(_ clothes: clothes) {
    self.shoppingBookList.append(clothes),
  }
  .
}
Copy the code

In practice, a software development principle called Don’t Repeat Yourself (DRY) should be followed to reduce the number of duplicate code blocks in your code and applications.

We should reduce the number of duplicate code blocks as much as possible.

Let’s start using generics in our code!

Write structures that contain generics

Let’s define a structure, Container, that contains an arbitrary type and date object

struct Container<Value> {
    var value: Value
    var date: Date
}

let stringContainer = Container(value: "IOS growth is north", date: Date())
let intContainer = Container(value: 2000, date: Date())
let dateContainer = Container(value: Date(), date: Date())
print("stringContainer.value = ", stringContainer.value)
print("intContainer.value = ", intContainer.value)
print("dateContainer.value = ", dateContainer.value)
// stringcontainer. value = iOS growth indicates north
//intContainer.value = 2000
//dateContainer.value = 2021-01-07 09:49:34 +0000
Copy the code

Container is a generic type that has a type argument Value in its generic argument clause. Another way to say it is that Container is a generic type over Value. For example, Container

and Queue

will become their own concrete types at run time.

Value is called a placeholder type. This tells Swift that Value is not an actual type, but a placeholder in the Container

Write functions that contain generics

In the goods example, we created a shopping method for the consumer, which we changed to support generics

struct Customer<Element> {
    var shoppingList = Array<Element> ()/ / purchase
    mutating func buy(_ product: Element) {
        self.shoppingList.append(product)
    }
}
Copy the code

At this point, our type is arbitrary, but we need to make our type follow the Purchaseable agreement that we can

Use generic type constraints

This can be useful in some cases if you can add specific type constraints to generic functions or generic types. Type constraint Specifies that type parameters must inherit from the specified class and follow a specific protocol or protocol combination.

struct Customer<Element: Purchaseable> {
    var shoppingList = Array<Element> ()/ / purchase
    mutating func buy(_ product: Element) {
        self.shoppingList.append(product)
        print("product.name", product.name)
    }
}
Copy the code

When customizing generic types, you can define your own type constraints that provide more powerful generic programming capabilities.

We can do this if we use type constraints in our methods

struct Customer {
  / / purchase
    mutating func buy<Element: Purchaseable> (_ product: Element) {
        print("product.name", product.name)
    }
}
Copy the code

In addition to custom protocols, Swift provides the following basic protocols:

  • EquatableFor values that can be equal or unequal
  • ComparableOr comparable values, such as a> b
  • Hashable Used forCan be a hashIs the unique integer representation of the value (usually used for dictionary keys)
  • CustomStringConvertible For values that can be represented as strings, a useful protocol for quickly converting custom objects into printable strings
  • NumericSignedNumericExponential word values, for example42and3.1415
  • Strideable Values that can be offset and measured

Association types

The type of association in Swift and is usually closely related to the protocol.

You can think of them literally as an associative type of protocol: they are one from the moment you put them together.

The associated type can be thought of as an alternative to a specific type in the protocol definition. In other words: this is the placeholder name of the type to use before adopting the protocol and specifying the exact type.

The association type is specified by the associatedType keyword.

The following example defines an Identifiable agreement, which defines an Identifiable type ID:

protocol Identifiable {
    associatedtype ID: Equatable & CustomStringConvertible

    var id: ID { get}}Copy the code

And then we assigned him a different type of ID in the object that we had contrarily agreed on

struct Book: Identifiable {
    let id: String
}

struct clothes: Identifiable {
    let id: Int
}
Copy the code

When we add to the cart, we can determine the number of items we add to the cart based on whether the ID is the same or not. And each type of ID can have its own type, even declared.

By simplifying common interfaces for multiple scenarios, they prevent repetitive code from being written. This allows the same logic to be used for many different types, allowing the logic to be written and tested only once.

This is similar to the advantage of generics, right?

Combine the use of generics, protocols, and associated types

Define a Collection protocol Collection

protocol Collection {
    associatedtype Item: Equatable

    var count: Int { get }
    subscript(index: Int) -> Item { get }
    mutating func append(_ item: Item)
}
Copy the code

A new CollectionSlice protocol is then added to the collection protocol to get the first n pieces of the collection and ensure that the types are equal.

protocol CollectionSlice: Collection {
    associatedtype Slice: CollectionSlice where Slice.Item = = Item
    func prefix(_ maxLength: Int) -> Slice
}
Copy the code

Then we define a structure UppercaseStringsCollection follow Collection agreement

struct UppercaseStringsCollection: Collection {
    
    var container: [String] = []
  
    var count: Int { container.count }
  
    mutating func append(_ item: String) {
        guard !container.contains(item) else { return }
        container.append(item.uppercased())
    }
    
    subscript(index: Int) -> String {
        return container[index]
    }
}
Copy the code

Then a new one for UppercaseStringsCollection we follow CollectionSlice the expansion of the agreement

extension UppercaseStringsCollection: CollectionSlice {
    func prefix(_ maxLength: Int) -> UppercaseStringsCollection {
        var collection = UppercaseStringsCollection(a)for index in 0..<min(maxLength, count) {
            collection.append(self[index])
        }
        return collection
    }
}
Copy the code

The combined use of generics, protocols, and association types is common in Swift.

conclusion

In general, generics are a powerful feature that allows us to write code that is easier to reuse, while also enabling local specialization.

Algorithms, data structures, and utilities are often the best candidates for generics because they usually only need the types they use to satisfy a specific set of requirements, rather than being bound to a specific specific type.

Opaque type

After Swift 5.1, we can use another approach to generics: opaque types. The some keyword allows you to hide the specific return type of an attribute or function. The exact type returned can be determined by the implementation itself, not by the calling code. This is why opaque types are sometimes called reverse generics.

Opaque types are related to generics.

  • Use placeholders for generics, commonTThe type of the placeholder is determined by the caller of the functionTThe specific type of.
  • For opaque types, the function implementation determines the specific type.

We will follow our previous commodity example to illustrate the role of opaque types.

We define a buyer for the commodity agreement

protocol Purchaseable {
    associatedtype Buyer
    var buyer: Buyer { get }
    // Product name
    var name: String { get set}}Copy the code

Then we define two products: books and clothes

struct Book: Purchaseable {
    var buyer: String = "IOS growth is north"
    var name: String = "I am a book."
}

struct clothes: Purchaseable {
    var buyer: Int = 89757
    var name: String = "I am a piece of clothing"
}
Copy the code

Define a Courier. A Courier sends goods to a buyer, but it doesn’t need to know what the buyer is buying. It can be any person that meets the terms of the goods agreement.

/ / Courier
struct Courier {
    func delivery(a) -> some Purchaseable {
        return Book()}}Copy the code

This is where opaque types are used.

Generic type placeholders allow function callers to externalize types used in generic functions. The protocol type allows us to return any type from a function as long as it conforms to the protocol.

This does not apply to associated types, however, because of the lack of specific type information. Therefore, we use the some keyword to create opaque types, reverse generics, and let the implementation of the function determine the specific type of the return value as well as any associated types.

It’s tricky, isn’t it?

To sum up a few points

  • First, opaque types can use a protocol with an associated type as a return type.
  • Second, unlike protocol types, opaque types retain type identity.
  • Third, and last but not least, opaque types are crucial to SwiftUI — we’ll cover that when we learn about SwiftUI.

Digest today’s content. We’ll talk about that next time.

Thank you for reading this! 🚀