I have implemented a simple Observable class. I think that there is still room for improvement. Especially the fact that I have separate array to observe results
and values
what if I want to observe errors for example, I think that this solution can be improved to be scalable. Also I'm not sure who is responsible for the threading the class implementation or the caller.
the desired result is to have an interface that allows:
1) Observable declaration:
private let isLoadingObservable = Observable<Bool>(false)
2) Update value:
self.isLoadingObservable.value = true
3) Observe values changes:
override func viewDidLoad() {
super.viewDidLoad()
isLoadingObservable.observeValues(on: self) { isLoading in
print(isLoading)
}
}
Also in case where failure is possible (network call for example) we can user Result
:
1) Observable declaration:
private let dataObservable = Observable<[User]>([])
2) Update value:
dataObservable.result = Result.failure(URLError.badURL)
3) Observe result changes:
dataObservable.observeResults(on: self) { result in
switch result {
case .success(let value):
print(value)
case .failure(let error):
print(error)
}
}
where we have
struct User {
let name: String
}
enum ServerError: Error {
case invalidDataError
}
The implementation:
import Foundation
public enum Result<Value> {
case success(Value)
case failure(Error)
var value: Value? {
switch self {
case .success(let value):
return value
case .failure:
return nil
}
}
}
class ResultObserver<Value> {
typealias ResultObserverBlock = (Result<Value>) -> Void
weak var observer: AnyObject?
let block: ResultObserverBlock
init(observer: AnyObject, block: @escaping ResultObserverBlock) {
self.observer = observer
self.block = block
}
}
class ValueObserver<Value> {
typealias ValueObserverBlock = (Value) -> Void
weak var observer: AnyObject?
let block: ValueObserverBlock
init(observer: AnyObject, block: @escaping ValueObserverBlock) {
self.observer = observer
self.block = block
}
}
public class Observable<Value> {
typealias ResultObserverBlock = (Result<Value>) -> Void
typealias ValueObserverBlock = (Value) -> Void
//MARK: - Private properties
private var valueObservers = [ValueObserver<Value>]()
private var resultObservers = [ResultObserver<Value>]()
//MARK: - Public properties
public var result : Result<Value> {
didSet {
self.notifyObservers()
}
}
public var value: Value? {
get{
return self.result.value
}
set {
if let value = newValue {
self.result = Result.success(value)
}
}
}
//MARK: - Struct lifecycle
public init(_ result: Result<Value>) {
self.result = result
}
public convenience init(_ value: Value) {
self.init(Result.success(value))
}
public convenience init(_ error: Error) {
self.init(Result.failure(error))
}
//MARK: - Observation
func observeResults(on observer: AnyObject, observerBlock: @escaping ResultObserverBlock) {
self.resultObservers.append(ResultObserver(observer: observer, block: observerBlock))
observerBlock(result)
}
func observeValues(on observer: AnyObject, observerBlock: @escaping ValueObserverBlock) {
self.valueObservers.append(ValueObserver(observer: observer, block: observerBlock))
if let value = value {
observerBlock(value)
}
}
func remove(observer: AnyObject) {
self.resultObservers = self.resultObservers.filter({$0.observer !== observer})
self.valueObservers = self.valueObservers.filter({$0.observer !== observer})
}
//MARK: - Helpers
private func notifyObservers() {
for observer in self.valueObservers {
if let value = value {
observer.block(value)
}
}
for observer in self.resultObservers {
observer.block(result)
}
}
}
Result
is the only thing that you need from Alamofire then I would suggest to include the definition here directly, to make your code independent of an (otherwise unrelated) framework. \$\endgroup\$Result<Value>
having too few parameters). – Also a (minimal) main program demonstrating the usage might be helpful. \$\endgroup\$