Sorry for the multi-question post, but they're all somewhat related. I apologize if some of this is obvious, I'm still trying to wrap my head around ReactiveCocoa.
I have a UIViewController that's being passed a RACSignal whose values it needs to display. This signal will either emit a model object immediately if it already exists on the device, or if the model needs to be fetched, when the fetch finishes; so, basically the signal is a promise. This allows me to remove all knowledge of network from the view controller.
if the user dismisses the view controller, I no longer need whatever the signal will emit. Is this a normal thing to do in the view controller?
- (void) dealloc
{
[_modelSignalDisposable dispose] ; // Dispose of the subscription to the signal
}
Let's say that the device loses its connection while a model fetch is occurring, and this causes the signal to emit an error. In this case I may display something like a reload button to let the user try again. When the user taps the reload button, can I just resubscribe to the same signal even though it's completed? If not, how do I "reset" the signal? Remember, the view controller can't really re-create the signal from scratch since it has no knowledge of how it was created to begin with.
The signal in question is actually a subclass of RACSubject called a Command (has nothing to do with a RACCommand). The idea behind it is that the Command starts network fetching on subscribe and thus overrides -subscribe: to start a network operation. If the view controller goes out of scope due to user interaction I'd like for the subscription disposal to cancel any related fetch operations. Is this the right pattern?
- (RACDisposable *) subscribe:(id<RACSubscriber>)subscriber
{
RACDisposable *rd = [super subscribe:subscriber] ;
[self.remoteService startFetching] ;
#weakify(self);
return [RACDisposable disposableWithBlock:^
{
#strongify(self) ;
[self.remoteService cancelFetching] ;
[rd dispose] ;
}] ;
}
if the user dismisses the view controller, I no longer need whatever the signal will emit. Is this a normal thing to do in the view controller?
Yep, that's totally reasonable. Though I'd use -takeUntil:self.rac_willDeallocSignal instead, so that I don't have to bother with keeping the disposable around.
Let's say that the device loses its connection while a model fetch is occurring, and this causes the signal to emit an error. In this case I may display something like a reload button to let the user try again. When the user taps the reload button, can I just resubscribe to the same signal even though it's completed? If not, how do I "reset" the signal? Remember, the view controller can't really re-create the signal from scratch since it has no knowledge of how it was created to begin with.
You can simply re-subscribe if the signal is a cold signal. That is, if the signal does something on subscription. Usually this means you created the signal with +[RACSignal createSignal:].
The signal in question is actually a subclass of RACSubject called a Command (has nothing to do with a RACCommand). The idea behind it is that the Command starts network fetching on subscribe and thus overrides -subscribe: to start a network operation. If the view controller goes out of scope due to user interaction I'd like for the subscription disposal to cancel any related fetch operations. Is this the right pattern?
You have the right idea, but you can just use +[RACSignal createSignal:] instead of subclassing RACSubject.
Related
I have a class, EventPresentationController, that displays data in an Event object. The class needs to be passed an Event object, but sometimes that object needs to be downloaded from a remote service first. I don't want the controller to have any knowledge of networking stuff, so I thought that instead of passing in an Event object, I'd pass it a RAC signal. Then if the object exists I can send the signal immediately, but if the object needs to be downloaded, I can download it then send the signal. However, I'd like for the controller to display an activity indicator if in fact a download needs to occur.
I see that RACCommand has an executing signal I can subscribe to, so I suppose I can pass that instead of an event, but semantically it seems weird to be initializing a view controller with a "command" (vs. an event, or a signal that will deliver an event). Is using RACCommand the right thing to do?
That's an interesting question. I think your instincts are right that you should pass the view controller a signal.
So let's go with that. Then let's suppose your view controller has a activityIndicator property.
You should be able to do something like:
- (id)initWithEventSignal:(RACSignal *)signal {
// ... init stuff ...
// Send a YES and then a NO when `signal` completes. If `signal`
// immediately sends a value, then it will immediately send NO.
RACSignal *loading = [[[RACSignal return:#YES] takeUntil:signal] concat:[RACSignal return:#NO]];
RAC(self.activityIndicator, hidden) = [loading not];
RAC(self, event) = signal;
// ... other stuff ...
}
We're using -takeUntil: to short-circuit our loading signal when the event signal sends its result.
I have an NSView in a Mac OS X application that draws itself. When I am ready for this to happen I request it with the call:
[self.imageRenderedView setNeedsDisplay:YES];
My question is, does this call block? That is, is it synchronous, and can I assume the drawing has happened by the time the subsequent statement is executed? This assumption seems to work for me, but I feel a bit insecure about it.
The setNeedsDisplay: call only marks the view as needing display by setting
a flag in the view object.
Therefore it returns very quickly, but the drawing has not yet happened when the method returns.
From the documentation:
Whenever the data or state used for drawing a view object changes, the
view should be sent a setNeedsDisplay: message. NSView objects marked
as needing display are automatically redisplayed on each pass through
the application’s event loop. (View objects that need to redisplay
before the event loop comes around can of course immediately be sent
the appropriate display... method.)
I come from a .NET web application background and have just started iOS development. The initial design of my app focuses around the NSNotificationCenter. I was reasonably happy with this until I saw various posts explaining how reaching for the NSNotificationCentre was a common newbie mistake.
Here is a simplified version of the problem I am trying to address:
My application is trying to show a list of messages that are populated using web service calls, think Facebook messaging.
When the app is first loaded it pulls a collection of messages from the server and displays them in a table to the user. The user can add new messages (which get sent back over the API) and the app can receive Push Notifications about new messages which are added to the feed.
The messages are never persisted to disk so I'm just using POCOs for the model to keep things simple.
I have a MessageFeedController which is responsible for populating the message feed view (in a storyboard). I also have a message feed model, which stores the currently retrieved values and has various methods:
(void) loadFromServer;
(void) createMessage: (DCMMessage*) message;
(void) addMessage: (DCMMessage*) message;
(NSArray*) messages;
(int) unreadMessages;
The current implementation I have is this:
Use case 1 : Initial Load
When the view first appears the "loadFromServer" method is called. This populates the messages collection and raises an NSNotificationCenter event.
The controller observes this event, and when received it populates the tableview
Use Case 2: New Message
When a user clicks the "add" button a new view appears, they enter their message, hit send and then the view is dismissed.
This calls the createMessage method on the model, which calls the API
Once we have a response the model raises the NSNotificationCenter event
Once again the MessageFeedController listens for this event and re-populates the table
Use Case 3: Push Message
A push notification is received while the app is open, with the new message details
The AppDelegate (or some other class) will call the addMessage method on the model, to add it to the collection
Once again, assuming the MessageFeed view is open, it re-populates
In all three cases the MessageFeed view is updated. In addition to this a BadgeManager also listens to these events which has the responsibility of setting the app icon badge and the tabbar badge, in both cases the badge number relates to the number of unread messages.
It's also possible that another view is open and is listening to these events, this view holds a summary of messages so needs to know when the collection changes.
Right, thanks for sticking with me, my question is: Does this seem like a valid use of NSNotificationCentre, or have I misused it?
One concern I have is that I'm not 100% sure what will happen if the messages collection changes half-way through re-populating the message table. The only time I could see this happening is if a push notification was received about a new message. In this case would the population of the table have to finish before acting upon the NSNotification anyway?
Thanks for your help
Dan.
In other words, you're posting a notification whenever the message list is updated. That's a perfectly valid use of NSNotificationCenter.
Another option is to use Key-Value Observing.
Your controller (and anyone else) can register as an observer to the "messages" property, and will be notified whenever that property changes. On the model side, you get KVO for free; simply calling a method named setMessages: will trigger the KVO change notification. You can also trigger the notification manually, and, if so desired, the KVO notification can include which indexes of the array have been added, removed, or changed.
KVO is a standardized way to do these kinds of change notifications. It's particularly important when implementing an OS X app using Cocoa Data Binding.
NSNotificationCenter is more flexible in that you can bundle any additional info with each notification.
It's important to ensure that your messages list is only updated on the main thread, and that the messages list is never modified without also posting a corresponding change notification. Your controller should also take care to ignore these notifications whenever it is not the top-most view controller or not on screen. It's not uncommon to register for change notifications in viewWillAppear: and unregister in viewWillDisappear:.
In my opinion using a delegate protocol pattern would be a much better fit for this scenario. Consider the scenario where your "api layer" needs used across many view controllers in an application. If another developer were to be introduced to your code, they would have to hunt around for notificationcenter subscriptions instead of just following a clean 'interface' like protocol.
That being said, your code will work just fine and this is a valid use of notification center. It is just my personal preference for 'cleaner' code to use a protocol based approach. Take a look around in the iOS SDK itself and you will see scenarios where Apple themselves use protocols and use notifications. I feel it is much more easy to comprehend and use the protocols than having to dig around and determine what I must listen to for a notification.
NSNotifications run the receivers code synchronously as soon as they are posted, so a new message during repopulation would join the back of that execution queue. On the whole it seems valid to me, and it keeps a reasonable degree of separation between The view controllers and the model.
Depending on the number of classes that are likely to want to listen for the same information arriving, you may want to use a delegate pattern, maybe keeping an dictionary of delegate objects, but I personally don't feel as though this scales so well, and you also have to take care of nil-ing out delegates if a page is dealloced to avoid crashes. To sum up, your approach seems good to me.
In many of my UIViewControllers, I update certain controls based on the state of my data. For example, I might have an edit button on a UITableViewController that should only be enabled when there is one or more items. Or perhaps I want to limit the number of items that can be added, and disable the 'add' button otherwise.
Every time I add or delete an item (or take any other action that can add/remove items), I have to remember to update any controls that might need enabling/disabling. This is trivial for the most part, but doesn't feel comfortable - there is a lot of repetition, and I have to remember to add the calls to updateControlEnabled (or whatever) whenever I add new functionality that might affect the data.
And then I noticed NSManagedObjectContextObjectsDidChangeNotification. Reading the docs, it looks like I can receive a notification whenever something changes in my managed object context. This seems ideal, but I have a few questions:
Is this an appropriate use of
NSManagedObjectContextObjectsDidChangeNotification?
Should I anticipate any performance impact if a controller
subscribes to these and parses each one to see if it needs to update
the UI? I will be checking the userInfo for every change, instead of
only those that I know I will care about.
Where should I subscribe to the notifications? My UIViewController has a
reference to the context, which helps, but I don't know where to
subscribe (loadView? viewDidLoad? init?) such that the view
controller will always have one and only one subscription.
The view controller will continue to receive and process notifications
when it's offscreen - enabling and disabling controls as the
data model is affected from elsewhere. Is this ok?
I guess I'm mostly just wondering if anyone else uses this approach and if so, what their experience is.
Q) Is this an appropriate use of NSManagedObjectContextObjectsDidChangeNotification?
A) Yes - I used it on OSX for a similar purpose.
Q) Should I anticipate any performance impact if a controller subscribes to these and parses each one to see if it needs to update the UI? I will be checking the userInfo for every change, instead of only those that I know I will care about.
A) NO - it will normally be a very small set of objects - ones that were directly changed.
Q) Where should I subscribe to the notifications? My UIViewController has a reference to the context, which helps, but I don't know where to subscribe (loadView? viewDidLoad? init?) such that the view controller will always have one and only one subscription.
A) Well, you cannot affect the UI til the view shows - so probably viewDidLoad or viewWillAppear. The problem with the later is you may get it a few times depending on push/pops, so maybe I'd do it in viewDidLoad.
Q) The view controller will continue to receive and process notifications when it's offscreen - enabling and disabling controls as the data model is affected from elsewhere. Is this ok?
A) Sure - when the view reappears all the elements will be setup correctly.
What you want to do is a classical use of that notification. Just check the thread it comes in on - if its not the mainThread then you want to make all your changes in a block posted to the mainThread.
I have an app that imports a potentially large amount of data from the web after the user explicitly presses a Sync button, and stores that data using Core Data. Since I want to show feedback and I don't want the user interacting with the rest of the app while this happens, pressing the Sync button brings up a Modal dialog. Since I want the operation to happen immediately, the operation executes in the viewDidAppear method. I'm sure this is frowned upon.
There are a bunch of problems with the approach right now:
Everything happens in the main thread. The user kind of gets feedback because there is an activity indicator that continues to animate, but there's no way to indicate progress or show intermediate messages. This is not the right way to do things.
But, I am told that when using Core Data, everything has to use the main thread, so breaking off the work into another thread does not seem like it will be straightforward.
If the app enters the background state (user hits Home button or iPad falls sleep), it's game over - the operation dies. It's clear to me from the documentation why this is the case.
I know there are "I'm about to enter the background" events that you can handle, but it's not as though I can move execution of code from one place to another in the middle of a file download. Whatever solution I use has to be a continuous action that executes in the same way both before and after the transitions to/from the background.
I want the operation to execute in the foreground as far as the user is concerned. It does not make sense for them to interact with other parts of the app while this operation is taking place.
I am reading the Apple documentation on this, but I'm asking this in hopes of finding more concise guidance on this particular combination of needs. Thanks.
You really should not freeze the main thread. You can still "prohibit" certain UI actions.
Create a separate context, as a child, and do all your work in there. When done (or at certain intervals), save the context to the main context, and notify the main thread to do some UI update interaction... maybe a progress bar or something...
NSManagedContext *backgroundContext = [NSManagedContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroudContext.parentContext = [self mainManagedObjectContext];
[backgroundContext performBlock:^{
// This block is running in a background thread.
// Go get your data from the web
// Call this to push data to the main MOC (either at end, or at intervals)
[backgroundContext save:&error];
// When you want to do something on the main thread...
dispatch_async(dispatch_get_main_queue(), ^{
// This block is running on the main queue... I can do anything with the UI...
}];
}];
Couple of things to note... your mainMOC needs to be private or main queue concurrency type. If you are using the Core Data template, where it is in the app delegate, just change the alloc/init to initWithConcurrencyType:NSMainQueueConcurrencyType.
I would, however, suggest using the canonical main/parent relationship. Create a private MOC, assign it to the persistent store, then create a main MOC, set its parent to be that private MOC. Now you are ready to handle any I/O with background operations, without blocking your UI.
Still, when loading from the web, use the pattern above: create a child MOC, then load objects into the main MOC.
Note, that the data is not saved to disk until the "root" MOC calls save.