Skip to main content

Select your location

Two programmers

Properties of object-oriented programming: computed and with closure

In an effort to share cool things we've learned at Kin + Carta, welcome to the first in what we hope will be many of Kin + Carta Tips. This will be a series where we share cool things we've learnt on projects and in our spare time.

cWondering...

On a current project, I had been wondering what the difference is between the 2 syntactically similar declarations of properties of object-oriented programming.

// Property declared with a closure

var gradientLayer: CAGradientLayer = {

    let gradientLayer = CAGradientLayer()

    return gradientLayer

}()

// Computed property

var gradientLayer: CAGradientLayer {

let gradientLayer = CAGradientLayer()

return gradientLayer

}

A property defined with a closure is defined once and stored to that variable. A computed property acts like a function, and in the example above will return a new CAGradientLayer object.

This gives us a great deal of flexibility with properties, allowing us to define a property once, or essentially create a shorthand for a function. Properties with closures are great to tidy up initialisation calls too, reducing the boiler plate code often found in initialisers.

An example

To provide an example of when we may want to use both, I want to create a new CAGradientLayer object that is set to the bounds of the view. I then want to set the colors property to a predefined set of colours defined by the class, but I dont' want to recreate this array every time since it won't change in our case.

class ExampleGradientView: UIView {

 

var newGradientLayer: CAGradientLayer {

let gradientLayer = CAGradientLayer()

gradientLayer.bounds = bounds

return gradientLayer

}

var gradientColors: [CGColor] = {

    return [

      UIColor.blackColor().CGColor,

      UIColor.whiteColor().CGColor

]

}()

}

Note: Renamed the computed property to newGradientLayer so that it is clear that we are always going to return a new CAGradientLayer object.

Our var gradientColors will only be defined once now, but gradientLayer will be defined and fit to the bounds of the view as expected.

Computed Properties for Sizing

I've also been experimenting with using computed properties to define the sizing of cells for flow layouts in collection views. This allows for my code to be incredibly succinct, and for the cell sizes to be defined dynamically.

class ExampleViewController: UIViewController, UICollectionViewDelegate {

var items: [ExampleItem] = []

 

// Spacing

let sectionInset = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)

let cellSpacing: Double = 10

// Cell sizing

let cellHeight: Double = 104

var cellWidth: Double {

switch items.count {

case 1:

return Double(CGRectGetWidth(collectionView.bounds) - sectionInset.left - sectionInset.right)

case 2:

return (Double(CGRectGetWidth(collectionView.bounds) - sectionInset.left - sectionInset.right) - cellSpacing) / 2.0

default:

return 136

}

}

  

function collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize

{

return CGSize(width: cellWidth, height: cellHeight)

}

}

In the above example, we have an items property, an array of ExampleItem. We have 3 potential layouts for the cells:

  • 1 item - Display a cell the entire width of the collection view bounds, minus the section insets
  • 2 items - Display 2 cells that are the width of the collection view, minus the section insets and the cell spacing
  • 3 items - Display all cells with a width of 136 pts, to show the collection view can be scrolled.

We use a computed property to define the width of the cell dynamically based on the number of items in our items array. The computed property is essentially a function, but provides a neater shorthand for us to call it, by just calling cellWidth.

Closing

My advice with computed properties is to use them in place of functions (with no arguments) that act as getters, and to use closures for properties you only need to define once.

Like all properties though, be aware that it may not be immediately clear that your computed property is dynamic. With the example below:

var gradientLayerClosure: CAGradientLayer = {

    let gradientLayer = CAGradientLayer()

    return gradientLayer

  }()

  

  var gradientLayerComputed: CAGradientLayer {

    let gradientLayer = CAGradientLayer()

    return gradientLayer

  }

In our swift interface, we will get the following:

internal var gradientLayerClosure: CAGradientLayer

internal var gradientLayerComputed: CAGradientLayer { get }

Note the { get }

There is a difference in the Swift interface, but a get doesn't imply that the property is being worked out dynamically. If another developer uses a computed getter, there may be unexpected results if they are unaware that the result returned is dynamic. To reduce any risk of unexpected behaviour, ensure that your documentation is absolutely clear that it is a dynamically computed getter, although this should be the rule regardless.

Interested in learning more?

Get in touch