Skip to content
Adam Share edited this page Oct 2, 2020 · 12 revisions

LifecycleKit

LifecycleKit is a framework for building lifecycle-aware architecture components.

Inspired by RIBs and Android Jetpack Lifecycle. Complemented by Needle, a compile-time safe Swift dependency injection framework.

Overview

LifecycleKit provides protocols and implementations that can be used to facilitate building safe and scalable application architecture.

Types

LifecycleState

initialized Lifecycle has been initialized but not yet active.
active Lifecycle is currently active.
inactive Lifecycle is currently inactive.
deinitialized Lifecycle deinit called.

LifecyclePublisher

Todo: description

Conforming Types

LifecycleOwner
LifecycleDependent
ScopeLifecycle
ViewLifecycle

Scope

ScopeLifecycle

Todo: description

LifecycleState

LifecycleOwner

Todo: description

class MyLifecycleOwner: LifecycleOwner {
    let scopeLifecycle: ScopeLifecycle = ScopeLifecycle()
}

LifecycleSubscriber

Todo: description

class MyLifecycleSubscriber: LifecycleSubscriber {
    init(scopeLifecycle: ScopeLifecycle) {
        subscribe(to: scopeLifecycle)
    }

    func didLoad(_ lifecyclePublisher: LifecyclePublisher) {
        // First active
    }

    func didBecomeActive(_ lifecyclePublisher: LifecyclePublisher) {
        // On active
    }

    func didBecomeInactive(_ lifecyclePublisher: LifecyclePublisher) {
        // On inactive
    }
}

LifecycleOwnerRouting

Todo: description

class MyParentLifecycleOwner: LifecycleOwner, LifecycleOwnerRouting {
    let scopeLifecycle: ScopeLifecycle = ScopeLifecycle()
    let childLifecycleOwner: LifecycleOwner

    init(childLifecycleOwner: LifecycleOwner) {
        self.childLifecycleOwner = childLifecycleOwner
    }

    func makeChildActive() {
        if attachChild(childLifecycleOwner) {
            print("Child is now active")
        }
    }

    func makeChildInactive() {
        detachChild(childLifecycleOwner)
    }
}

LifecycleDependent

Todo: description

class MyLifecycleDependent: LifecycleDependent {
    weak var scopeLifecycle: ScopeLifecycle?
}

Todo: description

class MyParentLifecycleDependent: LifecycleDependent, LifecycleOwnerRouting {
    weak var scopeLifecycle: ScopeLifecycle?

    let childLifecycleOwner: LifecycleOwner

    init(scopeLifecycle: ScopeLifecycle,
         childLifecycleOwner: LifecycleOwner) {
        self.scopeLifecycle = scopeLifecycle
        self.childLifecycleOwner = childLifecycleOwner
    }

    func makeChildActive() {
        if attachChild(childLifecycleOwner) {
            print("Child is now active")
        }
    }

    func makeChildInactive() {
        detachChild(childLifecycleOwner)
    }
}

AutoCancel

Todo: description

class AutoCancelLifecycleSubscriber: MyLifecycleSubscriber {
    
    private let textStream: RelayPublisher<String>

    init(scopeLifecycle: ScopeLifecycle,
         textStream: RelayPublisher<String>) {
        self.textStream = textStream
        super.init(scopeLifecycle: scopeLifecycle)
    }

    override func didBecomeActive(_ lifecyclePublisher: LifecyclePublisher) {
        super.didBecomeActive(lifecyclePublisher)
        
        textStream
            .autoCancel(lifecyclePublisher)
            .sink { value in
                print("""
                Safely capturing \(self) as retain cycle is broken when 
                \(lifecyclePublisher) becomes inactive deataching from parent scope.
                """)
            }
    }
}

View

ViewLifecycle

Todo: description

LifecycleState

ViewLifecycleOwner

Todo: description

import SwiftUI

class MyViewLifecycleOwner: ViewLifecycleOwner {
    let viewLifecycle: ViewLifecycle = ViewLifecycle()

    var view: some View {
        tracked {
            Text("Hello World")
        }
    }
}

ViewLifecycleSubscriber

Todo: description

class MyViewLifecycleSubscriber: ViewLifecycleSubscriber {
    init(viewLifecycle: ViewLifecycle) {
        subscribe(to: viewLifecycle)
    }

    func viewDidLoad() {
        // View initialized
    }

    func viewDidAppear() {
        // On appear
    }

    func viewDidDisappear() {
        // On disappear
    }
}

Example MVC Integration

Create your own LifecycleViewController

Todo: description

import Lifecycle
import SwiftUI

class LifecycleViewController: LifecycleOwner,
                               LifecycleSubscriber, 
                               LifecycleOwnerRouting, 
                               ViewLifecycleOwner, 
                               ViewLifecycleSubscriber {

    var view: some View {
        tracked {
            Text("Hello World")
        }
    }

    let scopeLifecycle: ScopeLifecycle = ScopeLifecycle()
    let viewLifecycle: ViewLifecycle = ViewLifecycle()

    init() {
        subscribe(to: scopeLifecycle)
        subscribe(to: viewLifecycle)
    }

    func didLoad(_ lifecyclePublisher: LifecyclePublisher) {}
    func didBecomeActive(_ lifecyclePublisher: LifecyclePublisher) {}
    func didBecomeInactive(_ lifecyclePublisher: LifecyclePublisher) {}
    func viewDidLoad() {}
    func viewDidAppear() {}
    func viewDidDisappear() {}
}

RootLifecycle

Todo: description

class SceneDelegate: UIResponder, UIWindowSceneDelegate, RootLifecycle {

    let root: LifecycleViewController = LifecycleViewController()

    var rootLifecycleOwner: LifecycleOwner {
        return root
    }

    var window: UIWindow?

    func scene(_ scene: UIScene, 
               willConnectTo session: UISceneSession,
               options connectionOptions: UIScene.ConnectionOptions) {

        guard let windowScene = scene as? UIWindowScene else { return }

        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: root.view)
        window.makeKeyAndVisible()

        activateRoot()

        self.window = window
    }
}

SPIR

Scoped Presenter Interactor Router

App architecture utilizing Lifecycle to create a scoped business logic tree independent of the view hierarchy.

Presenter ViewLifecycleOwner that provides a View to the parent scope and can be referenced as an ObservableObject by the returned view.
Interactor LifecycleOwner that contains business logic and optionally a strong reference to a Router and Presenter.
Router LifecycleDependent that encapsulates and generalizes routing logic between scopes.

What are the main difference between SPIR and RIBs?

  • Router is optional because the ScopeLifecycle is owned by the Interactor which manages both lifecycle state and attaching child scopes.
  • Router does not have a circular reference to Interactor and instead instaniates with the shared ScopeLifecycle instance.
  • Worker is no longer needed since an Interactor can encapsulate business logic as a child of another Interactor.
  • Builder classes are provided as a convenience for type erasure without requiring subclassing.
Clone this wiki locally