Skip to content

Instantly share code, notes, and snippets.

@pteasima
Last active May 31, 2018 18:07
Show Gist options
  • Save pteasima/8f2fbea125a15b2979b4198609737530 to your computer and use it in GitHub Desktop.
Save pteasima/8f2fbea125a15b2979b4198609737530 to your computer and use it in GitHub Desktop.
import ReactiveSwift
import enum Result.Result
import Alamofire
typealias Never = NoError
//This file alone wont build, its just for reading ;)
struct Tagged<Tag, RawValue> {
var rawValue: RawValue
}
struct AppContext {
var http: SessionManager
var userManager: UserManager
static let shared: AppContext = .init(
http: .default,
userManager: .init()
)
}
protocol Effect {
associatedtype Context
}
protocol NetworkEffect: Effect {
static func request(path: String) -> Tagged<Result<Data, NetworkError>, Self>
}
protocol APIEffect: NetworkEffect {
static func login(username: String, password: String) -> Tagged<Result<User, RequestError>, Self>
}
extension Tagged where RawValue: ReactiveEffect {
init(_ construct: @escaping (RawValue.Context) -> SignalProducer<Tag, Never>) {
rawValue = RawValue.wrappedInit { context in
construct(context).map { $0 as Any }
}
}
}
extension Tagged where RawValue: ReactiveEffect {
var producer: (_ in: RawValue.Context) -> SignalProducer<Tag, Never> {
return { context in self.rawValue.producer(context)
.filterMap {
guard let result = ($0 as? Tag) else { assertionFailure(); return nil }
return result
}
}
}
}
protocol ReactiveEffect: Effect {
var producer: (_ in: Context) -> SignalProducer<Any, Never> { get }
init(producer: @escaping (Context) -> SignalProducer<Any, Never>)
}
extension ReactiveEffect {
static func wrappedInit(producer: @escaping (Context) -> SignalProducer<Any, Never>) -> Self {
return self.init(producer: producer)
}
}
extension ReactiveEffect where Self: NetworkEffect, Context == AppContext {
static func request(path: String) -> Tagged<Result<Data, NetworkError>, Self> {
//its still possible to return a producer of the wrong type, but youd have to be a moron and not use this typesafe convenience init
// Preventing this would require replacing Tagged with a ReactiveSwift specific type, which would prevent the creation of other interpreters
return .init { context in
//context.http...
return .empty
}
}
}
extension NetworkEffect where Self: ReactiveEffect {
static func request<Value: Codable>(path: String) -> Tagged<Result<Value, RequestError>, Self> {
return Tagged.init { context in
self.request(path: path).producer(context)
.map { result in
switch result {
case let .success(data):
return Result { //TODO this will crash since it doesnt throw RequestError
try JSONDecoder().decode(Value.self, from: data)
}
case let .failure(e):
return .failure(RequestError.network(e))
}
}
}
}
}
extension ReactiveEffect where Self: APIEffect, Context == AppContext {
static func login(username: String, password: String) -> Tagged<Result<User, RequestError>, Self> {
return Tagged.init { context in
self.request(path: "login"/*, ...*/).producer(context)
}
}
}
struct NormalEffect<Context>: ReactiveEffect {
let producer: (_ in: Context) -> SignalProducer<Any, Never>
}
extension NormalEffect: NetworkEffect, APIEffect where Context == AppContext {}
struct MockEffect<Context>: ReactiveEffect {
let producer: (_ in: Context) -> SignalProducer<Any, Never>
}
extension MockEffect: NetworkEffect, APIEffect where Context == AppContext {
static func request(path: String) -> Tagged<Result<Data, NetworkError>, MockEffect> {
return Tagged.init { context in
SignalProducer(value: Result(value: Data()))
}
}
}
//use it
protocol LoginViewModeling: class {
}
final class LoginViewModel<E: NetworkEffect & APIEffect>
: LoginViewModeling where E: ReactiveEffect {
init(context: E.Context) {
E.request(path: "").producer(context).startWithValues {
print($0)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment