Observable implementation
up vote
1
down vote
favorite
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)
}
}
}
swift ios observer-pattern reactive-programming
add a comment |
up vote
1
down vote
favorite
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)
}
}
}
swift ios observer-pattern reactive-programming
IfResult
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.
– Martin R
Jan 24 at 12:35
@MartinR done, hope that it's easier to be reviewed now
– iOSGeek
Jan 24 at 12:38
That does not compile, there are several errors (related toResult<Value>
having too few parameters). – Also a (minimal) main program demonstrating the usage might be helpful.
– Martin R
Jan 24 at 12:42
@MartinR now it compiles and I have added usage information
– iOSGeek
Jan 24 at 13:13
add a comment |
up vote
1
down vote
favorite
up vote
1
down vote
favorite
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)
}
}
}
swift ios observer-pattern reactive-programming
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)
}
}
}
swift ios observer-pattern reactive-programming
swift ios observer-pattern reactive-programming
edited Jan 26 at 12:02
asked Jan 24 at 12:07
iOSGeek
1757
1757
IfResult
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.
– Martin R
Jan 24 at 12:35
@MartinR done, hope that it's easier to be reviewed now
– iOSGeek
Jan 24 at 12:38
That does not compile, there are several errors (related toResult<Value>
having too few parameters). – Also a (minimal) main program demonstrating the usage might be helpful.
– Martin R
Jan 24 at 12:42
@MartinR now it compiles and I have added usage information
– iOSGeek
Jan 24 at 13:13
add a comment |
IfResult
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.
– Martin R
Jan 24 at 12:35
@MartinR done, hope that it's easier to be reviewed now
– iOSGeek
Jan 24 at 12:38
That does not compile, there are several errors (related toResult<Value>
having too few parameters). – Also a (minimal) main program demonstrating the usage might be helpful.
– Martin R
Jan 24 at 12:42
@MartinR now it compiles and I have added usage information
– iOSGeek
Jan 24 at 13:13
If
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.– Martin R
Jan 24 at 12:35
If
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.– Martin R
Jan 24 at 12:35
@MartinR done, hope that it's easier to be reviewed now
– iOSGeek
Jan 24 at 12:38
@MartinR done, hope that it's easier to be reviewed now
– iOSGeek
Jan 24 at 12:38
That does not compile, there are several errors (related to
Result<Value>
having too few parameters). – Also a (minimal) main program demonstrating the usage might be helpful.– Martin R
Jan 24 at 12:42
That does not compile, there are several errors (related to
Result<Value>
having too few parameters). – Also a (minimal) main program demonstrating the usage might be helpful.– Martin R
Jan 24 at 12:42
@MartinR now it compiles and I have added usage information
– iOSGeek
Jan 24 at 13:13
@MartinR now it compiles and I have added usage information
– iOSGeek
Jan 24 at 13:13
add a comment |
2 Answers
2
active
oldest
votes
up vote
2
down vote
accepted
There is quite a lot of "almost similar" code due to the fact that you
treat "value observers" and "result observers" separately:
Two classes
class ResultObserver<Value>
class ValueObserver<Value>
with corresponding type aliases:
typealias ResultObserverBlock = (Result<Value>) -> Void
typealias ValueObserverBlock = (Value) -> Void
and instance variables
private var valueObservers = [ValueObserver<Value>]()
private var resultObservers = [ResultObserver<Value>]()
three init methods, two observeResults()
methods, etc.
This would be greatly simplified by just observing a generic
type Value
, which in particular can be a Result<>
:
class Observer<Value> {
typealias ObserverBlock = (Value) -> Void
weak var observer: AnyObject?
let block: ObserverBlock
init(observer: AnyObject, block: @escaping ObserverBlock) {
self.observer = observer
self.block = block
}
}
public class Observable<Value> {
//MARK: - Private properties
private var observers = [Observer<Value>]()
//MARK: - Public properties
public var value : Value {
didSet {
self.notifyObservers()
}
}
//MARK: - Struct lifecycle
public init(_ value: Value) {
self.value = value
}
//MARK: - Observation
func observe(on observer: AnyObject, observerBlock: @escaping Observer<Value>.ObserverBlock) {
self.observers.append(Observer(observer: observer, block: observerBlock))
observerBlock(value)
}
func remove(observer: AnyObject) {
self.observers = self.observers.filter({ $0.observer !== observer })
}
//MARK: - Helpers
private func notifyObservers() {
for observer in self.observers {
observer.block(value)
}
}
}
Note also the use of Observer<Value>.ObserverBlock
in order to
avoid defining the same closure type twice.
Now you can observe a simple (boolean) value:
private let isLoadingObservable = Observable(false)
// ...
isLoadingObservable.observe(on: self) { isLoading in
print("observed:", isLoading)
}
or a result:
private let dataObservable = Observable(Result<[User]>.success())
// ...
dataObservable.observe(on: self) { result in
switch result {
case .success(let value):
print("Value:", value)
case .failure(let error):
print("Error:", error)
}
}
Another possible improvement could be to check in func notifyObservers()
if the observing object still is alive, and
remove it from the list otherwise.
add a comment |
up vote
0
down vote
One of the big draws of an Observable class are the operators like map
, flatMap
, and scan
. At minimum you should implement those three functions.
That said, chaining your Observables together with map, & al. would be a bit problematic since they all would maintain a copy of the last element emitted (in the value
parameter.)
I would rather see the Observable class do away with that parameter so I can chain several together without wasting a bunch of memory.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f185872%2fobservable-implementation%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
accepted
There is quite a lot of "almost similar" code due to the fact that you
treat "value observers" and "result observers" separately:
Two classes
class ResultObserver<Value>
class ValueObserver<Value>
with corresponding type aliases:
typealias ResultObserverBlock = (Result<Value>) -> Void
typealias ValueObserverBlock = (Value) -> Void
and instance variables
private var valueObservers = [ValueObserver<Value>]()
private var resultObservers = [ResultObserver<Value>]()
three init methods, two observeResults()
methods, etc.
This would be greatly simplified by just observing a generic
type Value
, which in particular can be a Result<>
:
class Observer<Value> {
typealias ObserverBlock = (Value) -> Void
weak var observer: AnyObject?
let block: ObserverBlock
init(observer: AnyObject, block: @escaping ObserverBlock) {
self.observer = observer
self.block = block
}
}
public class Observable<Value> {
//MARK: - Private properties
private var observers = [Observer<Value>]()
//MARK: - Public properties
public var value : Value {
didSet {
self.notifyObservers()
}
}
//MARK: - Struct lifecycle
public init(_ value: Value) {
self.value = value
}
//MARK: - Observation
func observe(on observer: AnyObject, observerBlock: @escaping Observer<Value>.ObserverBlock) {
self.observers.append(Observer(observer: observer, block: observerBlock))
observerBlock(value)
}
func remove(observer: AnyObject) {
self.observers = self.observers.filter({ $0.observer !== observer })
}
//MARK: - Helpers
private func notifyObservers() {
for observer in self.observers {
observer.block(value)
}
}
}
Note also the use of Observer<Value>.ObserverBlock
in order to
avoid defining the same closure type twice.
Now you can observe a simple (boolean) value:
private let isLoadingObservable = Observable(false)
// ...
isLoadingObservable.observe(on: self) { isLoading in
print("observed:", isLoading)
}
or a result:
private let dataObservable = Observable(Result<[User]>.success())
// ...
dataObservable.observe(on: self) { result in
switch result {
case .success(let value):
print("Value:", value)
case .failure(let error):
print("Error:", error)
}
}
Another possible improvement could be to check in func notifyObservers()
if the observing object still is alive, and
remove it from the list otherwise.
add a comment |
up vote
2
down vote
accepted
There is quite a lot of "almost similar" code due to the fact that you
treat "value observers" and "result observers" separately:
Two classes
class ResultObserver<Value>
class ValueObserver<Value>
with corresponding type aliases:
typealias ResultObserverBlock = (Result<Value>) -> Void
typealias ValueObserverBlock = (Value) -> Void
and instance variables
private var valueObservers = [ValueObserver<Value>]()
private var resultObservers = [ResultObserver<Value>]()
three init methods, two observeResults()
methods, etc.
This would be greatly simplified by just observing a generic
type Value
, which in particular can be a Result<>
:
class Observer<Value> {
typealias ObserverBlock = (Value) -> Void
weak var observer: AnyObject?
let block: ObserverBlock
init(observer: AnyObject, block: @escaping ObserverBlock) {
self.observer = observer
self.block = block
}
}
public class Observable<Value> {
//MARK: - Private properties
private var observers = [Observer<Value>]()
//MARK: - Public properties
public var value : Value {
didSet {
self.notifyObservers()
}
}
//MARK: - Struct lifecycle
public init(_ value: Value) {
self.value = value
}
//MARK: - Observation
func observe(on observer: AnyObject, observerBlock: @escaping Observer<Value>.ObserverBlock) {
self.observers.append(Observer(observer: observer, block: observerBlock))
observerBlock(value)
}
func remove(observer: AnyObject) {
self.observers = self.observers.filter({ $0.observer !== observer })
}
//MARK: - Helpers
private func notifyObservers() {
for observer in self.observers {
observer.block(value)
}
}
}
Note also the use of Observer<Value>.ObserverBlock
in order to
avoid defining the same closure type twice.
Now you can observe a simple (boolean) value:
private let isLoadingObservable = Observable(false)
// ...
isLoadingObservable.observe(on: self) { isLoading in
print("observed:", isLoading)
}
or a result:
private let dataObservable = Observable(Result<[User]>.success())
// ...
dataObservable.observe(on: self) { result in
switch result {
case .success(let value):
print("Value:", value)
case .failure(let error):
print("Error:", error)
}
}
Another possible improvement could be to check in func notifyObservers()
if the observing object still is alive, and
remove it from the list otherwise.
add a comment |
up vote
2
down vote
accepted
up vote
2
down vote
accepted
There is quite a lot of "almost similar" code due to the fact that you
treat "value observers" and "result observers" separately:
Two classes
class ResultObserver<Value>
class ValueObserver<Value>
with corresponding type aliases:
typealias ResultObserverBlock = (Result<Value>) -> Void
typealias ValueObserverBlock = (Value) -> Void
and instance variables
private var valueObservers = [ValueObserver<Value>]()
private var resultObservers = [ResultObserver<Value>]()
three init methods, two observeResults()
methods, etc.
This would be greatly simplified by just observing a generic
type Value
, which in particular can be a Result<>
:
class Observer<Value> {
typealias ObserverBlock = (Value) -> Void
weak var observer: AnyObject?
let block: ObserverBlock
init(observer: AnyObject, block: @escaping ObserverBlock) {
self.observer = observer
self.block = block
}
}
public class Observable<Value> {
//MARK: - Private properties
private var observers = [Observer<Value>]()
//MARK: - Public properties
public var value : Value {
didSet {
self.notifyObservers()
}
}
//MARK: - Struct lifecycle
public init(_ value: Value) {
self.value = value
}
//MARK: - Observation
func observe(on observer: AnyObject, observerBlock: @escaping Observer<Value>.ObserverBlock) {
self.observers.append(Observer(observer: observer, block: observerBlock))
observerBlock(value)
}
func remove(observer: AnyObject) {
self.observers = self.observers.filter({ $0.observer !== observer })
}
//MARK: - Helpers
private func notifyObservers() {
for observer in self.observers {
observer.block(value)
}
}
}
Note also the use of Observer<Value>.ObserverBlock
in order to
avoid defining the same closure type twice.
Now you can observe a simple (boolean) value:
private let isLoadingObservable = Observable(false)
// ...
isLoadingObservable.observe(on: self) { isLoading in
print("observed:", isLoading)
}
or a result:
private let dataObservable = Observable(Result<[User]>.success())
// ...
dataObservable.observe(on: self) { result in
switch result {
case .success(let value):
print("Value:", value)
case .failure(let error):
print("Error:", error)
}
}
Another possible improvement could be to check in func notifyObservers()
if the observing object still is alive, and
remove it from the list otherwise.
There is quite a lot of "almost similar" code due to the fact that you
treat "value observers" and "result observers" separately:
Two classes
class ResultObserver<Value>
class ValueObserver<Value>
with corresponding type aliases:
typealias ResultObserverBlock = (Result<Value>) -> Void
typealias ValueObserverBlock = (Value) -> Void
and instance variables
private var valueObservers = [ValueObserver<Value>]()
private var resultObservers = [ResultObserver<Value>]()
three init methods, two observeResults()
methods, etc.
This would be greatly simplified by just observing a generic
type Value
, which in particular can be a Result<>
:
class Observer<Value> {
typealias ObserverBlock = (Value) -> Void
weak var observer: AnyObject?
let block: ObserverBlock
init(observer: AnyObject, block: @escaping ObserverBlock) {
self.observer = observer
self.block = block
}
}
public class Observable<Value> {
//MARK: - Private properties
private var observers = [Observer<Value>]()
//MARK: - Public properties
public var value : Value {
didSet {
self.notifyObservers()
}
}
//MARK: - Struct lifecycle
public init(_ value: Value) {
self.value = value
}
//MARK: - Observation
func observe(on observer: AnyObject, observerBlock: @escaping Observer<Value>.ObserverBlock) {
self.observers.append(Observer(observer: observer, block: observerBlock))
observerBlock(value)
}
func remove(observer: AnyObject) {
self.observers = self.observers.filter({ $0.observer !== observer })
}
//MARK: - Helpers
private func notifyObservers() {
for observer in self.observers {
observer.block(value)
}
}
}
Note also the use of Observer<Value>.ObserverBlock
in order to
avoid defining the same closure type twice.
Now you can observe a simple (boolean) value:
private let isLoadingObservable = Observable(false)
// ...
isLoadingObservable.observe(on: self) { isLoading in
print("observed:", isLoading)
}
or a result:
private let dataObservable = Observable(Result<[User]>.success())
// ...
dataObservable.observe(on: self) { result in
switch result {
case .success(let value):
print("Value:", value)
case .failure(let error):
print("Error:", error)
}
}
Another possible improvement could be to check in func notifyObservers()
if the observing object still is alive, and
remove it from the list otherwise.
answered Jan 25 at 21:09
Martin R
15.5k12264
15.5k12264
add a comment |
add a comment |
up vote
0
down vote
One of the big draws of an Observable class are the operators like map
, flatMap
, and scan
. At minimum you should implement those three functions.
That said, chaining your Observables together with map, & al. would be a bit problematic since they all would maintain a copy of the last element emitted (in the value
parameter.)
I would rather see the Observable class do away with that parameter so I can chain several together without wasting a bunch of memory.
add a comment |
up vote
0
down vote
One of the big draws of an Observable class are the operators like map
, flatMap
, and scan
. At minimum you should implement those three functions.
That said, chaining your Observables together with map, & al. would be a bit problematic since they all would maintain a copy of the last element emitted (in the value
parameter.)
I would rather see the Observable class do away with that parameter so I can chain several together without wasting a bunch of memory.
add a comment |
up vote
0
down vote
up vote
0
down vote
One of the big draws of an Observable class are the operators like map
, flatMap
, and scan
. At minimum you should implement those three functions.
That said, chaining your Observables together with map, & al. would be a bit problematic since they all would maintain a copy of the last element emitted (in the value
parameter.)
I would rather see the Observable class do away with that parameter so I can chain several together without wasting a bunch of memory.
One of the big draws of an Observable class are the operators like map
, flatMap
, and scan
. At minimum you should implement those three functions.
That said, chaining your Observables together with map, & al. would be a bit problematic since they all would maintain a copy of the last element emitted (in the value
parameter.)
I would rather see the Observable class do away with that parameter so I can chain several together without wasting a bunch of memory.
answered 16 mins ago
Daniel T.
521313
521313
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f185872%2fobservable-implementation%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
If
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.– Martin R
Jan 24 at 12:35
@MartinR done, hope that it's easier to be reviewed now
– iOSGeek
Jan 24 at 12:38
That does not compile, there are several errors (related to
Result<Value>
having too few parameters). – Also a (minimal) main program demonstrating the usage might be helpful.– Martin R
Jan 24 at 12:42
@MartinR now it compiles and I have added usage information
– iOSGeek
Jan 24 at 13:13