ABOUT
TEAM
WORK
CONTACT
|
BLOG

GirAppe blog

2017/09/21 - go to articles list

POP - Swift protocols with stored properties, and where to use them

Protocol oriented programming is recently on the crest of a wave. Composition has many advantages over inheritance, allowing to create more flexible and transparent code.

I will spare most of arguments in favour of POP, and focus on one particular example, I found quite interesting.

But first, I'll refer to todays post topic - how's with that protocol properties?

By the book, Swift protocols being a blueprints for real implementation, don't seem to have much flexibility here. But then, Swift 2.0 (yeach, I know, it's prehistory) introduced default implementations for Swift protocol extension, allowing something like this:

protocol SomeProtocol {
    func getSomeValue() -> Int
}

extension SomeProtocol {
    func getSomeValue() -> Int {
      return 0 // default behaviour
    }
}

class SomeClass: SomeProtocol { } // already adopting SomeProtocol

It works very same for properties, but the downside is that you can't have stored properties, and so implementing custom getters and setters is the only option.

protocol SomeProtocol {
    var someValue: Int? { get set }
}

extension SomeProtocol {
    var someValue: Int? {
      get { return nil } // not always possible to specify good default value
      set { } // empty setter is not really elegant
    }
}

class SomeClass: SomeProtocol { } // already adopting SomeProtocol

Before I get into how to workaround that, let's briefly focus on why do we even want to have them.

Please note, that in most cases this kind of requirement indicates some sort of design or architectural flaw.

In general, when we want to adopt protocol with property requirement, we add it in main class body (or we define computed property in class extension). It has some downsides though:

Points 1 and 2 are not very problematic - if you intend to fight with Swift just because of that- better stop reading now :)

As for point 3, in most cases you create some base class you do inherit from now on, which usually is perfectly fine. But during my work, I've stumbled upon two situations, where it was insufficient (or at least not fitting well).

Whole concept of adding stored properties is based on Obj-C runtime, which allows to actually do something like:

// MARK: - Helpers - Association
internal func associatedObject<ValueType: AnyObject>(base: AnyObject, key: UnsafePointer<UInt8>, initialiser: () -> ValueType) -> ValueType {
    guard let associated = objc_getAssociatedObject(base, key) as? ValueType else {
        let associated = initialiser()
        objc_setAssociatedObject(base, key, associated,
                                 .OBJC_ASSOCIATION_RETAIN)
        return associated
    }

    return associated
}

internal func optionalAssociatedObject<ValueType: AnyObject>(base: AnyObject, key: UnsafePointer<UInt8>, initialiser: () -> ValueType?) -> ValueType? {
    guard let associated = objc_getAssociatedObject(base, key) as? ValueType else {
        let associated = initialiser()
        if associated != nil {
            objc_setAssociatedObject(base, key, associated!,.OBJC_ASSOCIATION_RETAIN)
        }

        return associated
    }

    return associated
}

internal func associateObject<ValueType: AnyObject>(base: AnyObject, key: UnsafePointer<UInt8>, value: ValueType){
    objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN)
}

internal func associateOptionalObject<ValueType: AnyObject>(base: AnyObject, key: UnsafePointer<UInt8>, value: ValueType?){
    objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN)
}

Case study 1 - tracking ids for all UI controls

For one of the projects, client insisted on registering every ui action in analytics tool. That have lead to a problem, how (and where) to define tracking identifiers for every ui component. The most problematic were system components (text fields, buttons, segmented control etc). The solution had to be generic enough, to not to require to produce too much additional code and logic, just for tracking.

For that particular case, following protocol was proposed:

fileprivate var referenceKey: UInt8 = 10

public extension UIView {
    @IBInspectable public var trackingID: String {
        get {
            return associatedObject(base: self, key: &referenceKey, initialiser: { () -> String in
                return ""
            })
        }
        set {
            associateObject(base: self, key: &referenceKey, value: newValue)
        }
    }
}

That allows to set trackingID for all elements directly on storyboard, allowing to produce more generic analytics code.

Case study 2 - handling selection in SpriteKit and SceneKit

When preparing SpriteKit scene, one of the things you want to handle is responding to user selection. In general, you can do something like:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  guard let touch = touches.first else {
      return
  }

  let location = touch.location(in: self)
  let touchedNode = self.atPoint(location)
  // ... resolve domain action based on what type / what instance you've got
}  

Please note, that while it is quite sufficient for simple scenes, it flaws in more complex ones.

First of all, there is not always 1 to 1 match between node and domain model. In case of nodes, that are composed of other nodes it returns nested one! It can still be managed by searching recursively in parent, but fails when they are not in the same tree.

For that particular case, following protocol was proposed:

/// Object adopting selectable allows to set selected state
public protocol Selectable: class {
    var isSelectable: Bool { get }
    var selected: SelectionState { get set }
}

protocol SelectableNode: Selectable {
    var reference: Selectable? { get set }
}

extension SelectableNode {
    public var isSelectable: Bool {
        return reference?.isSelectable ?? false
    }
    public var selected: SelectionState {
        get { return reference?.selected ?? .none }
        set { reference?.selected = newValue }
    }
}

We specify there a Selectable protocol, to handle selection (by interacting with selection state). Adopting SelectableNode protocol allows to pass that selection stuff to some other instance, and so handling it only in places where it matters.

Usually, it will require now to define some kind of base class, that adopts SelectableNode protocol, and then inheriting it in our every custom node. But for SpriteKit, we will have to create base nodes for (naming only few):

Much easier solution seems to be adding protocol conformance to one class - SKNode directly:

// SKNode+SelectableNode.swift

fileprivate var referenceKey: UInt8 = 10

extension SKNode: SelectableNode {
  var reference: Selectable? {
      get { return (self.referenceObject as? Selectable) ?? parent.reference }
      set { referenceObject = newValue }
  }
  var referenceObject: AnyObject? {
      get {
          return optionalAssociatedObject(base: self, key: &referenceKey, initialiser: { () -> AnyObject? in
              return nil
          })
      }
      set {
          guard newValue is Selectable else { return }
          associateOptionalObject(base: self, key: &referenceKey, value: newValue)
      }
  }
}

Andrzej Michnia
Software Developer