I play around ReactiveCocoa 3 and Swift, and I decided to make a simple toy application to check how I can create an application with ReactiveCocoa 3 that implements the MVVM pattern.
The core staff works very well, but I'm not sure that it is best to handle signals created from the notification center.
Let's say that someone triggers a notification somewhere in the application. The notification is called TimerNotification and has an Int time object inside the user information dictionary, accessible by the TimerCount key. Now let me say that I have a controller that wants to print a message every time TimerNotification triggered.
In the old ObjC / RAC, 2 days I would do something like this
- (void)viewDidLoad { NSNotificationCenter * notificationCenter = [NSNotificationCenter defaultCenter]; RACSignal * timerSignal = [[notificationCenter rac_addObserverForNotificationName:@"TimerNotification" object:nil] takeUntil:self.rac_willDeallocSignal]; [timerSignal subscribeNext:^(NSNotification * notification){ NSValue * timerCount = notification.userInfo[@"TimerCount"]; NSLog(@"Timer count is %@", timerCount); }]; }
This ensures that when the controller is released, the subscription will be deleted.
My first attempt to do something similar in the Swift / RAC 3 world was
private func createTimerSignalProducer() -> SignalProducer<Int, NoError> { let notificationCenter = NSNotificationCenter.defaultCenter() let deallocSignalProducer = self.rac_willDeallocSignal().toSignalProducer() |> map { _ in () } |> catch { _ in SignalProducer.empty as SignalProducer<(), NoError> } return notificationCenter.rac_notifications(name: "TimerNotification") |> map { $0.userInfo!["TimerCount"] as! Int } |> takeUntil(deallocSignalProducer) }
and then inside viewDidLoad I would do
createTimerSignalProducer() |> start { count in println("Timer trigger for the \(count) time") }
It really worked, but what if you want to do something like this in an object that does not inherit from NSObject. Because in a regular Swift object you are not getting rac_willDeallocSignal() .
One possible solution is to save the one-time use in the instance variable and then delete it in deinit , but I would like to avoid manually processing the consumables.
UPDATE
What I ended up doing (since the Swift object has no root object) was
public protocol ViewModel { } public class BaseViewModel: ViewModel { private let deinitSignalProducerSinkPair = SignalProducer<(), NoError>.buffer() public var deinitSingalProducer: SignalProducer<(), NoError> { return deinitSignalProducerSinkPair.0 } deinit { sendNext(deinitSignalProducerSinkPair.1, ()) } }
and then in my model model
public class DetailViewModel: BaseViewModel { let title: String let subtitle: String let author: String let createdAt: NSDate let timerCounter = MutableProperty<Int>(0) let inputText = MutableProperty<String>("") let reverseInputText = MutableProperty<String>("") var formattedCreatedAt: String { let formatter = NSDateFormatter() formatter.dateFormat = "dd/MM/yy" return formatter.stringFromDate(createdAt) } public required init(title: String, subtitle: String, author: String, createdAt: NSDate) { self.title = title self.subtitle = subtitle self.author = author self.createdAt = createdAt super.init() timerCounter <~ createTimerSignalProducer() reverseInputText <~ (inputText.producer |> map { String(reverse($0)) }) }