Tip: Deep Dive into Any? Dictionaries

In many factory methods, you’ll find parameters with type Any? like this:

init(order:Any?) {
        
}

userInfo parameters in iOS and context in watchOS use these. You can place anything there, but most often, you’ll use a dictionary of type [String:Any]. This is a great way of moving multiple values from one method to another without making a class to do so. If you’ve never used them before, they might be a bit of a struggle to unwrap, some let me show you how.
You’ll find this starter playground as a GitHub download. I’ll shut down automatic running for now. I made three dictionaries for you of this very common type of dictionary.

  //: The three dictionaries we'll use.
 
var breakfastOrder:[String:Any] = ["meal":Meal.breakfast,"food":"French Toast","toppings":["Mango","Orange Cream"]]
 
var lunchOrder:[String:Any] = ["meal":Meal.lunch,"food":"Hamburger","toppings":"Everything","price":6.95]
 
var dinnerOrder:[String:Any] = ["meal":Meal.lunch,"food":"Pizza","toppings":["Goat Cheese","Olives","Duck Breast","Red Onions"],"crust":"Thin", "price":16.95]
 
  

This uses an enumeration Meal


Above the three dictionaries in the playground, I added a class Order with a few properties, methods and an initializer, which is now blank.

class Order{
    var meal = Meal.snack
    var food:String = ""
    var toppings:[String] = []
    var price: Double = 0.00
    var order:[String:Any] = [:]
    
    init(order:Any?) {
        
    }
    
    func showOrder(){
        var orderString = meal.rawValue + " Order: " + food
        if toppings.count > 0 {
            orderString += " with "
            for topping in toppings{
                orderString += " " + topping
            }
        }
        orderString += String(format:" %2.2f",price)
        print(orderString)        
    }
}

Inside that initializer, i have a parameter order of type Any?. Since Any? can accept anything, I’ll check for two things before I start using the dictionary. I check if the order is not nil, and if this is a dictionary of type[String:Any]. Optional chaining and casting let me do this in a single if clause.

 
if let order = order as? [String:Any]{
}

This is the pattern we’ll use several times with dictionaries, so it make sense to examine it a little more. The if let order = assigns a local constant order if there is a value in the parameter order. I often use the same name for the parameter and the constant, since the constant is the unwrapped version of the parameter.
The as? converts, otherwise known as downcasts, order to a type [String:Any], returning nil if it fails to cast. So you have nil if order is nil or if order is not the correct type. In a nil situation the expression returns false and the if won’t do what’s in the braces.

Within those braces, I’ll first save the unwrapped order to the property order.

self.order = order

The values of the dictionary are also of type Any?. Dictionary values are optionals, returning nil for keys not found. I’ll do the same optional chaining and downcasting to each entry in order. For meal I’ll do this to convert the dictionary entry to a class property.

    
if let meal = order["meal"] as? Meal{
    self.meal = meal
}

I’ll do the same for food.

if let food = order["food"] as? String{
   self.food = food
}

I can do something similar in a computed property. Here I’ll use my property order to get the toppings.

var toppings:[String]{
   if let toppings = order["toppings"] as? [String] {
       return toppings

I’ll do one more thing here. If this doesn’t work I’ll set the value to an empty array.

 
   return []

I’ll do the same for the price, returning 0.00 if there is no price.

    var price: Double{
        if let price = order["price"] as? Double{
            return price
        } 
        return 0.00
    }

I’ve already written for you an output method showOrder.

func showOrder(){
        var orderString = meal.rawValue + " Order: " + food
        if toppings.count > 0 {
            orderString += " with "
            for topping in toppings{
                orderString += " " + topping
            }
        }
        orderString += String(format:" %2.2f",price)
        print(orderString)        
    }

At the bottom of the code we’re set to run for a breakfast order. Tap Run and we get the order. Since the dictionary has no price, the price is zero. Try a lunch order

let myOrder = Order(order: lunchOrder)

There’s a hamburger order, this time without a price. We don’t get the everything string here, since it not an array. Try the dinnerOrder, and you get a pizza.
Whenever you find the type any? for a parameter, this is the kind of thing it is looking for. You’ll find it often in both delegate methods and closures to save making another class for passing multiple values in a single parameter. Be ready in a closure to unwrap this value before doing anything else.

This was a text version of the iOS Development Tips Weekly video series you can find at the Lynda.com and LinkedIn Learning libraries. Click the image at left  to view.

The Whole Code

Here is the code for this session. You can also find it on Github.

//:# Unwrapping Any?
//: How to unwrap `Any?` for a dictionay of `[String:Any]`

import UIKit

enum Meal:String{
    case breakfast = "Breakfast"
    case lunch = "Lunch"
    case dinner = "Dinner"
    case snack = "Snack"
}

class Order{
    var meal = Meal.snack
    var food:String = ""
    var toppings:[String] = []
    var price: Double = 0.00
    var order:[String:Any] = [:]
    
    init(order:Any?) {
        
    }
    
    func showOrder(){
        var orderString = meal.rawValue + " Order: " + food
        if toppings.count > 0 {
            orderString += " with "
            for topping in toppings{
                orderString += " " + topping
            }
        }
        orderString += String(format:" %2.2f",price)
        print(orderString)        
    }
}

//: The three dictionaries we'll use.

var breakfastOrder:[String:Any] = ["meal":Meal.breakfast,"food":"French Toast","toppings":["Mango","Orange Cream"]]

var lunchOrder:[String:Any] = ["meal":Meal.lunch,"food":"Hamburger","toppings":"Everything","price":6.95]

var dinnerOrder:[String:Any] = ["meal":Meal.lunch,"food":"Pizza","toppings":["Goat Cheese","Olives","Duck Breast","Red Onions"],"crust":"Thin", "price":16.95]

//: Use the class.
let myOrder = Order(order: breakfastOrder)
myOrder.showOrder()

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s