• 20 hours
  • Medium

Free online content available in this course.

Videos available in this course

Certificate of achievement available at the end this course

Got it!

Last updated on 5/16/19

Manage types

Log in or subscribe for free to enjoy all this course has to offer!

We are now able to create a perfectly structured tree of classes with an optimized base class and subsequent subclasses according to any particular need.

This may generate a situation when we are using objects of different types which have the same base (super class) and therefore could be considered of the same type.

Let’s look at an example. Say we have a Media class and a couple of subclasses, such as Movie and Song. Both of them are media and share some characteristics (say a title and duration) and each have their own unique ones at the same time (for example, a producer for a movie and a artist for a song):

class Media {
    var title: String
    var duration = 0.0
    
    init(title: String) {
        self.title = title
    }
}

class Movie: Media {
    var producer = ""
}

class Song: Media {
    var artist = ""
}

And then, say, we’ve got a library of movies and songs mixed together (we could store those in an array):

var library = [Movie(title:"The best movie"), Song(title:"The best song")]

When we walk/browse that array and want to do something with it, we need to know which exact Media subclass each element refers to - a movie or a song. For example, if we want to play it, we need to know which player to pass it to - video or audio. To resolve this, we need to check the type.

Checking the type

Checking the type means verifying if an object is of a certain class. This is done by using the  is   keyword:

for media in library {
    if media is Song {
        print("Audio player is required")
    }
    else if media is Movie {
        print("Video player is required")
    }
}

This works great as long as we don't need to access elements specific to a subclass, like the artist property for songs or producer for movies. If we try to, we'll get an error:

for media in library {
    if media is Song {
        print(media.artist) // Error
    }
    else if media is Movie {
        print(media.producer) // Error
    }
}

To resolve this, we need to make an object act as subclass.

Type casting

We can present an object as a subclass. To do so we use the modifier  as  :

for media in library {
    if let song = media as? Song {
        print(song.artist) // Ok
    }
    else if let movie = media as? Movie {
        print(movie.producer) // Ok
    }
}

We create a new constant of a subtype in each if statement and assign our media object to it. We can then proceed by using that constant the way we planned to, because now it's of the appropriate type.

 What are the question marks up there?

You noticed that we are using  as?   not just  as . That's because we have to treat a new variable as an optional.

Remember what optionals are?

Optionals are values that may or may not be there :waw:.

The only alternative to treating it as an optional (which is what we're doing) is treating it as a non-optional by forcing the type casting with  as! . Either way, we must specify whether we want it to be optional or non-optional.

So, let's sum up the ways we can validate the types:

  • using  is : checks if the type in question is present within the tree of classes of an instance

  • using  as? (optional modifier): attempts to modify the type of an instance to the specified type

  • using  as! (non-optional modifier): forces the type casting to the specified type

Basically, we can cast our elements to any class available in a given tree of subclasses.

Which casting technique to use?

The rules we apply to type casting are similar to those for general optionals unwrapping:

  • If you are certain an instance can be cased to a specific type - you can use  as!

  • If you are not certain an instance can be cased to a specific type - you can use  as? 

Ultimate base classes - Any & AnyObject

And finally with all this mix of types, there’s a handy solution! We have special data types to address it all: Any  and  AnyObject . This primarily helps us overcome the main constraint of using collections: requiring all elements to be of the same type. If we need to store the following array: 

["Bob", 10, true]

We can do it by specifying the type of elements Any:

var mixedArray: [Any] = ["Bob", 10, true]

Remember, there's the rule that a variable cannot change its type. So, if we attempt to do this it won't work:

var a: String
a = "text" // Ok
a = 10 // Error

As you may have guessed, there's a trick to it. We can use a variable of type Any:

var a: Any
a = "text" // Ok
a = 10 // Ok

Any is great! What's AnyObject  for?

The AnyObject type only works with instances of a class. This excludes all basic types: Int, Float, Double, Bool, String. For example:

var b: AnyObject = Animal() // Ok
var c: AnyObject = 10 // Error
var d: Any = 10 // Ok

When would we need to use Any/AnyObject

Common cases for Any/AnyObject are:

  • to generate mixed type collections

  • to assign values of different types to one variable

Great! let's apply this to NeXTDesination!

Making NeXTDesination better and better!

We've done quite extensive work on our Entertainment class. Time to move up to destinations. And again, the most exciting part is telling a story!

Here's the latest code:

class Destination {
    // properties
    var name: String
    var cost: Double
    var entertainmentOptions: [Entertainment]
    var accomplishments: [Entertainment]
    
    // initializer
    init(name: String, cost: Double, entertainmentOptions: [Entertainment]) {
        self.name = name
        self.cost = cost
        self.entertainmentOptions = entertainmentOptions
        accomplishments = []
    }
    
    // methods
    func pickEntertainment(availableBudget: Double) -> Entertainment? {
        return nil
    }
}

Let's add a tellStory method (like for the Entertainment implementation, it will return a story string):

func tellStory() -> String {
    return "It was great to visit \(name)"
}

Here's the full code for the class:

class Destination {
    // properties
    var name: String
    var cost: Double
    var entertainmentOptions: [Entertainment]
    var accomplishments: [Entertainment]
    
    // initializer
    init(name: String, cost: Double, entertainmentOptions: [Entertainment]) {
        self.name = name
        self.cost = cost
        self.entertainmentOptions = entertainmentOptions
        accomplishments = []
    }
    
    // methods
    func pickEntertainment(availableBudget: Double) -> Entertainment? {
        return nil
    }
    
    // tell story
    func tellStory() -> String {
        return "It was great to visit \(name)"
    }
}

Boring, isn't it :o?

Indeed, it is boring! There's so much more to tell here!

To continue improving our code, let's create some temporary content for us to use: a destination with accomplishments. For the moment, we can cheat a bit and use the whole array of general options as our accomplishments bypassing using the pickEntertainment() method:

var destination = Destination(name: "New York City", cost: 800.0, entertainmentOptions: Entertainment.generalOptions) // cheating
destination.accomplishments = Entertainment.generalOptions // cheating

Now we've got plenty of data to develop our tellStory() method for the Destination class. It's your turn to tell the story!

Implement your version of a storytelling method for the Destination class. Consider the following requirements:

  • Introduce a summary of accomplishments

  • List individual stories for all accomplishments

  • Provide financial information for the destination. Include what is spent for all the individual accomplishments, as well as the total.

  • In each accomplishment, highlight one of the properties. For example, you can say "I loved that it was Italian cuisine! ". Implementation hint: Typecasting will be necessary

Do it on your own before looking up the result:

// No scrolling yet!

























class Destination {
    /* ... */
    func tellStory() -> String {
        
        // create a variable to collect the sum of accomplishments cost
        var accomplishmentsCost = 0.0
        
        // intro
        var story = "It was great to visit \(name) \n"
        
        // summary
        if accomplishments.count > 0 {
            story += "While there I was lucky to have an opportunity to do \(accomplishments.count) things: \n"
        }
        else {
            story += "Sadly I wasn't able to do anythng there:( \n"
        }
        
        for entertainment in accomplishments {
            
            // add individual story
            story += entertainment.tellStory()
            
            // highlight sublass specific info
            if let activity = entertainment as? Activity {
                story += "I liked that it was \(activity.difficulty) \n"
            }
            else if let arts = entertainment as? Arts {
                story += "I liked that it was of \(arts.genre) genre \n"
            }
            else if let gastronomy = entertainment as? Gastronomy {
                story += "I liked that it was of \(gastronomy.cuisine) cuisine \n"
            }
            
            // include the cost in total
            accomplishmentsCost += entertainment.cost
        }
        
        // financial impact
        story += "The visit without entertainment cost me $\(cost). \n"
        story += "The total cost including entertainment amounted to $\(accomplishmentsCost + cost). \n"
        
        return story
    }
    /* ... */
}

Let's review the version above:

  • We implemented the summary of accomplishments by using a simple phrase and a condition - all or nothing. It would sound weird to say "I've done 0 things..."

  • We listed individual stories for all accomplishments by simply using a for-in loop and calling the tellStory function. Since this a method available in our parent class, no type casting is needed.

  • We declared a variable to calculate the costs for the accomplishments. While walking the accomplishments array to gather the stories, we also collect the cost amounts for all the accomplishments to present the cost breakdown at the end of the story.

  • We used typecasting to convert the general entertainment object to a needed type in order to access a selected property.

Now let's test it out and tell the story:

print(destination.tellStory())

And here's the full version:

class Entertainment {
    //properties
    static let generalOptions = Entertainment.generateGeneralOptions()
    var name: String
    var cost: Double
    var location: String
    
    static func generateGeneralOptions() -> [Entertainment] {
        var options = [Entertainment]()
        
        // activities
        let activity = Activity(name: "Horseback riding", cost: 60)
        activity.difficulty = .intermediate
        activity.equipmentRequired = "A helmet"
        activity.weatherConditions = "No heavy rain"
        options.append(activity)
        
        // arts
        let arts = Arts(name: "MoMA. Items: Is Fashion Modern?", cost: 12)
        arts.genre = .modern
        arts.teaser = "Items: Is Fashion Modern? explores the present, past—and sometimes the future—of 111 items of clothing and accessories that have had a strong impact on the world in the 20th and 21st centuries—and continue to hold currency today. "
        arts.ageLimit = 0
        options.append(arts)
        
        // gastronomy
        let gastronomy = Gastronomy(name: "Horseback riding", cost: 60)
        gastronomy.cuisine = "Italian"
        gastronomy.dressCode = .casual
        gastronomy.minNumberOfPeople = 4
        options.append(gastronomy)
        
        return options
    }
    
    //initializer
    init(name: String, cost: Double) {
        self.name = name
        self.cost = cost
        location = ""
    }
    
    func tellStory() -> String {
        return "It was fun to experience \(name)"
    }
}

enum Difficulty: String {
    case hard = "Hard"
    case intermediate = "Intermediate"
    case easy = "Easy"
}

enum Genre: String {
    case classical = "Classical"
    case modern = "Modern"
    case historical = "Historical"
}

enum DressCode: String {
    case streetwear = "Streewear"
    case casual = "Casual"
    case formal = "Formal"
}

class Activity: Entertainment {
    var weatherConditions = ""
    var equipmentRequired = ""
    var difficulty: Difficulty = .intermediate
    
    override func tellStory() -> String{
        // intro - print using the parent implementation
        super.tellStory()
        
        var story = " "
        
        // mention about weather requirements
        story += "Oh, the weather requirement: \(weatherConditions). "
        
        // tell about equipment requirements
        story += "And the equipment: \(equipmentRequired). "
        
        // finish off with the financial impact:)
        if cost <= 30 {
            story += "All that for ONLY $\(cost). "
        }
        else if cost >= 100 {
            story += "It was costly - $\(cost) - but definitely worth it. "
        }
        else {
            story += "It was afforable at a price of $\(cost). "
        }
        
        // share the full story
        return story
    }
}

class Arts: Entertainment {
    var genre: Genre = .modern
    var teaser = ""
    var ageLimit = 0
    
    override func tellStory() -> String{
        // intro
        var story = "\(name) was an interesting experience! "
        
        // talk about genre
        story += " It was \(genre.rawValue) - "
        switch genre {
        case .modern:
            story += " my cup of tea! "
        default:
            story += " I'm not a big fan, but it was educational! "
        }
        
        story += teaser
        
        // mention age restrictions
        if ageLimit > 0 {
            story += "You must be at least \(ageLimit) years of age to participate. "
        }
        else {
            story += "It's suitable for everyone. "
        }
        
        // finish off with the financial impact:)
        story += "It cost me $\(cost). "
        
        // share the full story
        return story
    }
}

class Gastronomy: Entertainment {
    var dressCode: DressCode = .casual
    var cuisine = ""
    var minNumberOfPeople = 1
    
    override func tellStory() -> String{
        // intro
        var story = "\(name) was outstanding! "
        
        // tell about cuisine
        story += "\(cuisine) cuisine was delicious. "
        
        // mention booking requirements
        if minNumberOfPeople > 1 {
            story += "I could go by myself - loved the food! "
        }
        else {
            story += "The place required minimum \(minNumberOfPeople) for booking. We all loved the food! "
        }
        
        // share the dress code info
        story += "There was a \(dressCode.rawValue) - "
        switch dressCode {
        case .streetwear:
            story += " no need change before going! "
        case .casual:
            story += " nothing fancy, very simple! "
        case .formal:
            story += " - a great opportunity to dress up! "
        }
        
        // finish off with the financial impact:)
        story += "All that for $\(cost). "
        
        // share the full story
        return story
    }
}

class Destination {
    // properties
    var name: String
    var cost: Double
    var entertainmentOptions: [Entertainment]
    var accomplishments: [Entertainment]
    
    // initializer
    init(name: String, cost: Double, entertainmentOptions: [Entertainment]) {
        self.name = name
        self.cost = cost
        self.entertainmentOptions = entertainmentOptions
        accomplishments = []
    }
    
    // methods
    func pickEntertainment(availableBudget: Double) -> Entertainment? {
        return nil
    }
    
    // tell story
    func tellStory() -> String {
        
        // create a variable to collect the sum of accomplishments cost
        var accomplishmentsCost = 0.0
        
        // intro
        var story = "It was great to visit \(name) "
        
        // summary
        if accomplishments.count > 0 {
            story += "While there I was lucky to have an opportunity to do \(accomplishments.count) things: "
        }
        else {
            story += "Sadly I wasn't able to do anythng there:( "
        }
        
        for entertainment in accomplishments {
            
            // add individual story
            story += entertainment.tellStory()
            
            // highlight sublass specific info
            if let activity = entertainment as? Activity {
                story += "I liked that it was \(activity.difficulty) "
            }
            else if let arts = entertainment as? Arts {
                story += "I liked that it was of \(arts.genre) genre "
            }
            else if let gastronomy = entertainment as? Gastronomy {
                story += "I liked that it was of \(gastronomy.cuisine) cuisine "
            }
            
            // include the cost in total
            accomplishmentsCost += entertainment.cost
        }
        
        // financial impact
        story += "The visit without entertainment cost me $\(accomplishmentsCost + cost). "
        story += "The total cost including entertainment amounted to $\(accomplishmentsCost + cost). "
        
        return story
    }
}

var destination = Destination(name: "New York City", cost: 800.0, entertainmentOptions: Entertainment.generalOptions) // cheating
destination.accomplishments = Entertainment.generalOptions // cheating

print(destination.tellStory())

Enjoy the reading - nicely done!

Let's Recap!

  • The two most common use cases of type checking and type casting are: Inheritance and Any/ AnyObject types.

  • The Any and AnyObject types allow variables to accept values of any type (only class instances for the AnyObject type). They are used to generate collections of mixed types and to provide an ability for a variable to hold different value types.

  • Checking a type is done using  is  .

  • Type casting is done using  as?  or   as! .

     

Example of certificate of achievement
Example of certificate of achievement