Swift. Gg /2018/08/28/… By Andrew Jaffee translator: BigLuo Proofread: Numbbbbb,muhlenXi final text: CMB

Question 1: Can I write a Swift function to find the location \ index of any instance object of any type stored in any array?

Question 2: Can I write a Swift function that determines the type of any instance object of any type stored in any array?

When I say “any type,” I include custom types, such as our own Class type. Tip: I know I can use the built-in methods of the Swift Array type, such as Index and Contains, but today I’ll use simple code examples to illustrate some of the features in the Swift generics.

In general, I define generic programming as follows:

… a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters. This approach, pioneered by ML in 1973, permits writing common functions or types that differ only in the set of types on which they operate when used, thus reducing duplication.

A style of computer programming in which the algorithm mechanism is types to-be-specified-later, which instantiates the specific type when it is passed in as an argument. This method was pioneered by ML in 1973. A set of types can be represented by common functions and types to reduce the repetition of function operations.

In particular, from the Apple Swift documentation on the topic of “generics” :

Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.

Generics are one of the most powerful features of Swift , And much of the Swift Standard library is built with generic code…. For example, Swift’s Array and Dictionary types are both generic collections. You can create an Array that holds Int values, or an array that holds String values, or indeed an array for any other type that can be created in Swift . Similarly, you can create a dictionary to store values of any specified type, And there are no limitations on what that type can be…

Generic coding allows you to write flexible, reusable functions that fit requirements, support any type. You can write code that avoids repetition and that is abstract, clear, elegant in its programming style.

Generics are one of the most powerful features of Swift, and a large portion of the Swift standard library uses generic encoding. For example, Swift’s arrays and dictionaries are generic collections. You can create an array of integer values, string values, or, if necessary, any of the types supported by Swift. Similarly, you can create a dictionary to store values of any given type.

I’ve always advocated building reusable, concise, and maintainable code, and generics in Swift, if used properly, can help me achieve some of the effects mentioned above. So my answer to these two questions is “YES”.

Living in a world of specific types of coding

Let’s write a Swift method to say whether a particular string exists in an array of strings:

func existsManual(item:String, inArray:[String]) -> Bool
{
    var index:Int = 0
    var found = false
    
    while (index < inArray.count && found == false)
    {
        if item == inArray[index]
        {
            found = true
        }
        else
        {
            index = index + 1}}if found
    {
        return true
    }
    else
    {
        return false}}Copy the code

Let’s test this method:

let strings = ["Ishmael"."Jacob"."Ezekiel"]
 
let nameExistsInArray = existsManual(item: "Ishmael", inArray: strings)
// returns true
 
let nameExistsInArray1 = existsManual(item: "Bubba", inArray: strings)
// returns false
Copy the code

After creating the existsManual function to find an array of Strings. What if I decided I wanted something similar for searching Integer, Float, and Double arrays — or even for finding custom classes in arrays? I ended up spending precious time writing many functions that did the same thing. I have to write a lot of code to do that. What if I found a new/faster search algorithm? What if there’s a bug in my search algorithm? I had to change the versions of all my lookups. I found it a hell of reuse:

func existsManual(item:String, inArray:[String]) -> Bool.func existsManual(item:Int, inArray:[Int]) -> Bool.func existsManual(item:Float, inArray:[Float]) -> Bool.func existsManual(item:Double, inArray:[Double]) -> Bool.// "Person" is a custom class we'll create
// "Person" is the custom class we will create
func existsManual(item:Person, inArray:[Person]) -> Bool
Copy the code

The problem

We are tired of living in a world of processing types and having to create new methods for every array type we want to find. Ultimately, this left us with a lot of technical debt. Because of the incredible complexity of modern software, developers like you and me need to use better practices, better techniques, better methods to control the chaos as much as possible with our neurons. It is estimated that Windows 7 contains about 40 million lines of code and macOS 10.4 (Tiger) about 85 million lines of code, so it is impossible to estimate the number of potential behaviors on a system like this.

The solution to generics

(Again, keeping in mind the purpose of learning generics, we still assume that Swift’s array type built-in functions, index and Contains, do not exist.)

Let’s first try writing a Swift function that determines whether a particular instance of the Swift standard type (such as String, Integer, Float, or Double) exists in an array of the Swift standard type. So how do we do that?

Let’s switch to Swift generics, specifically generic functions, type parameters, type constraints, and the Equatable protocol. Before I define any terms, I wrote some code, and think about what you see.

func exists<T: Equatable>(item: T, inArray: [T]) -> Bool
{
    var index:Int = 0
    var found = false
    
    while (index < inArray.count && found == false)
    {
        if item == inArray[index]
        {
            found = true
        }
        else
        {
            index = index + 1}}if found
    {
        return true
    }
    else
    {
        return false}}Copy the code

Let’s test out my new generic method

let myFriends:[String] = ["John"."Dave"."Jim"]
 
let isOneOfMyFriends = exists(item: "Dave", inArray: myFriends)
// returns true
 
let isOneOfMyFriends1 = exists(item: "Laura", inArray: myFriends)
// returns false
 
let myNumbers:[Int] = [1.2.3.4.5.6]
 
let isOneOfMyNumbers = exists(item: 3, inArray: myNumbers)
// returns true
 
let isOneOfMyNumbers1 = exists(item: 0, inArray: myNumbers)
// returns false
 
let myNumbersFloat:[Float] = [1.0.2.0.3.0.4.0.5.0.6.0,]
 
let isOneOfMyFloatNumbers = exists(item: 3.0000, inArray: myNumbersFloat)
// returns true
Copy the code

I recently wrote that the EXISTS method is a generic function that “works on any parameter type.” In addition, let’s look at its function signature.

func exists<T: Equatable >(item: T, inArray: [T]) -> Bool
Copy the code

We see that the function uses a placeholder type name (called T, in this case) instead of the actual type name (e.g. : Int, Stirng, or Double) the placeholder type name does not specify what T must be, but it does specify that [item] and [inArray] must be of the same type. Whatever T stands for, whenever the [exists(_:_:)] function is called, The real type used to replace T is determined.

The placeholder type T in the exists function is called the type parameter:

It specifies and names the type of the placeholder, written directly after the function name, between a pair of Angle brackets (for example).

Once you specify a type parameter, you can use it to define the type of the function argument (e.g. [item] and [inArray] [exists(_:_:) function) or the type of the return value of the function. In any case, the type parameter will be replaced by the real type when the function is called.

To reinforce what we’ve learned so far, here’s a Swift function that finds the index of any type of instance stored in an array.

func find<T: Equatable>(item: T, inArray: [T]) -> Int?
{
    var index:Int = 0
    var found = false
    
    while (index < inArray.count && found == false)
    {
        if item == inArray[index]
        {
            found = true
        }
        else
        {
            index = index + 1}}if found
    {
        return index
    }
    else
    {
        return nil}}Copy the code

Let’s test it out

let myFriends:[String] = ["John"."Dave"."Jim"."Arthur"."Lancelot"]
 
let findIndexOfFriend = find(item: "John", inArray: myFriends)
// returns 0
 
let findIndexOfFriend1 = find(item: "Arthur", inArray: myFriends)
// returns 3
 
let findIndexOfFriend2 = find(item: "Guinevere", inArray: myFriends)
// returns nil
Copy the code

aboutEquatableagreement

What is the

annotation in the exists function? It’s called a type constraint, and it states that “that type parameter must inherit from a particular class or adhere to a particular protocol or combination of protocols. I specified the exists function parameters, item: T and inArray: [T], which must be of type T and which must comply with the Equatable protocol. Why is this so?

All Swift built-in types have been built to support the Equatable protocol. From the [Apple docs] (developer.apple.com/documentati… Swift/Equatable): “Compare equality with the type of Equatable protocol, using the equality operator (==) to determine equality, or using the inequality operator (! =) judgment is unequal. This is why my generic function “exists” works on Swift types such as String, Integer, Float, and Double. All of these types define == and! = operator.

Custom types and generics

Suppose I declare a new class called “BasicPerson” as follows. Can I use my exists function to find out if there is an instance of the “BasicPerson” class in the array? No way! Why not? Take a look at the following code, which we will discuss next:

class BasicPerson
{
    var name: String
    var weight: Int
    var sex: String
    
    init(weight: Int, name: String, sex: String)
    {
        self.name = name
        self.weight = weight
        self.sex = sex
    }
}
 
let Jim = BasicPerson(weight: 180, name: "Jim Patterson", sex: "M")
let Sam = BasicPerson(weight: 120, name: "Sam Patterson", sex: "F")
let Sara = BasicPerson(weight: 115, name: "Sara Lewis", sex: "F")
 
let basicPersons = [Jim.Sam.Sara]
 
let isSamABasicPerson = exists(item: Sam, inArray: basicPersons)
Copy the code

See the last line because it has a compilation error:

error: in argument type '[BasicPerson] ', 'BasicPerson' does not conform to expected type 'Equatable'
let isSamABasicPerson = exists(item: Sam, inArray: basicPersons)
Copy the code

This is too bad. In an array of type “BasicPerson”, you cannot use the Swift array’s built-in functions index and contains. You must define a closure whenever you want to use those two methods blah,blah,blah… I won’t mention that.)

So again, why do I get an error?

Because the “BasicPerson” class does not comply with the Equeatable protocol (see below for a hint)

Comply with theEquatableagreement

To allow my “BasicPerson” class to be able to use my “exists” and “find” generic methods, all I need to do is:

  • With our classEquatableagreement
  • Of an overloaded class instance= =The operator

Note that [this] (developer.apple.com/documentati… Swift/Equatable)” The Swift standard library provides all types that follow the Euqatable protocol that are not equal to (! Implementation of the =) operator. Get its inverse result by calling the custom == function.

If you’re not familiar with operator overloading, I recommend you read these topics, linked here and here. Trust me, you’ll want to know about operator overloads.

Tip: I renamed the “BasicPerson” class to “Person” so that they can coexist in the same Swift Playground file, and then we come to the “Person” class.

I’m going to implement the == operator, so it can compare the “name”, “weight”, and “sex” attributes between different instances of the “Person” class. If two instances of the “Person” class have the same three attributes. They are equal. If one attribute is different, they are not equal (! =). That’s why my “Person” class complies with the Equatable protocol:

lass Person: Equatable 
{
    var name:String
    var weight:Int
    var sex:String
    
    init(weight: Int, name: String, sex: String)
    {
        self.name = name
        self.weight = weight
        self.sex = sex
    }
    
    static func= =(lhs: Person, rhs: Person) -> Bool
    {
        if lhs.weight == rhs.weight &&
            lhs.name == rhs.name &&
            lhs.sex == rhs.sex
        {
            return true
        }
        else
        {
            return false}}}Copy the code

Note the == overload method above, which requires that “Person” comply with the Equatable protocol. Note the LHS and RHS arguments in the == overloaded method. This is general. When overloading the operator, the objects on either side of the medium sign should correspond to the physical position in the argument, as in:

lhs == rhs
left-hand side == right-hand side
Copy the code

Is it practical?

If you follow my guide, you can create generic functions like the “exists” and “find” I wrote for any new type you create, such as a class or structure. Lets you customize collection types of classes and structures that comply with the Equatable protocol, such as the index and Contains built-in functions in Swift Array. They do work:

let Joe = Person(weight: 180, name: "Joe Patterson", sex: "M")
let Pam = Person(weight: 120, name: "Pam Patterson", sex: "F")
let Sue = Person(weight: 115, name: "Sue Lewis", sex: "F")
let Jeb = Person(weight: 180, name: "Jeb Patterson", sex: "M")
let Bob = Person(weight: 200, name: "Bob Smith", sex: "M")
 
let myPeople:Array = [Joe.Pam.Sue.Jeb]
 
let indexOfOneOfMyPeople = find(item: Jeb, inArray: myPeople)
// returns 3 from custom generic function
// Return 3 from custom generic functions
 
let indexOfOneOfMyPeople1 = myPeople.index(of: Jeb)
// returns 3 from built-in Swift member function
// Returns 3 from the Swift built-in member function
 
let isSueOneOfMyPeople = exists(item: Sue, inArray: myPeople)
// returns true from custom generic function
// Return true from custom generic functions
 
let isSueOneOfMyPeople1 = myPeople.contains(Sue)
// returns true from built-in Swift member function
// Returns true from the Swift built-in member function
 
let indexOfBob = find(item: Bob, inArray: myPeople)
// returns nil from custom generic function
// Return nil from custom generic functions
 
let indexOfBob1 = myPeople.index(of: Bob)
// returns nil from built-in Swift member function
// Return nil from the Swift built-in member function
 
let isBobOneOfMyPeople1 = exists(item: Bob, inArray: myPeople)
// returns false from custom generic function
// Return false from custom generic functions
 
let isBobOneOfMyPeople2 = myPeople.contains(Bob)
// returns false from built-in Swift member function
// Returns false from the Swift built-in member function
 
if Joe= =Pam
{
    print("they're equal")}else
{
    print("they're not equal")}// returns "they're not equal"
Copy the code

Further reading

Apple tips on more benefits of the Equatable protocol:

Adding Equatable conformance to your custom types means that you can use more convenient APIs when searching for particular instances in a collection. Equatable is also the base protocol for the Hashable and Comparable protocols, which allow more uses of your custom type, such as constructing sets or sorting the elements of a collection.

Making your custom types compliant with the Equatable protocol means that you can use the apis provided by many systems to make it easier to find a particular instance in a collection.

The Equatable protocol is also the basis for the Hashable and Comparable protocols. This allows you to use more custom types, such as building collections or sorting elements in collections.

For example, if you comply with the Comparable protocol, you can reload and use the <, >, <= and >= operators, which is really Cool.

instructions

Consider our “Person” class, assuming we have some instances like the following:

let Joe = Person(weight: 180, name: "Joe Patterson", sex: "M")
let Pam = Person(weight: 120, name: "Pam Patterson", sex: "F")
let Sue = Person(weight: 115, name: "Sue Lewis", sex: "F")
let Jeb = Person(weight: 180, name: "Jeb Patterson", sex: "M")
let Bob = Person(weight: 200, name: "Bob Smith", sex: "M")
let Jan = Person(weight: 115, name: "Sue Lewis", sex: "F")
 
if Jan= =Sue
{
    print("they're equal")}else
{
    print("they're not equal")}// returns "they're equal" for 2 different objects
// Return "they're equal" for two different objects
Copy the code

Look at the last line, because the “Jan” and “Sue” objects in these “Person” objects are absolutely equal. Even though they are two different instance objects. Your software is only as good as your design. In database terminology, you would need a “primary key” in a collection of “Person” classes — maybe an index variable in the class design. Such as a social security code, or some other unique value you’re familiar with to ensure the uniqueness of an instance of the “Person” class in an Array, or, of course, the === operator.

Enjoy!