What Do you do with Any?

There’s been a major change to Swift 3.0 That many people might find a little disturbing in code, and others may have no idea what it is. In one sense that’s the idea. Apple changed AnyObject to Any in Swift 3.0. This three-letter word is an extremely powerful feature,a backbone of many functions and classes, and the core of watchOS’s way of transmitting data from one controller to another.

The class Any is the object that is anything. It is generic. You learned early in your coding lessons that everything has a type and everything gets assigned a type like this:

let radius = 5.0   // a Double
var diameter:Int = 10  //an Int
var pizzaTopping = "Pepperoni"  //a string

Functions need a type in their parameters too. For example, there is this function

func pizzaArea(radius:Double)-> Double {
let area = radius * radius * M_PI
return area
}

Since everything needs a type to work right, a function assigning something like this might seem a little odd.

func pizzaSize(context:Any) -> Double {
    var size = 0.0
    let radius = context as! Double
    size = radius * radius * M_PI
    return size
}

If you tried

print(pizzaSize(context: 5.0))

You would get a result around 78.54.

Any really isnt totally generic. It’s a place holder because there are times when writing code, you have to be vague in how you add a parameter. Look closely at pizzaSize(context:Any). You’ll see this is identical to pizzaArea(radius:Double) but I set the type in my code instead of in the parameter. To set the type, I downcast the type Any to Double using as!. Another example

let myAnyType:Any = 12.0
let myDoubleType:Double = myAnyType as! Double
print(myDoubleType)

Why would you want to delay typing? Because you, or some other developer such as Apple wrote some code and has no idea how you are going to use that code. Consider the following class

class PizzaGeometry{
init(context:Any){
self.awake(context: context)
}
func awake(context:Any){}
}

This code by itself does nothing but call an empty function awake(context:) and have an empty function pizzaArea. But we could subclass this to do areas of pizzas for both integers and doubles by awake(context:).

class PizzaAreaInt:PizzaGeometry{
    var radius:Int = 0
    override func awake(context: Any) {
        radius = context as! Int
    }
    func pizzaArea() {
        print (radius * radius * 3)
    }
}

class PizzaAreaDouble:PizzaGeometry{
    var radius:Double = 0.0
    override func awake(context:Any){
        radius = context as! Double
    }
    func pizzaArea(){
        print(M_PI * radius * radius )
    }
}

I couldn’t have done that had I set the type in PizzaGeometery. Most of you are probably iOS developers and haven’t touched watchOS yet. However in Apple’s obsession about keeping things simple in watchOS, PizzaGeometery is the skeletal definition of a view controller , named WKInterfaceController class in watchOS. watchOS passes data between view controllers directly without, like iOS,  referencing the destination in prepare(forSegue:) or initializing the destination class and setting properties. It sends those values as a context, and lets the awake(withContext context:any) deal with it. Developers unpack the context for the controller they are currently writing by overriding awake. However Any is not just a watchOS thing. iOS uses it too, and one form of this shows up all over the place: Dictionaries

Area for circles is a single property. What if we wanted to make a class for more than one property in PizzaGeometery?  That’s where dictionaries come in. In Swift, you can define a dictionary of type [String:Any].  What does that do? It means you can have an identifier name as a key and place its value — no matter the type — as a the value. for example:

let rectanglePizzaDictionary:[String:Any] = 
    ["width":10,"length":12,"height":1.2]

I can unpack a dictionary like that in my Subclass

class PizzaRectangle:PizzaGeometry{
    var height:Double = 0.0
    var width:Int = 0
    var length:Int = 0
    override func awake(context: Any) {
        let contextDictionary = context as! [String:Any]
        height = contextDictionary["height"] as! Double
        width = contextDictionary["width"] as! Int
        length = contextDictionary["length"] as! Int
    }
    func pizzaArea(){
        print(width * length)
    }
    func pizzaVolume(){
        print (width * length * Int(height))
    }
}

I unpacked twice. First I unpacked to the type [String:Any]. Then I unpacked the entries in the dictionary.  Here’s the one issue you’ll need to be careful with here. For simplicity I didn’t error check, but if any of these values are not what I expect for the dictionary the app will crash. You need error checking here and maybe even more importantly, good documentation of the possible dictionary entries. As iOS developers this is going to be a big problem since a lot of methods in UIKit use this dictionary format to send information back to you as a developer. Apple used to be worse at this, but their current documentation system seems to have solved a lot of this. For example one of the excruciating examples of this is the UIImagePickerControllerDelegate method  imagePickerController(_:didFinishPickingMediaWithInfo:). There’s now a link in the documentation, but for a while it was a very hard search to find the keys and value in the info dictionary.

Any is very helpful. Apple uses it often once you start looking for it. Any means “I don’t know what supposed to go in here, could you set it up for me” when it comes to values. Dictionaries of [String:Any] exist all over the API, often in delegates and closures. watchOS makes it the way to pass values between interface controllers. What at first glance looks like an empty generic container is really a powerful way of deferring type until necessary, making for a more flexible programming environment.

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s