diff --git a/Generator/Sources/NeedleFramework/Generating/Serializers/DependencyProviderSerializer.swift b/Generator/Sources/NeedleFramework/Generating/Serializers/DependencyProviderSerializer.swift index eba992a3..e47845d0 100644 --- a/Generator/Sources/NeedleFramework/Generating/Serializers/DependencyProviderSerializer.swift +++ b/Generator/Sources/NeedleFramework/Generating/Serializers/DependencyProviderSerializer.swift @@ -35,6 +35,8 @@ class DependencyProviderFuncSerializer: Serializer { func serialize() -> String { return """ /// \(provider.unprocessed.pathString) +@preconcurrency +@MainActor private func \(funcNameSerializer.serialize())(_ component: NeedleFoundation.Scope) -> AnyObject { return \(classNameSerializer.serialize())(\(paramsSerializer.serialize())) }\n diff --git a/Generator/Sources/NeedleFramework/Generating/Serializers/OutputSerializer.swift b/Generator/Sources/NeedleFramework/Generating/Serializers/OutputSerializer.swift index 023b16b7..6906c564 100644 --- a/Generator/Sources/NeedleFramework/Generating/Serializers/OutputSerializer.swift +++ b/Generator/Sources/NeedleFramework/Generating/Serializers/OutputSerializer.swift @@ -68,6 +68,8 @@ class OutputSerializer: Serializer { let traversalHelpers = (1...maxLevel).map { num in return """ + @preconcurrency + @MainActor private func parent\(num)(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope { return component\(String(repeating: ".parent", count: num)) } @@ -101,7 +103,10 @@ class OutputSerializer: Serializer { .trimmingCharacters(in: .whitespacesAndNewlines) let regNum = ($0 / linesPerHelper) + 1 registrationHelperFuncs.append(""" - @inline(never) private func register\(regNum)() { + @inline(never) + @preconcurrency + @MainActor + private func register\(regNum)() { \(helperBody) } """) @@ -132,11 +137,15 @@ class OutputSerializer: Serializer { #endif + @preconcurrency + @MainActor private func factoryEmptyDependencyProvider(_ component: NeedleFoundation.Scope) -> AnyObject { return EmptyDependencyProvider(component: component) } // MARK: - Registration + @preconcurrency + @MainActor private func registerProviderFactory(_ componentPath: String, _ factory: @escaping (NeedleFoundation.Scope) -> AnyObject) { __DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: componentPath, factory) } @@ -146,6 +155,8 @@ class OutputSerializer: Serializer { \(registrationHelpers) #endif + @preconcurrency + @MainActor public func registerProviderFactories() { #if !NEEDLE_DYNAMIC \(registrationBody) diff --git a/Generator/bin/needle b/Generator/bin/needle index 9550c1c6..fb74886a 100755 Binary files a/Generator/bin/needle and b/Generator/bin/needle differ diff --git a/Sample/MVC/TicTacToe/Sources/Game/GameComponent.swift b/Sample/MVC/TicTacToe/Sources/Game/GameComponent.swift index bad0eaa5..1c045ce7 100644 --- a/Sample/MVC/TicTacToe/Sources/Game/GameComponent.swift +++ b/Sample/MVC/TicTacToe/Sources/Game/GameComponent.swift @@ -17,6 +17,7 @@ import NeedleFoundation import UIKit +@MainActor protocol GameDependency: Dependency { var mutableScoreStream: MutableScoreStream { get } var playersStream: PlayersStream { get } @@ -40,6 +41,7 @@ class GameComponent: Component, GameBuilder { // Use a builder protocol to allow mocking for unit tests. At the same time, // this allows GameViewController to be initialized lazily. +@MainActor protocol GameBuilder { var gameViewController: UIViewController { get } } diff --git a/Sample/MVC/TicTacToe/Sources/Game/GameViewController.swift b/Sample/MVC/TicTacToe/Sources/Game/GameViewController.swift index 233e5b93..8c4a3314 100644 --- a/Sample/MVC/TicTacToe/Sources/Game/GameViewController.swift +++ b/Sample/MVC/TicTacToe/Sources/Game/GameViewController.swift @@ -21,7 +21,7 @@ import UIKit private let rowCount = 3 private let colCount = 3 private let sectionCount = 1 -private let cellSize: CGFloat = UIScreen.main.bounds.width / CGFloat(colCount) +@MainActor let cellSize: CGFloat = UIScreen.main.bounds.width / CGFloat(colCount) private let cellIdentifier = "TicTacToeCell" private enum Players: Int { diff --git a/Sample/MVC/TicTacToe/Sources/LoggedIn/LoggedInComponent.swift b/Sample/MVC/TicTacToe/Sources/LoggedIn/LoggedInComponent.swift index 84ce32c2..d279e8b0 100644 --- a/Sample/MVC/TicTacToe/Sources/LoggedIn/LoggedInComponent.swift +++ b/Sample/MVC/TicTacToe/Sources/LoggedIn/LoggedInComponent.swift @@ -42,6 +42,7 @@ class LoggedInComponent: Component, LoggedInBuilder { // Use a builder protocol to allow mocking for unit tests. At the same time, // this allows LoggedInViewController to be initialized lazily. +@MainActor protocol LoggedInBuilder { var loggedInViewController: UIViewController { get } } diff --git a/Sample/MVC/TicTacToe/Sources/LoggedIn/LoggedInViewController.swift b/Sample/MVC/TicTacToe/Sources/LoggedIn/LoggedInViewController.swift index 803f8d4f..7f0ff6d8 100644 --- a/Sample/MVC/TicTacToe/Sources/LoggedIn/LoggedInViewController.swift +++ b/Sample/MVC/TicTacToe/Sources/LoggedIn/LoggedInViewController.swift @@ -97,10 +97,8 @@ class LoggedInViewController: UIViewController, ScoreSheetListener { } func done() { - dismiss(animated: true) - } - - deinit { - gameDisposable?.dispose() + dismiss(animated: true) { [weak self] in + self?.gameDisposable?.dispose() + } } } diff --git a/Sample/MVC/TicTacToe/Sources/LoggedOut/LoggedOutComponent.swift b/Sample/MVC/TicTacToe/Sources/LoggedOut/LoggedOutComponent.swift index 0db7b36a..af789f81 100644 --- a/Sample/MVC/TicTacToe/Sources/LoggedOut/LoggedOutComponent.swift +++ b/Sample/MVC/TicTacToe/Sources/LoggedOut/LoggedOutComponent.swift @@ -17,6 +17,7 @@ import NeedleFoundation import UIKit +@MainActor protocol LoggedOutDependency: Dependency { var mutablePlayersStream: MutablePlayersStream { get } } @@ -30,6 +31,7 @@ class LoggedOutComponent: Component, LoggedOutBuilder { // Use a builder protocol to allow mocking for unit tests. At the same time, // this allows LoggedOutViewController to be initialized lazily. +@MainActor protocol LoggedOutBuilder { var loggedOutViewController: UIViewController { get } } diff --git a/Sample/MVC/TicTacToe/Sources/NeedleGenerated.swift b/Sample/MVC/TicTacToe/Sources/NeedleGenerated.swift index 09c9b730..b93b5803 100644 --- a/Sample/MVC/TicTacToe/Sources/NeedleGenerated.swift +++ b/Sample/MVC/TicTacToe/Sources/NeedleGenerated.swift @@ -23,10 +23,14 @@ private let needleDependenciesHash : String? = nil // MARK: - Traversal Helpers +@preconcurrency +@MainActor private func parent1(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope { return component.parent } +@preconcurrency +@MainActor private func parent2(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope { return component.parent.parent } @@ -50,6 +54,8 @@ private class GameDependency1ab5926a977f706d3195Provider: GameDependency { } } /// ^->RootComponent->LoggedInComponent->GameComponent +@preconcurrency +@MainActor private func factorycf9c02c4def4e3d508816cd03d3cf415b70dfb0e(_ component: NeedleFoundation.Scope) -> AnyObject { return GameDependency1ab5926a977f706d3195Provider(loggedInComponent: parent1(component) as! LoggedInComponent, rootComponent: parent2(component) as! RootComponent) } @@ -63,10 +69,14 @@ private class ScoreSheetDependency97f2595a691a56781aaaProvider: ScoreSheetDepend } } /// ^->RootComponent->LoggedInComponent->GameComponent->ScoreSheetComponent +@preconcurrency +@MainActor private func factory3f7d60e2119708f293bac0d8c882e1e0d9b5eda1(_ component: NeedleFoundation.Scope) -> AnyObject { return ScoreSheetDependency97f2595a691a56781aaaProvider(loggedInComponent: parent2(component) as! LoggedInComponent) } /// ^->RootComponent->LoggedInComponent->ScoreSheetComponent +@preconcurrency +@MainActor private func factory3f7d60e2119708f293ba0b20504d5a9e5588d7b3(_ component: NeedleFoundation.Scope) -> AnyObject { return ScoreSheetDependency97f2595a691a56781aaaProvider(loggedInComponent: parent1(component) as! LoggedInComponent) } @@ -80,6 +90,8 @@ private class LoggedOutDependencyacada53ea78d270efa2fProvider: LoggedOutDependen } } /// ^->RootComponent->LoggedOutComponent +@preconcurrency +@MainActor private func factory1434ff4463106e5c4f1bb3a8f24c1d289f2c0f2e(_ component: NeedleFoundation.Scope) -> AnyObject { return LoggedOutDependencyacada53ea78d270efa2fProvider(rootComponent: parent1(component) as! RootComponent) } @@ -105,31 +117,39 @@ extension LoggedOutComponent: Registration { extension LoggedInComponent: Registration { public func registerItems() { - + localTable["scoreStream-ScoreStream"] = { [unowned self] in self.scoreStream as Any } } } extension RootComponent: Registration { public func registerItems() { - + localTable["playersStream-PlayersStream"] = { [unowned self] in self.playersStream as Any } + localTable["mutablePlayersStream-MutablePlayersStream"] = { [unowned self] in self.mutablePlayersStream as Any } } } #endif +@preconcurrency +@MainActor private func factoryEmptyDependencyProvider(_ component: NeedleFoundation.Scope) -> AnyObject { return EmptyDependencyProvider(component: component) } // MARK: - Registration +@preconcurrency +@MainActor private func registerProviderFactory(_ componentPath: String, _ factory: @escaping (NeedleFoundation.Scope) -> AnyObject) { __DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: componentPath, factory) } #if !NEEDLE_DYNAMIC -@inline(never) private func register1() { +@inline(never) +@preconcurrency +@MainActor +private func register1() { registerProviderFactory("^->RootComponent->LoggedInComponent->GameComponent", factorycf9c02c4def4e3d508816cd03d3cf415b70dfb0e) registerProviderFactory("^->RootComponent->LoggedInComponent->GameComponent->ScoreSheetComponent", factory3f7d60e2119708f293bac0d8c882e1e0d9b5eda1) registerProviderFactory("^->RootComponent->LoggedInComponent->ScoreSheetComponent", factory3f7d60e2119708f293ba0b20504d5a9e5588d7b3) @@ -139,6 +159,8 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi } #endif +@preconcurrency +@MainActor public func registerProviderFactories() { #if !NEEDLE_DYNAMIC register1() diff --git a/Sample/MVC/TicTacToe/Sources/Root/RootViewController.swift b/Sample/MVC/TicTacToe/Sources/Root/RootViewController.swift index b58342e0..1f86d26a 100644 --- a/Sample/MVC/TicTacToe/Sources/Root/RootViewController.swift +++ b/Sample/MVC/TicTacToe/Sources/Root/RootViewController.swift @@ -52,6 +52,11 @@ class RootViewController: UIViewController { updateChildViewController() } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + playersStreamDisposable?.dispose() + } + private func updateChildViewController() { if playersStreamDisposable != nil { return @@ -89,8 +94,4 @@ class RootViewController: UIViewController { present(viewController, animated: true, completion: nil) } } - - deinit { - playersStreamDisposable?.dispose() - } } diff --git a/Sample/MVC/TicTacToe/Sources/ScoreSheet/ScoreSheetComponent.swift b/Sample/MVC/TicTacToe/Sources/ScoreSheet/ScoreSheetComponent.swift index ec706a8e..c1565b78 100644 --- a/Sample/MVC/TicTacToe/Sources/ScoreSheet/ScoreSheetComponent.swift +++ b/Sample/MVC/TicTacToe/Sources/ScoreSheet/ScoreSheetComponent.swift @@ -18,6 +18,7 @@ import NeedleFoundation import RxSwift import UIKit +@MainActor protocol ScoreSheetDependency: Dependency { var scoreStream: ScoreStream { get } } @@ -30,6 +31,7 @@ class ScoreSheetComponent: Component, ScoreSheetBuilder { } // Use a builder protocol to allow mocking for unit tests +@MainActor protocol ScoreSheetBuilder { var scoreSheetViewController: UIViewController { get } } diff --git a/Sample/MVC/TicTacToe/Sources/ScoreSheet/ScoreSheetViewController.swift b/Sample/MVC/TicTacToe/Sources/ScoreSheet/ScoreSheetViewController.swift index 0f246503..d4293720 100644 --- a/Sample/MVC/TicTacToe/Sources/ScoreSheet/ScoreSheetViewController.swift +++ b/Sample/MVC/TicTacToe/Sources/ScoreSheet/ScoreSheetViewController.swift @@ -19,6 +19,7 @@ import RxSwift import SnapKit import UIKit +@MainActor protocol ScoreSheetListener { func done() } diff --git a/Sample/MVC/TicTacToe/Tests/RootViewControllerTests.swift b/Sample/MVC/TicTacToe/Tests/RootViewControllerTests.swift index 4bb5dfea..60a92cd4 100644 --- a/Sample/MVC/TicTacToe/Tests/RootViewControllerTests.swift +++ b/Sample/MVC/TicTacToe/Tests/RootViewControllerTests.swift @@ -18,6 +18,7 @@ import RxSwift import XCTest +@MainActor class RootViewControllerTests: XCTestCase { private var playersStream: PlayersStreamMock! diff --git a/Sample/MVC/TicTacToe/TicTacToe.xcodeproj/project.pbxproj b/Sample/MVC/TicTacToe/TicTacToe.xcodeproj/project.pbxproj index 1999a8df..5c04d30b 100644 --- a/Sample/MVC/TicTacToe/TicTacToe.xcodeproj/project.pbxproj +++ b/Sample/MVC/TicTacToe/TicTacToe.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -567,6 +567,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.uber.needle.tictactoe.TicTacToe; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -587,6 +588,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.uber.needle.tictactoe.TicTacToe; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/Sample/Pluginized/TicTacToe/ScoreSheet/ScoreSheetComponent.swift b/Sample/Pluginized/TicTacToe/ScoreSheet/ScoreSheetComponent.swift index 50460948..a554ee83 100644 --- a/Sample/Pluginized/TicTacToe/ScoreSheet/ScoreSheetComponent.swift +++ b/Sample/Pluginized/TicTacToe/ScoreSheet/ScoreSheetComponent.swift @@ -18,6 +18,7 @@ import NeedleFoundation import RxSwift import UIKit +@MainActor public protocol ScoreSheetDependency: Dependency { var scoreStream: ScoreStream { get } } @@ -30,6 +31,7 @@ public class ScoreSheetComponent: Component, ScoreSheetBui } // Use a builder protocol to allow mocking for unit tests +@MainActor public protocol ScoreSheetBuilder { var scoreSheetViewController: UIViewController { get } } diff --git a/Sample/Pluginized/TicTacToe/ScoreSheet/ScoreSheetViewController.swift b/Sample/Pluginized/TicTacToe/ScoreSheet/ScoreSheetViewController.swift index 9e01811d..22ebc2b5 100644 --- a/Sample/Pluginized/TicTacToe/ScoreSheet/ScoreSheetViewController.swift +++ b/Sample/Pluginized/TicTacToe/ScoreSheet/ScoreSheetViewController.swift @@ -19,6 +19,7 @@ import RxSwift import SnapKit import UIKit +@MainActor public protocol ScoreSheetListener { func done() } diff --git a/Sample/Pluginized/TicTacToe/TicTacToe.xcodeproj/project.pbxproj b/Sample/Pluginized/TicTacToe/TicTacToe.xcodeproj/project.pbxproj index be3c7288..abf7368f 100644 --- a/Sample/Pluginized/TicTacToe/TicTacToe.xcodeproj/project.pbxproj +++ b/Sample/Pluginized/TicTacToe/TicTacToe.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -987,6 +987,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = complete; }; name = Debug; }; @@ -1041,6 +1042,7 @@ SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_STRICT_CONCURRENCY = complete; VALIDATE_PRODUCT = YES; }; name = Release; diff --git a/Sample/Pluginized/TicTacToe/TicTacToeCore/Game/GameComponent.swift b/Sample/Pluginized/TicTacToe/TicTacToeCore/Game/GameComponent.swift index c1370cef..13eae8a3 100644 --- a/Sample/Pluginized/TicTacToe/TicTacToeCore/Game/GameComponent.swift +++ b/Sample/Pluginized/TicTacToe/TicTacToeCore/Game/GameComponent.swift @@ -19,11 +19,13 @@ import ScoreSheet import TicTacToeIntegrations import UIKit +@MainActor protocol GameDependency: Dependency { var mutableScoreStream: MutableScoreStream { get } var playersStream: PlayersStream { get } } +@MainActor protocol GamePluginExtension: PluginExtension { var scoreSheetBuilder: ScoreSheetBuilder { get } } @@ -62,6 +64,7 @@ class GameComponent: UberPluginizedComponent, LoggedOutBuilder { // Use a builder protocol to allow mocking for unit tests. At the same time, // this allows LoggedOutViewController to be initialized lazily. +@MainActor protocol LoggedOutBuilder { var loggedOutViewController: UIViewController { get } } diff --git a/Sample/Pluginized/TicTacToe/TicTacToeCore/NeedleGenerated.swift b/Sample/Pluginized/TicTacToe/TicTacToeCore/NeedleGenerated.swift index 0d09033b..0a513fb7 100644 --- a/Sample/Pluginized/TicTacToe/TicTacToeCore/NeedleGenerated.swift +++ b/Sample/Pluginized/TicTacToe/TicTacToeCore/NeedleGenerated.swift @@ -21,18 +21,24 @@ import TicTacToeIntegrations import UIKit // swiftlint:disable unused_declaration -private let needleDependenciesHash : String? = "4b585865bab35437b0cbc60e9d74b1b1" +private let needleDependenciesHash : String? = "7d206250bd06106f1c31c7f537788c58" // MARK: - Traversal Helpers +@preconcurrency +@MainActor private func parent1(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope { return component.parent } +@preconcurrency +@MainActor private func parent2(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope { return component.parent.parent } +@preconcurrency +@MainActor private func parent3(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope { return component.parent.parent.parent } @@ -51,6 +57,8 @@ private class LoggedOutDependencyacada53ea78d270efa2fProvider: LoggedOutDependen } } /// ^->RootComponent->LoggedOutComponent +@preconcurrency +@MainActor private func factory1434ff4463106e5c4f1bb3a8f24c1d289f2c0f2e(_ component: NeedleFoundation.Scope) -> AnyObject { return LoggedOutDependencyacada53ea78d270efa2fProvider(rootComponent: parent1(component) as! RootComponent) } @@ -64,6 +72,8 @@ private class ScoreSheetDependencyea879b8e06763171478bProvider: ScoreSheetDepend } } /// ^->RootComponent->LoggedInComponent->GameComponent->GameNonCoreComponent->ScoreSheetComponent +@preconcurrency +@MainActor private func factoryb11b7d1dec7e3c9b3dca49b41e44e0ed6a6f8eaf(_ component: NeedleFoundation.Scope) -> AnyObject { return ScoreSheetDependencyea879b8e06763171478bProvider(loggedInComponent: parent3(component) as! LoggedInComponent) } @@ -77,6 +87,8 @@ private class ScoreSheetDependency6fb80fa6e1ee31d9ba11Provider: ScoreSheetDepend } } /// ^->RootComponent->LoggedInComponent->LoggedInNonCoreComponent->ScoreSheetComponent +@preconcurrency +@MainActor private func factory3306c50e89e2421d0b0c65d055996113f3c13de1(_ component: NeedleFoundation.Scope) -> AnyObject { return ScoreSheetDependency6fb80fa6e1ee31d9ba11Provider(loggedInNonCoreComponent: parent1(component) as! LoggedInNonCoreComponent) } @@ -95,6 +107,8 @@ private class GameDependency1ab5926a977f706d3195Provider: GameDependency { } } /// ^->RootComponent->LoggedInComponent->GameComponent +@preconcurrency +@MainActor private func factorycf9c02c4def4e3d508816cd03d3cf415b70dfb0e(_ component: NeedleFoundation.Scope) -> AnyObject { return GameDependency1ab5926a977f706d3195Provider(loggedInComponent: parent1(component) as! LoggedInComponent, rootComponent: parent2(component) as! RootComponent) } @@ -133,7 +147,8 @@ extension LoggedOutComponent: Registration { extension RootComponent: Registration { public func registerItems() { - + localTable["playersStream-PlayersStream"] = { [unowned self] in self.playersStream as Any } + localTable["mutablePlayersStream-MutablePlayersStream"] = { [unowned self] in self.mutablePlayersStream as Any } } } extension ScoreSheetComponent: Registration { @@ -144,15 +159,15 @@ extension ScoreSheetComponent: Registration { extension GameNonCoreComponent: Registration { public func registerItems() { - localTable["scoreSheetBuilder-ScoreSheetBuilder"] = { self.scoreSheetBuilder as Any } + localTable["scoreSheetBuilder-ScoreSheetBuilder"] = { [unowned self] in self.scoreSheetBuilder as Any } } } extension LoggedInNonCoreComponent: Registration { public func registerItems() { - localTable["scoreSheetBuilder-ScoreSheetBuilder"] = { self.scoreSheetBuilder as Any } - localTable["mutableScoreStream-MutableScoreStream"] = { self.mutableScoreStream as Any } - localTable["scoreStream-ScoreStream"] = { self.scoreStream as Any } + localTable["scoreSheetBuilder-ScoreSheetBuilder"] = { [unowned self] in self.scoreSheetBuilder as Any } + localTable["mutableScoreStream-MutableScoreStream"] = { [unowned self] in self.mutableScoreStream as Any } + localTable["scoreStream-ScoreStream"] = { [unowned self] in self.scoreStream as Any } } } extension GameComponent: Registration { @@ -185,18 +200,25 @@ extension LoggedInComponent: ExtensionRegistration { #endif +@preconcurrency +@MainActor private func factoryEmptyDependencyProvider(_ component: NeedleFoundation.Scope) -> AnyObject { return EmptyDependencyProvider(component: component) } // MARK: - Registration +@preconcurrency +@MainActor private func registerProviderFactory(_ componentPath: String, _ factory: @escaping (NeedleFoundation.Scope) -> AnyObject) { __DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: componentPath, factory) } #if !NEEDLE_DYNAMIC -@inline(never) private func register1() { +@inline(never) +@preconcurrency +@MainActor +private func register1() { registerProviderFactory("^->RootComponent->LoggedOutComponent", factory1434ff4463106e5c4f1bb3a8f24c1d289f2c0f2e) registerProviderFactory("^->RootComponent", factoryEmptyDependencyProvider) registerProviderFactory("^->RootComponent->LoggedInComponent->GameComponent->GameNonCoreComponent->ScoreSheetComponent", factoryb11b7d1dec7e3c9b3dca49b41e44e0ed6a6f8eaf) @@ -214,6 +236,8 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi } #endif +@preconcurrency +@MainActor public func registerProviderFactories() { #if !NEEDLE_DYNAMIC register1() diff --git a/Sample/Pluginized/TicTacToe/TicTacToeCore/ObservableViewController.swift b/Sample/Pluginized/TicTacToe/TicTacToeCore/ObservableViewController.swift index 4025ac6b..3db4c603 100644 --- a/Sample/Pluginized/TicTacToe/TicTacToeCore/ObservableViewController.swift +++ b/Sample/Pluginized/TicTacToe/TicTacToeCore/ObservableViewController.swift @@ -21,7 +21,6 @@ import UIKit enum ViewControllerLifecycle { case viewDidAppear case viewDidDisappear - case `deinit` } /// A view controller whose lifecycle events can be observed via Rx. @@ -47,8 +46,4 @@ class ObservableViewController: UIViewController { // MARK: - Private private let lifecycleSubject = ReplaySubject.create(bufferSize: 1) - - deinit { - lifecycleSubject.onNext(.deinit) - } } diff --git a/Sample/Pluginized/TicTacToe/TicTacToeCore/PluginizedScopeLifecycle.swift b/Sample/Pluginized/TicTacToe/TicTacToeCore/PluginizedScopeLifecycle.swift index 734e1cc1..be5d10dd 100644 --- a/Sample/Pluginized/TicTacToe/TicTacToeCore/PluginizedScopeLifecycle.swift +++ b/Sample/Pluginized/TicTacToe/TicTacToeCore/PluginizedScopeLifecycle.swift @@ -25,8 +25,6 @@ extension ObservableViewController: PluginizedScopeLifecycleObservable { let disposable = lifecycle .subscribe(onNext: { (event: ViewControllerLifecycle) in switch event { - case .deinit: - observer(.deinit) case .viewDidAppear: observer(.active) case .viewDidDisappear: diff --git a/Sample/Pluginized/TicTacToe/TicTacToeCore/Root/RootViewController.swift b/Sample/Pluginized/TicTacToe/TicTacToeCore/Root/RootViewController.swift index b58342e0..1f86d26a 100644 --- a/Sample/Pluginized/TicTacToe/TicTacToeCore/Root/RootViewController.swift +++ b/Sample/Pluginized/TicTacToe/TicTacToeCore/Root/RootViewController.swift @@ -52,6 +52,11 @@ class RootViewController: UIViewController { updateChildViewController() } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + playersStreamDisposable?.dispose() + } + private func updateChildViewController() { if playersStreamDisposable != nil { return @@ -89,8 +94,4 @@ class RootViewController: UIViewController { present(viewController, animated: true, completion: nil) } } - - deinit { - playersStreamDisposable?.dispose() - } } diff --git a/Sample/Pluginized/TicTacToe/TicTacToeCoreTests/RootViewControllerTests.swift b/Sample/Pluginized/TicTacToe/TicTacToeCoreTests/RootViewControllerTests.swift index 4bb5dfea..60a92cd4 100644 --- a/Sample/Pluginized/TicTacToe/TicTacToeCoreTests/RootViewControllerTests.swift +++ b/Sample/Pluginized/TicTacToe/TicTacToeCoreTests/RootViewControllerTests.swift @@ -18,6 +18,7 @@ import RxSwift import XCTest +@MainActor class RootViewControllerTests: XCTestCase { private var playersStream: PlayersStreamMock! diff --git a/Sample/SwiftUI-MVVM/TicTacToe/Sources/Game/GameComponent.swift b/Sample/SwiftUI-MVVM/TicTacToe/Sources/Game/GameComponent.swift index 33401fa5..80b8eebe 100644 --- a/Sample/SwiftUI-MVVM/TicTacToe/Sources/Game/GameComponent.swift +++ b/Sample/SwiftUI-MVVM/TicTacToe/Sources/Game/GameComponent.swift @@ -17,6 +17,7 @@ import NeedleFoundation import SwiftUI +@MainActor protocol GameDependency: Dependency { var mutableScoreStream: MutableScoreStream { get } var playersStream: PlayersStream { get } @@ -52,6 +53,7 @@ class GameComponent: Component, GameBuilder { // Use a builder protocol to allow mocking for unit tests. At the same time, // this allows GameView to be initialized lazily. +@MainActor protocol GameBuilder { var gameView: AnyView { get } } diff --git a/Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedIn/LoggedInComponent.swift b/Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedIn/LoggedInComponent.swift index f54653f7..cb361b56 100644 --- a/Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedIn/LoggedInComponent.swift +++ b/Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedIn/LoggedInComponent.swift @@ -52,6 +52,7 @@ class LoggedInComponent: Component, LoggedInBuilder { // Use a builder protocol to allow mocking for unit tests. At the same time, // this allows LoggedInView to be initialized lazily. +@MainActor protocol LoggedInBuilder { var loggedInView: AnyView { get } } diff --git a/Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedOut/LoggedOutComponent.swift b/Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedOut/LoggedOutComponent.swift index 1381638b..3a772955 100644 --- a/Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedOut/LoggedOutComponent.swift +++ b/Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedOut/LoggedOutComponent.swift @@ -17,6 +17,7 @@ import NeedleFoundation import SwiftUI +@MainActor protocol LoggedOutDependency: Dependency { var mutablePlayersStream: MutablePlayersStream { get } } @@ -28,7 +29,7 @@ class LoggedOutComponent: Component, LoggedOutBuilder { mutablePlayersStream: dependency.mutablePlayersStream ) } - + var loggedOutView: AnyView { return AnyView( LoggedOutView(viewModel: loggedOutViewModel) @@ -38,6 +39,7 @@ class LoggedOutComponent: Component, LoggedOutBuilder { // Use a builder protocol to allow mocking for unit tests. At the same time, // this allows LoggedOutView to be initialized lazily. +@MainActor protocol LoggedOutBuilder { var loggedOutView: AnyView { get } } diff --git a/Sample/SwiftUI-MVVM/TicTacToe/Sources/NeedleGenerated.swift b/Sample/SwiftUI-MVVM/TicTacToe/Sources/NeedleGenerated.swift index 34ea7652..b542f5b5 100644 --- a/Sample/SwiftUI-MVVM/TicTacToe/Sources/NeedleGenerated.swift +++ b/Sample/SwiftUI-MVVM/TicTacToe/Sources/NeedleGenerated.swift @@ -22,10 +22,14 @@ private let needleDependenciesHash : String? = nil // MARK: - Traversal Helpers +@preconcurrency +@MainActor private func parent1(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope { return component.parent } +@preconcurrency +@MainActor private func parent2(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope { return component.parent.parent } @@ -49,6 +53,8 @@ private class GameDependency1ab5926a977f706d3195Provider: GameDependency { } } /// ^->RootComponent->LoggedInComponent->GameComponent +@preconcurrency +@MainActor private func factorycf9c02c4def4e3d508816cd03d3cf415b70dfb0e(_ component: NeedleFoundation.Scope) -> AnyObject { return GameDependency1ab5926a977f706d3195Provider(loggedInComponent: parent1(component) as! LoggedInComponent, rootComponent: parent2(component) as! RootComponent) } @@ -62,10 +68,14 @@ private class ScoreSheetDependency97f2595a691a56781aaaProvider: ScoreSheetDepend } } /// ^->RootComponent->LoggedInComponent->GameComponent->ScoreSheetComponent +@preconcurrency +@MainActor private func factory3f7d60e2119708f293bac0d8c882e1e0d9b5eda1(_ component: NeedleFoundation.Scope) -> AnyObject { return ScoreSheetDependency97f2595a691a56781aaaProvider(loggedInComponent: parent2(component) as! LoggedInComponent) } /// ^->RootComponent->LoggedInComponent->ScoreSheetComponent +@preconcurrency +@MainActor private func factory3f7d60e2119708f293ba0b20504d5a9e5588d7b3(_ component: NeedleFoundation.Scope) -> AnyObject { return ScoreSheetDependency97f2595a691a56781aaaProvider(loggedInComponent: parent1(component) as! LoggedInComponent) } @@ -79,6 +89,8 @@ private class LoggedOutDependencyacada53ea78d270efa2fProvider: LoggedOutDependen } } /// ^->RootComponent->LoggedOutComponent +@preconcurrency +@MainActor private func factory1434ff4463106e5c4f1bb3a8f24c1d289f2c0f2e(_ component: NeedleFoundation.Scope) -> AnyObject { return LoggedOutDependencyacada53ea78d270efa2fProvider(rootComponent: parent1(component) as! RootComponent) } @@ -104,31 +116,39 @@ extension LoggedOutComponent: Registration { extension LoggedInComponent: Registration { public func registerItems() { - + localTable["scoreStream-ScoreStream"] = { [unowned self] in self.scoreStream as Any } } } extension RootComponent: Registration { public func registerItems() { - + localTable["playersStream-PlayersStream"] = { [unowned self] in self.playersStream as Any } + localTable["mutablePlayersStream-MutablePlayersStream"] = { [unowned self] in self.mutablePlayersStream as Any } } } #endif +@preconcurrency +@MainActor private func factoryEmptyDependencyProvider(_ component: NeedleFoundation.Scope) -> AnyObject { return EmptyDependencyProvider(component: component) } // MARK: - Registration +@preconcurrency +@MainActor private func registerProviderFactory(_ componentPath: String, _ factory: @escaping (NeedleFoundation.Scope) -> AnyObject) { __DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: componentPath, factory) } #if !NEEDLE_DYNAMIC -@inline(never) private func register1() { +@inline(never) +@preconcurrency +@MainActor +private func register1() { registerProviderFactory("^->RootComponent->LoggedInComponent->GameComponent", factorycf9c02c4def4e3d508816cd03d3cf415b70dfb0e) registerProviderFactory("^->RootComponent->LoggedInComponent->GameComponent->ScoreSheetComponent", factory3f7d60e2119708f293bac0d8c882e1e0d9b5eda1) registerProviderFactory("^->RootComponent->LoggedInComponent->ScoreSheetComponent", factory3f7d60e2119708f293ba0b20504d5a9e5588d7b3) @@ -138,6 +158,8 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi } #endif +@preconcurrency +@MainActor public func registerProviderFactories() { #if !NEEDLE_DYNAMIC register1() diff --git a/Sample/SwiftUI-MVVM/TicTacToe/Sources/ScoreSheet/ScoreSheetComponent.swift b/Sample/SwiftUI-MVVM/TicTacToe/Sources/ScoreSheet/ScoreSheetComponent.swift index 2db3b520..6b134cdb 100644 --- a/Sample/SwiftUI-MVVM/TicTacToe/Sources/ScoreSheet/ScoreSheetComponent.swift +++ b/Sample/SwiftUI-MVVM/TicTacToe/Sources/ScoreSheet/ScoreSheetComponent.swift @@ -17,6 +17,7 @@ import NeedleFoundation import SwiftUI +@MainActor protocol ScoreSheetDependency: Dependency { var scoreStream: ScoreStream { get } } @@ -28,7 +29,7 @@ class ScoreSheetComponent: Component, ScoreSheetBuilder { scoreStream: dependency.scoreStream ) } - + var scoreSheetView: AnyView { return AnyView( ScoreSheetView( @@ -39,6 +40,7 @@ class ScoreSheetComponent: Component, ScoreSheetBuilder { } // Use a builder protocol to allow mocking for unit tests +@MainActor protocol ScoreSheetBuilder { var scoreSheetView: AnyView { get } } diff --git a/Sample/SwiftUI-MVVM/TicTacToe/Sources/View+Extension.swift b/Sample/SwiftUI-MVVM/TicTacToe/Sources/View+Extension.swift index 8f2e0b0c..894c3f6e 100644 --- a/Sample/SwiftUI-MVVM/TicTacToe/Sources/View+Extension.swift +++ b/Sample/SwiftUI-MVVM/TicTacToe/Sources/View+Extension.swift @@ -18,6 +18,7 @@ import SwiftUI #if canImport(UIKit) extension View { + @MainActor func hideKeyboard() { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } diff --git a/Sample/SwiftUI-MVVM/TicTacToe/Tests/RootViewTests.swift b/Sample/SwiftUI-MVVM/TicTacToe/Tests/RootViewTests.swift index f256fa72..e3ad77d2 100644 --- a/Sample/SwiftUI-MVVM/TicTacToe/Tests/RootViewTests.swift +++ b/Sample/SwiftUI-MVVM/TicTacToe/Tests/RootViewTests.swift @@ -19,6 +19,7 @@ import SwiftUI import Combine import XCTest +@MainActor class RootViewTests: XCTestCase { private var loggedOutBuilder: LoggedOutBuilderMock! diff --git a/Sample/SwiftUI-MVVM/TicTacToe/TicTacToe.xcodeproj/project.pbxproj b/Sample/SwiftUI-MVVM/TicTacToe/TicTacToe.xcodeproj/project.pbxproj index 8f1a95a9..3cfaa2bf 100644 --- a/Sample/SwiftUI-MVVM/TicTacToe/TicTacToe.xcodeproj/project.pbxproj +++ b/Sample/SwiftUI-MVVM/TicTacToe/TicTacToe.xcodeproj/project.pbxproj @@ -589,6 +589,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.uber.needle.tictactoe.TicTacToe; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -610,6 +611,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.uber.needle.tictactoe.TicTacToe; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/Sources/NeedleFoundation/Bootstrap.swift b/Sources/NeedleFoundation/Bootstrap.swift index cdf50a19..5d8f8e80 100644 --- a/Sources/NeedleFoundation/Bootstrap.swift +++ b/Sources/NeedleFoundation/Bootstrap.swift @@ -19,6 +19,8 @@ import Foundation /// An empty protocol that can be used for any components that require no /// dependencies. This can be used as the dependnecy protocol of the root /// component of a dependency graph. +@preconcurrency +@MainActor public protocol EmptyDependency: AnyObject {} /// The dependency provider that conforms to `EmptyDependency`. This is diff --git a/Sources/NeedleFoundation/Component.swift b/Sources/NeedleFoundation/Component.swift index 075fe2a3..321aab15 100644 --- a/Sources/NeedleFoundation/Component.swift +++ b/Sources/NeedleFoundation/Component.swift @@ -17,6 +17,8 @@ import Foundation /// The base protocol of a dependency, enabling Needle's parsing process. +@preconcurrency +@MainActor public protocol Dependency: AnyObject {} #if NEEDLE_DYNAMIC @@ -29,6 +31,8 @@ public protocol Registration { /// from the `Component` base class, instead of using this protocol /// directly. /// @CreateMock +@preconcurrency +@MainActor public protocol Scope: AnyObject { /// The path to reach this component on the dependnecy graph. var path: [String] { get } diff --git a/Sources/NeedleFoundation/Internal/DependencyProviderRegistry.swift b/Sources/NeedleFoundation/Internal/DependencyProviderRegistry.swift index c5d8ff8b..92e09f86 100644 --- a/Sources/NeedleFoundation/Internal/DependencyProviderRegistry.swift +++ b/Sources/NeedleFoundation/Internal/DependencyProviderRegistry.swift @@ -26,9 +26,13 @@ import Foundation // method in the base component class. Generate extensions to all the // component subclasses that override the method to instantiate the // dependnecy providers. +@preconcurrency +@MainActor public class __DependencyProviderRegistry { /// The singleton instance. + @preconcurrency + @MainActor public static let instance = __DependencyProviderRegistry() /// Register the given factory closure with given key. diff --git a/Sources/NeedleFoundation/Pluginized/PluginizedScopeLifecycle.swift b/Sources/NeedleFoundation/Pluginized/PluginizedScopeLifecycle.swift index 17ef5fa8..7a225c8d 100644 --- a/Sources/NeedleFoundation/Pluginized/PluginizedScopeLifecycle.swift +++ b/Sources/NeedleFoundation/Pluginized/PluginizedScopeLifecycle.swift @@ -41,6 +41,8 @@ public protocol ObserverDisposable: AnyObject { } /// The observable of the lifecycle events of a pluginized scope. +@preconcurrency +@MainActor public protocol PluginizedScopeLifecycleObservable: AnyObject { /// Observe the lifecycle events with given observer.