Pitch: Property Delegates

Here is an example of a property delegate I would want to express to get rid of the shortcomings and a lot of programmer mistakes in projects on apple platforms:

@propertyDelegate
struct DelayedImmutableView<View: UIView> {
  private unowned let _controller: UIViewController
  private var _value: View? = nil

  init(controller: UIViewController) {
  	self._controller = controller
  }

  var value: View {
    get {
      guard _controller.isViewLoaded else {
      	fatalError("controller's view must be loaded before accessing the view")
      }
      guard let value = _value else {
        fatalError("property accessed before being initialized")
      }
      return value
    }

    // Perform an initialization, trapping if the
    // value is already initialized.
    set {
      if _value != nil {
        fatalError("property initialized twice")
      }
      _value = newValue
    }
  }
}

Then I would write something a property like this:

@IBOutlet lazy var label: UILabel by DelayedImmutableView

It would translate into this:

lazy var $label = DelayedImmutableView<UILabel>(controller: self)
var label: UILabel {
  get { return $label.value }
  set { $label.value = newValue }
}

And can be safely used from here:

func viewDidLoad() {
  super.viewDidLoad()
  // No more optionals, no more silly mistakes updating a view
  // before the controller was woken from `Nib` where you just lose
  // that data.
  self.label.text = "Swift is awesome"
}

If we could also get rid of lazy here and create multiple delegates that are also composable that would be amazing. We can potentially make @IBOutlet into a property delegate as well and move it from the language surface into UIKit. But then we definitely need a way to compose multiple delegates var label: UILabel by [IBOutlet, DelayedImmutableView].


Sad story that @IBOutlet requires the type to be optional otherwise we could already use the above delegate type.

class Controller: UIViewController {
  private lazy var __label = DelayedImmutableView<UILabel>(controller: self)
  @IBOutlet var label: UILabel { // error @IBOutlet property has non-optional type 'UILabel'
    get { return __label.value }
    set { __label.value = newValue }
  }
}
4 Likes