What is the proper use of NSNotificationCenter with ReactiveCocoa 3 and Swift?

1.3k views Asked by At

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

There are 0 answers