Skip to content

Commit

Permalink
Fix SwiftUI bug, when object realloceted after invalidate view
Browse files Browse the repository at this point in the history
  • Loading branch information
SpectralDragon committed Nov 17, 2019
1 parent 043c612 commit f4d152c
Show file tree
Hide file tree
Showing 25 changed files with 244 additions and 111 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
/.build
/Packages
/*.xcodeproj
/xcuserdata
/.swiftpm
/Pods
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# SwiftDI
SwiftDI it's a tool for Dependency Injection using `@propertyDelegate`. Right now SwiftDI is alpha version. **Be careful!**
SwiftDI it's a tool for Dependency Injection using `@propertyWrapper`. Right now SwiftDI is alpha version. **Be careful!**

SwiftDI works with Swift 5.1 only and SwiftUI.

Expand Down Expand Up @@ -61,13 +61,14 @@ Does it! You're finish setup your DI container.

## SwiftDI ❤️ SwiftUI!

SwiftDI also supports `SwiftUI` framework. You can inject `BindableObject` and property automatically connect to view state.
For this magic just use `@InjectableObjectBinding`
SwiftDI also supports `SwiftUI` framework.
You can inject your `BindableObject` and property automatically connect to view state.
For this magic just use `@EnvironmentObservedInject`

```swift
struct ContentView: View {

@InjectableObjectBinding var viewModel: ContentViewModel
@EnvironmentObservedInject var viewModel: ContentViewModel

var body: some View {
HStack {
Expand All @@ -77,6 +78,42 @@ struct ContentView: View {
}
```

For non-mutating view object use `@EnvironmentInject`:

```swift
struct ContentView: View {

@EnvironmentInject var authService: AuthorizationService

var body: some View {
HStack {
Text("Waiting...")
}.onAppear { self.authService.auth() }
}
}
```

By default SwiftDI using shared container, but if you wanna pass custom container to view using method `inject(container:)` for view:
```swift
let container = DIContainer()

let view = HomeView().inject(container: container)
```

Or if you wanna add some method to container using method `environmentInject`:

```swift

// SceneDelegate.swift

let window = UIWindow(windowScene: windowScene)
let authService = AuthorizationService(window: window)

let view = HomeView().environmentInject(authService)

// etc
```

## Scopes
SwiftDI supports object scopes, you can use method `lifeCycle`

Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftDI/DIComponentContext.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// DIComponentContext.swift
//
// SwiftDI
//
// Created by v.a.prusakov on 01/07/2019.
//
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftDI/DIComponentManager.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// DIComponentManager.swift
//
// SwiftDI
//
// Created by v.a.prusakov on 01/07/2019.
//
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftDI/DIContainer.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//
// DIContainer.swift
//
// SwiftDI
//
// Created by v.a.prusakov on 01/07/2019.
//

import Foundation

public class DIContainer: DIContainerConvertible, CustomStringConvertible {
public final class DIContainer: CustomStringConvertible {

public var description: String {
return componentManager.registerContainers.description
Expand Down
20 changes: 0 additions & 20 deletions Sources/SwiftDI/DIContainerConvertible.swift

This file was deleted.

2 changes: 1 addition & 1 deletion Sources/SwiftDI/DIPart.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// DIPart.swift
//
// SwiftDI
//
// Created by v.a.prusakov on 01/07/2019.
//
Expand Down
16 changes: 10 additions & 6 deletions Sources/SwiftDI/Injectable.swift → Sources/SwiftDI/Inject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,27 @@
import Foundation
import Combine

@available(*, deprecated, renamed: "Inject")
public typealias Injectable = Inject

/// Read only property wrapper injector.
/// Injectable using lazy initialization, because instead cycle dependencies will crash in init.

@propertyWrapper
public struct Injectable<T> {
public struct Inject<Value> {

typealias LazyInject = () -> T
// Injectable using lazy initialization, because instead cycle dependencies will crash in init.
typealias LazyInject = () -> Value

var _value: T?
var _value: Value?
var lazy: LazyInject?

public init() {
let bundle = (T.self as? AnyClass).flatMap { Bundle(for: $0) }
let bundle = (Value.self as? AnyClass).flatMap { Bundle(for: $0) }
let lazy: LazyInject = { SwiftDI.sharedContainer.resolve(bundle: bundle) }
self.lazy = lazy
}

public var wrappedValue: T {
public var wrappedValue: Value {
mutating get {
if let value = _value {
return value
Expand Down
54 changes: 0 additions & 54 deletions Sources/SwiftDI/InjectableObjectBinding.swift

This file was deleted.

4 changes: 2 additions & 2 deletions Sources/SwiftDI/SwiftDI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ public enum SwiftDI {
public static var lifeCycle = DILifeCycle.objectGraph
}

internal private(set) static var sharedContainer: DIContainerConvertible = DIContainer()
internal private(set) static var sharedContainer: DIContainer = DIContainer()

/// Use container for inject dependencies using `@Injectable` and `@InjecatableObjectBinding`.
/// Call method `didConnectToSwiftDI` when container passed to method. Initialize your singletons here.
public static func useContainer(_ container: DIContainerConvertible) {
public static func useContainer(_ container: DIContainer) {
self.sharedContainer = container
container.didConnectToSwiftDI()
}
Expand Down
27 changes: 27 additions & 0 deletions Sources/SwiftDI/SwiftUI/EnvironmentInject.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// EnvironmentInject.swift
// SwiftDI
//
// Created by Vladislav Prusakov on 17.11.2019.
//

#if canImport(SwiftUI)
import SwiftUI
import Combine

/// A property wrapper that inject object from environment container
/// Read only object
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper
public struct EnvironmentInject<Value: AnyObject>: DynamicProperty {

public let wrappedValue: Value

public init() {
let bundle = Bundle(for: Value.self)
let resolvedValue = Environment(\.container).wrappedValue.resolve(bundle: bundle) as Value
self.wrappedValue = resolvedValue
}
}

#endif
36 changes: 36 additions & 0 deletions Sources/SwiftDI/SwiftUI/EnvironmentObservedInject.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// EnvironmentObservedInject.swift
// SwiftDI
//
// Created by v.a.prusakov on 27/06/2019.
//

#if canImport(SwiftUI)
import SwiftUI
import Combine

/// A dynamic view property that subscribes to a `ObservableObject` automatically invalidating the view
/// when it changes.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper
public struct EnvironmentObservedInject<Value: ObservableObject>: DynamicProperty {

@ObservedObject private var _wrappedValue: Value
public var wrappedValue: Value {
_wrappedValue
}

public init() {
let bundle = Bundle(for: Value.self)
let resolvedValue = Environment(\.container).wrappedValue.resolve(bundle: bundle) as Value
self.__wrappedValue = ObservedObject<Value>(initialValue: resolvedValue)
}

/// The binding value, as "unwrapped" by accessing `$foo` on a `@Binding` property.
public var projectedValue: ObservedObject<Value>.Wrapper {
return __wrappedValue.projectedValue
}
}

#endif

37 changes: 37 additions & 0 deletions Sources/SwiftDI/SwiftUI/View+SwiftDI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// View+SwiftDI.swift
// SwiftDI
//
// Created by Vladislav Prusakov on 17.11.2019.
//

#if canImport(SwiftUI)
import SwiftUI

public extension View {

/// Set dependency container to view
func inject(container: DIContainer) -> some View {
self.environment(\.container, container)
}

// Inject property into container
func environmentInject<Value>(_ value: Value) -> some View {
return self.transformEnvironment(\.container) { con in
con.register({ value })
}
}
}

struct DIContainerEnvironmentKey: EnvironmentKey {
static var defaultValue: DIContainer = SwiftDI.sharedContainer
}

extension EnvironmentValues {
var container: DIContainer {
get { self[DIContainerEnvironmentKey.self] }
set { self[DIContainerEnvironmentKey.self] = newValue }
}
}

#endif
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,26 @@ extension InjectableProperty {
}
}

extension Injectable: InjectableProperty {
extension Inject: InjectableProperty {
var type: Any.Type {
return T.self
return Value.self
}
}

#if canImport(SwiftUI)

@available(iOS 13.0, *)
extension InjectableObjectBinding: InjectableProperty {
extension EnvironmentInject: InjectableProperty {
var type: Any.Type {
return BindableObjectType.self
return Value.self
}
}

@available(iOS 13.0, *)
extension EnvironmentObservedInject: InjectableProperty {
var type: Any.Type {
return Value.self
}
}

#endif
File renamed without changes.
6 changes: 6 additions & 0 deletions SwiftDIDemo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
/.swiftpm
/Pods
2 changes: 1 addition & 1 deletion SwiftDIDemo/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
platform :ios, '13.0'

target 'SwiftDIDemo' do
# Comment the next line if you don't want to use dynamic frameworks
Expand Down
Loading

0 comments on commit f4d152c

Please sign in to comment.