Zaxter Zaxter - 6 months ago 31
Swift Question

Do I have strong reference cycles here?

I have a

BaseViewController
which is subclassed by all other view controllers in my app.

I have some state variables which must be consistent across all view controllers, so I plan to write code for passing back-and-forth these state variables once in the
BaseViewController
.
For this I'm providing a helper function
pushStatefulViewControllerWithIdentifier()
for forward passing and using
StatePassBackDelegate
for backward passing.

import UIKit

class BaseViewController: UIViewController, StatePassBackDelegate {

class State {
var connected = false
var loggedIn = false
}


var state = State()
weak var delegate: StatePassBackDelegate? = nil


// MARK: Lifecycle
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)

if self.isMovingFromParentViewController() == true {
delegate?.passBackState(state)
}
}


// MARK: StatePassBackDelegate functions
func passBackState(state: AnyObject) {
self.state = state as! State
}


// MARK: Helpers
final func pushStatefulViewControllerWithIdentifier(identifier: String) {
let vc = storyboard?.instantiateViewControllerWithIdentifier(identifier) as! BaseViewController
vc.state = state
vc.delegate = self
navigationController!.pushViewController(vc, animated: true)
}

}


protocol StatePassBackDelegate: class {

func passBackState(state: AnyObject)

}



  1. Do I have strong reference cycles here?

  2. Should I instead use the singleton pattern here?


Answer
  1. I don't see retain cycle in your code. If you afraid that some object is not destroying when it's expected, you can log its deallocation to check:

    deinit {
        print("deinit of \(self)")
    }
    
  2. Agree, that you rather need to introduce some Model level entity for state caring instead of having it on the Controllers layer. Singleton is ok if you don't plan to cover related logic with unit tests.

EDITED

You can store shared objects in app delegate.

Not sure that I fully understand your app object model. I assumed that the state should be shared across all the app, so better to inject some SystemStateMachine into all interested components. Hope it helps:

    @UIApplicationMain
    class MyAppDelegate: UIResponder, UIApplicationDelegate {

        var controllersFactory: IViewControllersFactory! = nil

        func applicationDidFinishLaunching(application: UIApplication) {

            let storyboard = UIStoryboard(name:NSBundle.mainBundle().infoDictionary!["UIMainStoryboardFile"] as! String , bundle: NSBundle.mainBundle())
            controllersFactory = ViewControllersFactory(storyboard: storyboard, stateMachine: SystemStateMachine())
        }
    }

    struct SystemState : OptionSetType {
        let rawValue: Int

        static let None   = SystemState(rawValue: 0)
        static let Connected = SystemState(rawValue: 1 << 0)
        static let LoggedIn  = SystemState(rawValue: 1 << 1)

    }

    protocol ISystemStateMachine {

        var currentState: SystemState { get set }
    }

    class SystemStateMachine: ISystemStateMachine {

        var currentState: SystemState = .None
    }

    protocol IViewControllersFactory {

        func instantiateViewController(identifier: String) -> BaseViewController?
    }

    class ViewControllersFactory: IViewControllersFactory {

        let _storyboard: UIStoryboard
        let _stateMachine: ISystemStateMachine

        init(storyboard: UIStoryboard, stateMachine: ISystemStateMachine) {
            _storyboard = storyboard
            _stateMachine = stateMachine
        }

        func instantiateViewController(identifier: String) -> BaseViewController? {
            if let vc = _storyboard.instantiateViewControllerWithIdentifier(identifier) as? BaseViewController {
                vc.stateMachine = _stateMachine
                return vc
            }
            return nil
        }
    }

    class BaseViewController: UIViewController {

        var stateMachine: ISystemStateMachine!

        // MARK: Lifecycle
        func somethingWorthHappen() {
            //switch state
            stateMachine.currentState.insert(.Connected)
        }
        func somethingWorth2Happen() {
            //switch state
            stateMachine.currentState.insert(.LoggedIn)

            guard let appDelegate = UIApplication.sharedApplication().delegate as? MyAppDelegate else {
                //log an error
                return
            }
            if let vc = appDelegate.controllersFactory.instantiateViewController("myViewController") {
                navigationController!.pushViewController(vc, animated: true)
            }
        }
    }