Zaxter
Zaxter

Reputation: 3035

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?

Upvotes: 0

Views: 72

Answers (1)

Igor B.
Igor B.

Reputation: 2229

  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)
            }
        }
    }

Upvotes: 1

Related Questions