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