Boundaries

... and how to define them

What are Boundaries?

🤔

A Macro Perspective

  • We embrace OOP, so ...
  • Everything is an Object
     
  • An Object Boundary:
    • public members (properties, methods)
    • Side effects (delegate, callback, ...)
  • A Function Boundary:
    • Mutable reference type args
    • Non-local variables (self)
       
  • Our goal is always to Minimize it!

An Example

  • MVC, a familiar face
  • Network writes to Model
  • Model notifies Controller
  • Controller updates View
  • User taps on View
  • View notifies Controller
  • Controller updates Model

Not Covered Today

  • The object hierarchy
  • Deciding on responsibilities

Why are they Important?

🧐

Well Defined Boundaries:

  • Less code to worry about
  • State/error containment
  • Extremely easy to test

Achieving Good Boundaries

  • Less globals
  • Zero exposed state
  • Good naming

Workshop

🛠

NetworkError

@objc public let error: NSError?
@objc public let serverErrorCode: String?
@objc public let serverErrorDesc: String?
@objc public let serverErrorFields: [String: Any]?
public enum Domain: Equatable {
    case unknown
    case connection
    case url(String)
    case backoff
    case server(ErrorDTO)
}

DispatchGroup

Problem

final class XYZ {

    private var dispatchGroup: DispatchGroup? = nil

    func doStuffThenProceed() {
        
        dispatchGroup = DispatchGroup()
        functionA()
        if condition {
            functionB()
        }
        ...
        dispatchGroup.notify {
            self.proceed()
        }
    }
}

DispatchGroup

Solution

final class XYZ {

    private let operationQueue = OperationQueue()

    func doStuffThenProceed() {
                
        var operations: [Operation] = []
        operations.append(functionA())
        if condition {
            operations.append(functionB())
        }
        ...
        let completion = {
            self.proceed()
        }
        operations.forEach {
            completion.addDependency($0)
            operationQueue.add($0)
        }
        OperationQueue.main.add(completion)
    }
}
// before
private func loadMixpanelToggles() {
    
    toggleDispatchGroup.enter()
    togglesLoadedOrTimedOut = false
    Mixpanel.mainInstance().joinExperiments {
        if self.togglesLoadedOrTimedOut == false {
            self.togglesLoadedOrTimedOut = true
            self.toggleDispatchGroup.leave()
        }
    }
    
    DispatchQueue.main.asyncAfter(deadline: .now() + ...) {
        if self.togglesLoadedOrTimedOut == false {
            self.togglesLoadedOrTimedOut = true
            self.toggleDispatchGroup.leave()
        }
    }
}

// after
private func loadMixpanelToggles() -> Operation {

    return BlockOperation {
        let semaphore = DispatchSemaphore(value: 0)
        Mixpanel.mainInstance().joinExperiments {
            semaphore.signal()
        }
        
        semaphore.wait(timeout: .now() + ...)
    }
}
Made with Slides.com