I am playing around the ReactiveCocoa 3 and Swift and I decided to do a simple toy app to test how would I design an app with ReactiveCocoa 3 that implements the MVVM pattern.
The basic staff works pretty good but I am not sure what is the best way to handle signals created from a notification center.
Lets say that somebody is triggering a notification somewhere in the app. The notification is named TimerNotification
and has an object of time Int
inside the user info dictionary accessible by the key TimerCount
. Now lets say I have a controller that wants to print a message everytime a TimerNotification
is 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);
}];
}
That will ensure me that when the controller gets deallocated the subscription will be disposed.
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 the viewDidLoad
I would do
createTimerSignalProducer()
|> start { count in
println("Timer trigger for the \(count) time")
}
That actually worked but what if you want to do something similar in an object that does not inherit from NSObject. Because in a regular Swift object you don't get rac_willDeallocSignal()
.
One possible solution is to store the disposable in an instance variable and then dispose it in the deinit
but I would like to avoid manually handling the disposables.
UPDATE
What I ended up doing (because Swift object don't have a 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 view 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)) })
}
// MARK - Private methods
private func createTimerSignalProducer() -> SignalProducer<Int, NoError> {
return NSNotificationCenter.defaultCenter().rac_notifications(name: "TimerNotification")
|> map { $0.userInfo!["TimerCount"] as! Int }
|> takeUntil(deinitSingalProducer)
}
}