简体   繁体   中英

ReactiveCocoa: creating a signal that applies a map over an observer

From what I understand, the RACSubject equivalent of ReactiveCocoa 4 is the Observer class. I want to have a signal and an observer linked together so that the signal sends events applying a map operation to the events sent to the observer. In Obj-C it looks like this:

// ViewModel.h

@interface ViewModel

@property (readonly) RACSubject *someAction; //expects e.g. int values
@property (readonly) RACSignal *someSignal; //sends e.g. string values

@end

// ViewModel.m

//redeclaring the signal and action as readwrite

@implementation

- (id)init {
  _someAction = [RACSubject subject];
  _someSignal = [_someAction map:^id(NSNumber *index) {
     return "Some string based on index passed in";
  }];
}

@end

Now when someone pushes a value onto someAction , the someSignal will fire an event containing a derived value. How do I achieve the same effect in Swift?

What I've been able to do so far is something like this:

public class ViewModel: NSObject {
    public let (internalSignal, someAction) = Signal<Int, NoError>.pipe()
    public var someSignal: Signal<String, NoError> {
        get {
            return self.internalSignal.map({ [unowned self](index: Int) -> String in
                return "Some string value based on \(self.someArray[index])"
            })
        }
    }
    public let someArray = [1, 2, 3, 4, 5]
}

Which looks like a bad solution because

  1. internalSignal should be private but needs to be declared public in order to match it to Signal's pipe
  2. someSignal is computed every time it's needed therefore, even though the same signal could be reused over and over. Also can't be declared as a let constant.

You could initialize the members in init just like ObjC...

public class ViewModel: NSObject {
    private let internalSignal: Signal<Int, NoError>
    public let someAction: Observer<Int, NoError>
    public let someSignal: Signal<String, NoError>

    override init() {
        (internalSignal, someAction) = Signal<Int, NoError>.pipe()
        someSignal = internalSignal.map { index in
            "Some string value based on \(index)"
        }
        super.init()
    }
}

For someSignal you could also use lazy initialization , which allows the member to refer to self :

public class ViewModel: NSObject {
    private let internalSignal: Signal<Int, NoError>
    public let someAction: Observer<Int, NoError>
    public private(set) lazy var someSignal: Signal<String, NoError> =
        self.internalSignal.map { [unowned self] index in
            "Some string value based on \(self.someArray[index])"
        }

    override init() {
        (internalSignal, someAction) = Signal<Int, NoError>.pipe()
        super.init()
    }
}

Unlike the first piece of code, the lazy-var is initialized only before someSignal is used, not at the ViewModel's initialization.

Also, since it is a var , Swift allows you use mutate its value (there is no such thing as lazy let ). We can restrict the permission using private(set) , but this won't prevent you accidentally write self.someSignal = ... somewhere.


Alternatively, you could make someSignal an implicitly unwrapped optional and initialize manually:

public class ViewModel: NSObject {
    private let internalSignal: Signal<Int, NoError>
    public let someAction: Observer<Int, NoError>
    public private(set) var someSignal: Signal<String, NoError>!

    override init() {
        (internalSignal, someAction) = Signal<Int, NoError>.pipe()
        super.init()
        someSignal = internalSignal.map { [unowned self] index in
            "Some string value based on \(self.someArray[index])"
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM