The introduction

Continuing with the Swift documentation, from the previous chapter on error Handling, we learned about Swift error handling using throwing, throwing, using do catches to handle errors, and converting errors to optional values (using try?). , disable error passing (use try!) , deferred action processing (use the defer keyword), and so on. Now, let’s learn about Swift type conversions. Due to the long space, here is a section to record, next, let’s begin!

Next chapter: Nested types

Type conversion

A cast is a way of checking the type of an instance, or treating the instance as a different parent or subclass from elsewhere in its own class hierarchy.

Type conversions in Swift are implemented through the IS and AS operators. These two operators provide a simple and expressive way to check the type of a value or to convert a value to a different type.

You can also use type conversions to check for protocol compliance, as described in Checking protocol Conformance.

Define the class hierarchy for type conversions

Type casting can be used with hierarchies of classes and subclasses to check the type of a particular class instance and cast that instance to another class in the same hierarchy. The following three code snippets define a hierarchy of classes and an array containing instances of those classes for use in the conversion example.

The first code snippet defines a new base class, MediaItem. This class provides basic functionality for any type of project that appears in a digital media library. Specifically, it declares a String name property and an init name initializer. (Assume that all media projects, including all movies and songs, have a title.)

class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}
Copy the code

The next fragment defines two subclasses of MediaItem. The first subclass, Movie, encapsulates additional information about a Movie or Movie. It adds a director property to the top of the base MediaItem class using the appropriate initializer. The second subclass, Song, adds an artist attribute and initializer at the top of the base class:

class Movie: MediaItem {
    var director: String
    init(name: String, director: String) {
        self.director = director
        super.init(name: name)
    }
}

class Song: MediaItem {
    var artist: String
    init(name: String, artist: String) {
        self.artist = artist
        super.init(name: name)
    }
}
Copy the code

The final code snippet creates an array of constants called Library that contains two instances of Movie and three instances of Song. The type of the Library array is inferred by using the content initialization of the array text. Swift’s type checker can infer that Movie and Song have a common MediaItem parent, so it infer the [MediaItem] type of the Library array:

let library = [
    Movie(name: "Casablanca", director: "Michael Curtiz"),
    Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
    Movie(name: "Citizen Kane", director: "Orson Welles"),
    Song(name: "The One And Only", artist: "Chesney Hawkes"),
    Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
// the type of "library" is inferred to be [MediaItem]
Copy the code

The items stored in the Library are still behind the scenes instances of Movie and Song. However, if you iterate over the contents of this array, the returned item will be typed MediaItem instead of Movie or Song. In order to use them as their own types, you need to check their types or delegate them to other types, as described below.

2 Check Type

Use the type checking operator (IS) to check whether an instance belongs to a subclass type. The type-checking operator returns true if the instance is of that subclass type; If not, return false.

The following example defines two variables, movieCount and songCount, to count the number of Movie and Song instances in the library array:

var movieCount = 0
var songCount = 0

for item in library {
    if item is Movie {
        movieCount += 1
    } else if item is Song {
        songCount += 1
    }
}

print("Media library contains \(movieCount) movies and \(songCount) songs")
// Prints "Media library contains 2 movies and 3 songs"
Copy the code

This example iterates over all items in the Library array. On each pass, the for-in loop sets the item constant to the next MediaItem in the array. Item is Movie Returns true if the current MediaItem is the Movie instance; If not, return false. Similarly, item is Song checks if item is an instance of Song. At the end of the for-in loop, the values of movieCount and songCount contain the count of MediaItem instances found for each type.

3 Downcasting

Constants or variables of a class type may actually reference instances of subclasses behind the scenes. In this case, you can try using the type conversion operator (as? Or as!) .

Because downcasting can fail, there are two different forms of the type conversion operator. Conditional forms, like what? Returns the optional value of the type to be converted down to. Forms of coercion, like! , try to push down and force the result to expand into a single compound operation.

Use the type conversion operator (as?) Conditional form when you are not sure whether the downward transition will be successful. This form of operator will always return an optional value, which will be zero if no downward transformation is possible. This enables you to check if the downward transition has been successfully performed.

Use the forced form of the type conversion operator (as!) Only if you are sure that the downward transition will always be successful. This form of operator will trigger a runtime error if you try to cast down to an incorrect class type.

The following example iterates through each MediaItem in the library and prints the appropriate description for each item. To do this, it needs to access each item as a real Movie or Song, not just MediaItem. This is required so that it can access the director or artist attribute of Movie or Song for use in the description.

In this case, each item in the array could be Movie or Song. You don’t know in advance which actual class to use for each item, so use the type conversion operator (as?) The condition form is appropriate in the loop for each check down transition:

for item in library {
    if let movie = item as? Movie {
        print("Movie: \(movie.name), dir. \(movie.director)")
    } else if let song = item as? Song {
        print("Song: \(song.name), by \(song.artist)")
    }
}

// Movie: Casablanca, dir. Michael Curtiz
// Song: Blue Suede Shoes, by Elvis Presley
// Movie: Citizen Kane, dir. Orson Welles
// Song: The One And Only, by Chesney Hawkes
// Song: Never Gonna Give You Up, by Rick Astley
Copy the code

The example first tries to transition the current item down as a movie. Since item is an instance of MediaItem, it could be Movie; Again, it could be a Song, or even just a basic mediaItem. Because of this uncertainty, as? The form of the type conversion operator returns an optional value when attempting to cast down to a subclass type. The project results are as? Movie is Movie? “Or” Optional Movie “.

The downward transition to Movie fails when applied to Song instances in the Library array. To solve this problem, the example above uses the optional binding to check if the optional Movie really contains a value (that is, to determine if the downward transition was successful) “if let Movie = item as? Movie “, can be interpreted as:

“Try to access item as Movie. If successful, set a new temporary constant named Movie to the value stored in the returned optional movie. “If the downward transition succeeds, the movie property will be used to print a description of the Moview instance, including its director name. A similar principle is used to examine the Song instance and print the appropriate description (including artist’s name) when the Song is found in the library.

Pay attention to

The cast does not actually modify the instance or change its value. The underlying instance remains the same; It is only processed and accessed as an instance of the type to which it was cast.

4 Type conversion of Any and AnyObject

Swift provides two special types to handle non-specific types:

  • Any can represent instances of Any type, including function types.
  • AnyObject can represent an instance of any class type.

Use Any and AnyObject only when you clearly need the behavior and functionality they provide. It is best to specify the type you want to use in your code. Here is an example of using Any to handle different types, including function and non-class types. This example creates an array called things that can store values of type Any:

Var things = [Any](0) things. Append (0.0) things. Append (42) things. Append (3.14159) things. Append ((3.0, 5.0)) things. Append (Movie(name: "Ghostbusters", director: "Ivan Reitman")) things. String) -> String in "Hello, \(name)" })Copy the code

The Things array contains two ints, two doubles, a string value, a tuple type (Double, Double), movie “Ghostbusters”, and a closure expression that takes a string value and returns another string value. To discover a particular type of constant or variable that only Any or AnyObject type is known, you can use the IS or AS schema in the case of switch statements. The following example iterates over items in the THINGS array and queries the type of each item using the Switch statement. Some cases of switch statements bind their matching value to a constant of the specified type in order to print its value:

for thing in things {
    switch thing {
    case 0 as Int:
        print("zero as an Int")
    case 0 as Double:
        print("zero as a Double")
    case let someInt as Int:
        print("an integer value of \(someInt)")
    case let someDouble as Double where someDouble > 0:
        print("a positive double value of \(someDouble)")
    case is Double:
        print("some other double value that I don't want to print")
    case let someString as String:
        print("a string value of \"\(someString)\"")
    case let (x, y) as (Double, Double):
        print("an (x, y) point at \(x), \(y)")
    case let movie as Movie:
        print("a movie called \(movie.name), dir. \(movie.director)")
    case let stringConverter as (String) -> String:
        print(stringConverter("Michael"))
    default:
        print("something else")
    }
}

// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called Ghostbusters, dir. Ivan Reitman
// Hello, Michael
Copy the code

The Any type represents Any type of value, including optional types. Swift warns you if you use optional values in cases where a value of type Any is required. If you really need to use the optional value as Any, you can use the AS operator to explicitly convert the optional value to Any, as shown below.

let optionalNumber: Int? = 3
things.append(optionalNumber)        // Warning
things.append(optionalNumber as Any) // No warning
Copy the code

conclusion

This chapter mainly talks about the syntax implementation of type conversion, mainly involving:

  • as! andas?Syntax for Downcasting two types
  • You can useisKeywords for type checking
  • Type conversion of Any and AnyObject

Ok, the content of this section is relatively simple, the friends who have already understood can re-consolidate it, there is a harvest, trouble to point a praise oh ~ thank you!

Previous section: Error handling

Next chapter: Nested types

See Swift-type Casting