Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP]: Reliant/Swift: Context substitution #18

Open
wants to merge 3 commits into
base: feature/reliant-in-swift
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public protocol ReliantContext {
static func createContext() -> ContextType
}


public protocol ReliantContextHolder {
typealias ContextType : ReliantContext
var context:ContextType { get }
Expand Down
26 changes: 20 additions & 6 deletions SwiftDraft/ReliantFramework/ReliantFramework/RelyOn.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,31 @@

import Foundation

class ContextCache {
static let sharedInstance:ContextCache = ContextCache()
var cache:Dictionary<String, Any> = Dictionary()
struct ContextCache {
// If we're only going to use a static cache, we might as well use a struct?
static var standard = [String:Any]()

static var substitutions = [String:Any.Type]()
}

public func relyOn<T:ReliantContext>(type:T.Type) -> T.ContextType {
if let result = ContextCache.sharedInstance.cache[String(type)] {
let typeKey = String(type)
if let result = ContextCache.standard[typeKey] {
return result as! T.ContextType
} else {
let result = type.createContext()
ContextCache.sharedInstance.cache[String(type)] = result
var result:T.ContextType

if let substitutionType = ContextCache.substitutions[typeKey] as? T.Type {
result = substitutionType.createContext()
} else {
result = type.createContext()
}

ContextCache.standard[String(type)] = result
return result;
}
}

public func relyOnSubstitute<T:ReliantContext>(type:T.Type)(_ otherType:T.Type) {
ContextCache.substitutions[String(type)] = otherType
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,25 @@ struct ContextNeedingContext : ReliantContext {
return ContextNeedingContext()
}
}
class SubWaver : Waver {
func wave(reason: String) -> String {
return "Substitute waving"
}
}




class SubstituteContext : SimpleReferenceContext {
override init() {
super.init()
waver = SubWaver()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted in the summary, having to subclass feels icky and rules out the use of struct-based contexts. It also requires class members to be mutable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we work through protocols and delegation instead?
Eg: protocol Waver, structs RealWaver and SubWaver, SubWaver initialises RealWaver and delegates to it when needed, overriding it's behaviour when wanted? It would mean people need some knowledge on how to architect this in, but it's better than having the above mentioned restrictions...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the current method is pretty much a no-go. I think there are still some alternatives to explore though.

}
}

class ReliantFrameworkTests: XCTestCase {

override func setUp() {
super.setUp()
ReliantFrameworkTestsHelper.sharedInsance.reset()
ContextCache.sharedInstance.cache = Dictionary()
ContextCache.standard.removeAll()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See, this I would like to avoid, but I don't know how, since we are tight to the global relyOn function...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll always have global state, I think, even if we opt to bind the the relyOn function to some other object, this object will either be a framework object like an app delegate, or something else that sticks around longer than necessary.

}

func testRelyOnReturnsSameInsanceEveryTimeForReferenceTypes() {
Expand All @@ -84,6 +92,17 @@ class ReliantFrameworkTests: XCTestCase {
XCTAssertEqual(needed.needy.decorateGreeting(), "Oh! Hello Needy")
}


func testSubstitutions() {
relyOnSubstitute(SimpleReferenceContext)(SubstituteContext)
XCTAssertTrue(ContextCache.substitutions.contains({ (key, value) -> Bool in
return key == String(SimpleReferenceContext) && value == SubstituteContext.self
}))

let context = relyOn(SimpleReferenceContext)

// Failing test.
// The createContext() function is static and thus final. Actual context type seems
// to be correct, but createContext() is called on original context class
XCTAssertTrue(context is SubstituteContext)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import Reliant
class SimpleReferenceContext : ReliantContext {
private let bothWorlds = BothWorlds(prefix:"Hello")

let waver:Waver
let greeter:Greeter
var waver:Waver
var greeter:Greeter

init() {
ReliantFrameworkTestsHelper.sharedInsance.markInitCalled()
Expand Down