Observable implementation











up vote
1
down vote

favorite
1












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)
}
}
}









share|improve this question
























  • 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















up vote
1
down vote

favorite
1












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)
}
}
}









share|improve this question
























  • 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













up vote
1
down vote

favorite
1









up vote
1
down vote

favorite
1






1





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)
}
}
}









share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Jan 26 at 12:02

























asked Jan 24 at 12:07









iOSGeek

1757




1757












  • 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


















  • 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
















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










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.






share|improve this answer




























    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.






    share|improve this answer





















      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
      });


      }
      });














      draft saved

      draft discarded


















      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.






      share|improve this answer

























        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.






        share|improve this answer























          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.






          share|improve this answer












          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.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Jan 25 at 21:09









          Martin R

          15.5k12264




          15.5k12264
























              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.






              share|improve this answer

























                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.






                share|improve this answer























                  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.






                  share|improve this answer












                  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.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered 11 mins ago









                  Daniel T.

                  521313




                  521313






























                      draft saved

                      draft discarded




















































                      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.




                      draft saved


                      draft discarded














                      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





















































                      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







                      Popular posts from this blog

                      Quarter-circle Tiles

                      build a pushdown automaton that recognizes the reverse language of a given pushdown automaton?

                      Mont Emei