Created
November 14, 2020 23:59
-
-
Save DeFrenZ/cce61facb35f6b314ef953eb8a05750f to your computer and use it in GitHub Desktop.
Dependency Injection a-la SwiftUI.Environment style by (ab)using the responder chain
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
@main | |
final class AppDelegate: UIResponder, UIApplicationDelegate { | |
private let testService: TestService = .init() | |
func application( | |
_ application: UIApplication, | |
configurationForConnecting connectingSceneSession: UISceneSession, | |
options: UIScene.ConnectionOptions) -> UISceneConfiguration | |
{ | |
setEnvironmentValue(testService, for: \.testService) | |
return UISceneConfiguration( | |
name: "Default Configuration", | |
sessionRole: connectingSceneSession.role) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Taken https://stackoverflow.com/a/63738419/1288097 as a base | |
/* | |
AssociatedObject.swift | |
Copyright © 2020 RFUI. | |
https://github.com/BB9z/iOS-Project-Template | |
The MIT License | |
https://opensource.org/licenses/MIT | |
*/ | |
import Foundation | |
/** | |
Objective-C associated value wrapper. | |
Usage | |
``` | |
private let fooAssociation = AssociatedObject<String>() | |
extension SomeObject { | |
var foo: String? { | |
get { fooAssociation[self] } | |
set { fooAssociation[self] = newValue } | |
} | |
} | |
``` | |
*/ | |
public final class AssociatedObject<T> { | |
private let policy: objc_AssociationPolicy | |
/// Creates an associated value wrapper. | |
/// - Parameter policy: The policy for the association. | |
public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) { | |
self.policy = policy | |
} | |
/// Accesses the associated value. | |
/// - Parameter index: The source object for the association. | |
public subscript(index: AnyObject) -> T? { | |
get { objc_getAssociatedObject(index, associatedObjectKey) as? T } | |
set { objc_setAssociatedObject(index, associatedObjectKey, newValue, policy) } | |
} | |
private var associatedObjectKey: UnsafeMutableRawPointer { | |
Unmanaged.passUnretained(self).toOpaque() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
final class RootViewController: UIViewController { | |
@ResponderEnvironment(\.testService) var testService | |
override func viewDidAppear(_ animated: Bool) { | |
super.viewDidAppear(animated) | |
print(testService.value) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
final class SceneDelegate: UIResponder, UIWindowSceneDelegate { | |
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 = RootViewController() | |
self.window = window | |
window.makeKeyAndVisible() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Combine | |
final class TestService { | |
@Published | |
private(set) var value: Int = 0 | |
} | |
// MARK: - ResponderEnvironment | |
private let testServiceAssociation: AssociatedObject<TestService> = .init() | |
extension ResponderEnvironmentStorage { | |
var testService: TestService? { | |
get { testServiceAssociation[self] } | |
set { testServiceAssociation[self] = newValue } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
@propertyWrapper | |
struct ResponderEnvironment<Value> { | |
private let key: Key | |
init(_ key: Key) { | |
self.key = key | |
} | |
var wrappedValue: Value { | |
fatalError() | |
} | |
static subscript <EnclosingSelf: UIResponder> ( | |
_enclosingInstance object: EnclosingSelf, | |
wrapped wrappedKeyPath: Key, | |
storage storageKeyPath: KeyPath<EnclosingSelf, Self>) -> Value | |
{ | |
object.environmentValue(wrappedKeyPath) | |
} | |
typealias Key = KeyPath<Storage, Value?> | |
} | |
extension ResponderEnvironment { | |
typealias Storage = ResponderEnvironmentStorage | |
} | |
final class ResponderEnvironmentStorage: NSObject { | |
} | |
private let responderEnvironmentStorageAssociation: AssociatedObject<ResponderEnvironmentStorage> = .init() | |
extension UIResponder { | |
fileprivate var storage: ResponderEnvironmentStorage { | |
get { | |
if let storage = responderEnvironmentStorageAssociation[self] { return storage } | |
let newStorage = ResponderEnvironmentStorage() | |
self.storage = newStorage | |
return newStorage | |
} | |
set { responderEnvironmentStorageAssociation[self] = newValue } | |
} | |
fileprivate func environmentValue <Value> (_ key: KeyPath<ResponderEnvironmentStorage, Value?>) -> Value! { | |
storage[keyPath: key] | |
?? next?.environmentValue(key) | |
} | |
func setEnvironmentValue <Value> ( | |
_ value: Value, | |
for key: WritableKeyPath<ResponderEnvironmentStorage, Value>) | |
{ | |
storage[keyPath: key] = value | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment