Skip to content

Commit

Permalink
reworked project
Browse files Browse the repository at this point in the history
  • Loading branch information
Hadevs committed Jul 8, 2019
0 parents commit efeede4
Show file tree
Hide file tree
Showing 32 changed files with 1,406 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 Danil Kovalev

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
![alt text](header.png)

# SwiftInjector
[![Swift](https://img.shields.io/badge/swift-language-orange.svg)](https://swift.org)
[![Swift](https://img.shields.io/badge/dependency--injection-convinient-brightgreen.svg)]()
[![GitHub license](https://img.shields.io/badge/License-MIT-lightgrey.svg)](https://raw.githubusercontent.com/Boerworz/Gagat/master/LICENSE)


SwiftInjector - library for dependency injection, maden for convinient and fast properties connection

## Things already implemented:
1. Auto registration and autoresolving
Just for example:
![alt text](example_1.png)
![alt text](example_2.png)
![alt text](example_3.png)

2. Works even with private properties!
3. Resolving and registration with 'name' parameter
4. Classes each-to-each references without recursive crash (like in Swinject or DIP libraries)

## Things TO DO for first release
1. Add manual registration and resolving
2. Add certain registration by String Key or specific arguments
3. Perfomance test
4. 70% Code coverage
5. Documentation

## Rules
1. Properties will be automatically injected only on your class, not your parent classes.
![alt text](footer.png)
529 changes: 529 additions & 0 deletions SwiftInjector.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>SwiftInjector.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>
20 changes: 20 additions & 0 deletions SwiftInjector/Container.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Container.swift
// SwiftInjector
//
// Created by Ghost on 30.06.2019.
// Copyright © 2019 Ghost. All rights reserved.
//

import Foundation

protocol Container: class {
var parentContainer: Containerable { get set }
func register()
}

extension Container {
func resolve<T: Containerable.Object>() -> T? {
return parentContainer.resolve()
}
}
186 changes: 186 additions & 0 deletions SwiftInjector/Containerable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//
// Containerable.swift
// SwiftInjector
//
// Created by Ghost on 30.06.2019.
// Copyright © 2019 Ghost. All rights reserved.
//

import Foundation

protocol Containerable: class {
typealias Object = AnyObject
typealias ServiceName = String
typealias Service = (() -> Object)
typealias RelationIn = String
typealias RelationOut = String

var dispatchRegistrationGroup: DispatchGroup { get }

var services: [ServiceName: [ContainerObject]] { get set }
var relations: [RelationIn: [RelationOut]] { get set }
var recursiveNotResolvedObjects: [Object] { get set }
}

struct ContainerObject {
enum RegistrationType {
case manual
case automatic
}
var name: String? = nil
var registrationType: RegistrationType
var registration: Containerable.Service
var object: Containerable.Object? = nil
init(_ registration: @escaping Containerable.Service,
name: String? = nil,
registrationType: RegistrationType) {
self.registration = registration
self.name = name
self.registrationType = registrationType
}
}


//MARK: TODO: MOVE IT TO DIContainer.swift
extension Containerable {
private func resolveAny(typeString: String, name: String? = nil) -> Object? {
let array: [ContainerObject]? = {
if let filterName = name {
return services[typeString]?.filter { $0.name == filterName }
} else {
return services[typeString]
}
}()

if (array?.count ?? 0) > 1 {
SILogger("Warning in \(typeString) resolving. You registered two different objects for this type. Try to provide \"name\" argument while registering your object. But you will receive first object of all services you registered.").log(priority: .high)
}

if let object = array?.first?.object {
print(1)
return object
} else if let object = array?.first?.registration() {

var objects = services[typeString]
if objects?.count ?? 0 > 0 {
objects?[0].object = object
}

services[typeString] = objects ?? []
if array?.first?.registrationType == .automatic {
autoresolve(on: object)
finishRegistrations()
}

return object
} else {
return nil
}
}

func resolve<T: Object>(name: String? = nil) -> T? {
let key = String(describing: T.self)
let object = resolveAny(typeString: key, name: name) as? T

return object
}

func finishRegistrations() {
relations = [:]
for object in recursiveNotResolvedObjects {
autoresolve(on: object)
}
}

private func autoresolve<T: Object>(on object: T) {
let mirror = Mirror(reflecting: object)

for attr in mirror.children {
if let property_name = attr.label {
let objectType = type(of: attr.value)
let typeString = String(describing: objectType).replacingOccurrences(of: "Optional<", with: "").replacingOccurrences(of: ">", with: "")

// MARK: MUTATE VARIABLE (DANGER OBJC RUNTIME ZONE)
if let ivar = class_getInstanceVariable(type(of: object), property_name) {
let typeStringOfMyObject = formattedString(of: object)
recordRelations(relationIn: typeString, relationOut: typeStringOfMyObject)
if relations[typeStringOfMyObject]?.contains(typeString) ?? false {
relations[typeString]?.removeAll(where: { (string) -> Bool in
return string == typeStringOfMyObject
})
recursiveNotResolvedObjects.append(object)
return
}
if let value = resolveAny(typeString: typeString) {
object_setIvar(object, ivar, value)
}
}
}
}
}

private func memmoryAddress(_ object: Object) -> String {
return "\(Unmanaged.passUnretained(object).toOpaque())"
}


// MARK: *TODO: Unable to test*
private func recordRelations(relationIn: RelationIn, relationOut: RelationOut) {
if let relationsOut = relations[relationIn], !relationsOut.contains(relationOut) {
var newRelationsOut = relationsOut
newRelationsOut.append(relationOut)
relations[relationIn] = newRelationsOut
} else {
relations[relationIn] = [relationOut]
}
}

// MARK: *TESTED*
func formattedString<T: Object>(of object: T) -> String {
return String(describing: type(of: object)).replacingOccurrences(of: "Optional<", with: "").replacingOccurrences(of: ">", with: "")
}

// MARK: *TESTED*
func property<T>(object: Object, propertyName: String) -> T? {
let mirror = Mirror(reflecting: object)
for attr in mirror.children {
if attr.label == propertyName {
return attr.value as? T
}
}

return nil
}

// MARK: *TESTED*
// Returns only first variable of type
func propertyName(by typeString: String, in object: Object) -> String? {
let mirror = Mirror(reflecting: object)
for attr in mirror.children {
let propertyType = type(of: attr.value)
let typeStringInside = String(describing: propertyType).replacingOccurrences(of: "Optional<", with: "").replacingOccurrences(of: ">", with: "")
if typeStringInside == typeString {
return attr.label
}
}

return nil
}

// MARK: *TESTED*
func register<T: Object>(_ registration: @escaping (() -> T),
name: String? = nil,
registrationType: ContainerObject.RegistrationType = .automatic) {
dispatchRegistrationGroup.enter()
let object = registration()
let key = String(describing: type(of: object))
if let array = services[key] {
var newArray = array
newArray.append(ContainerObject(registration, name: name, registrationType: registrationType))
services[key] = newArray
} else {
services[key] = [ContainerObject(registration, name: name, registrationType: registrationType)]
}
dispatchRegistrationGroup.leave()
}
}
32 changes: 32 additions & 0 deletions SwiftInjector/DIContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// DIContainer.swift
// SwiftInjector
//
// Created by Ghost on 30.06.2019.
// Copyright © 2019 Ghost. All rights reserved.
//

import Foundation

class DIContainer: Container {
var parentContainer: Containerable

init(parentContainer: Containerable? = nil) {
self.parentContainer = parentContainer ?? RootContainer()
self.register()
}

final func register<T: Containerable.Object>(registrationType: ContainerObject.RegistrationType = .automatic,
name: String? = nil,
_ registration: @escaping (() -> T)) {
parentContainer.register(registration, name: name, registrationType: registrationType)
}

final func resolve<T: Containerable.Object>(name: String? = nil) -> T? {
return parentContainer.resolve(name: name)
}

func register() {
// Mark: leave empty only in this class
}
}
22 changes: 22 additions & 0 deletions SwiftInjector/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>
29 changes: 29 additions & 0 deletions SwiftInjector/RootContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Container.swift
// SwiftInjector
//
// Created by Ghost on 30.06.2019.
// Copyright © 2019 Ghost. All rights reserved.
//

import Foundation

class RootContainer: Containerable {
var recursiveNotResolvedObjects: [Object] = []

var relations: [RelationIn: [RelationOut]] = [:]
var services: [ServiceName: [ContainerObject]] = [:]

var dispatchRegistrationGroup: DispatchGroup = DispatchGroup()


init() {
let date = Date()
dispatchRegistrationGroup.notify(queue: .main) {

self.finishRegistrations()
let seconds = Date().timeIntervalSince(date)
print("All properties have been successfully injected for \(seconds) seconds.")
}
}
}
Loading

0 comments on commit efeede4

Please sign in to comment.