Search code examples
swiftnscache

NSCache behaviour with non-object types


The following code works perfectly well, but I am surprised, because I thought NSCache would require object keys & values, and would not take CGFloats. Can someone explain to me what is happening?

class A
    var cachedPoints = NSCache()

    func rAt(theta theta: CGFloat) -> CGFloat {        
        if let r = self.cachedPoints.objectForKey(theta) as? CGFloat {
            return r
        }
        // do some very expensive maths here...
        let r = self.veryExpensiveFunction(theta)
        self.cachedPoints.setObject(r, forKey: theta)
        return r
    }
}

Solution

  • From Working with Cocoa Data Types in the "Using Swift with Cocoa and Objective-C" documentation:

    Numbers

    Swift automatically bridges certain native number types, such as Int and Float, to NSNumber.
    ...
    It also allows you to pass a value of type Int, for example, to an argument expecting an NSNumber.

    The documentation mentions Int, UInt, Float, Double, Bool as types which are automatically bridged to NSNumber, but apparently this works for CGFloat as well (if "Foundation" is imported). So in

    self.cachedPoints.setObject(r, forKey: theta)
    

    both r and theta are wrapped into NSNumber instances and then passed to the setObject() method which takes two parameters of type AnyObject.

    There is also a bridging in the reverse direction, but that is less well documented (or I could not find it). You can cast an instance of NSNumber to CGFloat (or Int, Float, ...)

    let num : NSNumber = ...
    let x = num as CGFloat
    

    and the result is the same as if you called doubleValue (or floatValue, depending on the architecture) on the number instance. In

    let obj : AnyObject = ...
    let y = obj as? CGFloat // CGFloat?
    

    the object is optionally cast to NSNumber and – if that was successful – converted to CGFloat as in the previous example.

    And that is what happens in

    if let r = self.cachedPoints.objectForKey(theta) as? CGFloat { ... }
    

    If the return value from objectForKey() is an NSNumber then it is converted to CGFloat and assigned to r. Otherwise, the optional binding fails.