Consider something like the following example:
I have a library with books, books have a difficulty and genre.
Each book is an instance of some object and could be presented by a view controller (I.E. a collection view, where each cell is a book, of perhaps a detail view where only one book is displayed).
The data of these books can be updated in the background by some kind of synchronisation method. It is possible only one book is updated, or perhaps one genre.
I would like the classes (mostly the views) to receive a notification of updates. I would like these notifications to be pretty clear. So when all non-fiction books are updated this is what should be notified.
I could of course use separate notification names for each kind, but if we are talking about an entire library, a big collection view containing thousands of objects would mean registering too many observers. In this case the observer would perhaps choose to receive any notification on books, of maybe any of a genre.
What I am missing (or can't seem to figure out) in NSNotification is some kind of granularity to specify this need.
So in short:
Is there a way to tell NSNotification more specifically what kind of notifications I would like to receive/who to send it to?
Alternatively, can I attach an object to a notification? If so, I could model scope (like meta-data) of the notification in this object and let the receiver check this data.
Yes , you can do that, just specify your object and pack your meta-data in an dictionary and attach your notification as userInfo.and use this method to post notification :
- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo
then you can access your notification like :
- (void)handleNotification:(NSNotification *)noti{
NSDictionary *userInfo = [noti userInfo];
YourObject *object = [noti object];
}
Related
I'm looking to integrate iCloud with a Core-Data-managed SQLite database (only on iOS 7 and later). I've been reading Apple's guide on using Core Data with iCloud (https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/UsingCoreDataWithiCloudPG.pdf).
To quote from the guide, "Core Data posts an NSPersistentStoreCoordinatorStoresWillChangeNotification notification. In your notification handler, you reset your managed object context and drop any references to existing managed objects."
Calling -reset on the MOC to reset it isn't the problem, the problem is the part where they say all references to managed objects need to be dropped. I understand why this needs to be done (because the persistent store is changing), what I don't know is how to do it.
All my Core Data work is handled by a singleton and I had originally thought of posting a notification, and listening classes could set all their managed objects to nil. First, this doesn't sound like a particularly good way of doing it. Secondly, I have a FetchedResultsController managing a tableView, the FetchedResultsController manages it's own managed objects, therefore, as far as I know, I can't set them to nil.
I'd be really grateful for any advice on what to do here.
Thanks in advance.
The way I handle situations like this is to post two notifications in my app: just before resetting, and just after resetting.
For example, I might post MYMainContextWillResetNotification, then reset the context, then post MYMainContextDidResetNotification.
Any controller receiving the will-reset notification should release its managed objects, but also store any information it will need to recover after the reset. Usually this will be one or more NSManagedObjectID objects. In some cases, you may not need to store anything, simply performing a fetch after the reset instead.
A typical method might look like this:
- (void)mainContextWillReset:(NSNotification *)notif
{
self->noteID = note.objectID;
}
This code supposes there is a controller for a single note object. When the reset is about to take place, the note's object identifier is stored in an instance variable.
The did-reset notification method retrieves the note.
- (void)mainContextDidReset:(NSNotification *)notif
{
note = [context existingObjectWithID:noteID error:NULL];
[self refreshViews];
}
This code uses existingObjectWithID:error:, but you could equally do a fetch.
With an NSFetchedResultsController, you would need to call performFetch: in the did-reset method, to refresh the objects.
I am developing an app that fetches data from the web and displays it to the user. Assume that the data is reviews of a restaurant and one review is displayed on one view. The user can swipe left or right to go to the prev/next review. The data is fetched asynchronously (one thread for each review).
Here is the problem statement - Assume that 5 reviews have been fetched and the user is looking at the 3rd one currently. Now, the 6th review is fetched and I want to display it as the 4th review to the user (because the publish date of the 6th review is more recent than the 5th review). How should my model class inform the view controller?
I have considered some options -
Provide an array to the view controller and then send NSNotifications about new items to be inserted in-between the array at a specific index
Use an NSFetchedResultsController (this is a bit tricky because I am not using it with a table view controller)
View controller always asks for the next review to be displayed (from the model) and does not have a array of reviews with it
Are there any established design patterns that are employed in such a scenario? Other suggestions apart from the 3 above are welcome!
Just use an NSFetchedResultsController. When using NSIndexPaths just ignore the section. It's basically a glorified NSArray with free notifications.
Here's how I think I'd do it:
Make sure that the NSFetchRequest for your NSFetchedResultsController is sorted by publish date.
Handle NSFetchedResultsControllerDelegate methods.
When the NSFetchedResultsController updates, save the current object, reload the collection view, and then scroll to the saved object without any animation. This will appear to the user as if nothing happened to the current page.
While there is no perfect design pattern for every programming problem, the closest I can think of that relates to your problem is a combination of the Command and Observer patterns.
https://en.wikipedia.org/wiki/Command_pattern
The observer pattern is used in the NSNotification center.
While it's unclear as to why you'd want to skip a review, you could have two arrays to store them when fetched. The first holds all reviews that you have fetched. The second holds all reviews that are displayed.
Then you can get the last review in the fetched array, as if it were a stack. This way you always have the last one loaded displayed to the user.
I am confused why the order of display is different than the true order, ie why the 6th review comes before the 5th, but you asked about patterns to help.
Apart from MVC and observer, which are in the other answers and comments, I'd suggest using lazy loading with a virtual proxy. When reviews have been fetched, you can just display their proxy (eg with a "loading..." Message until they're fully in memory).
See more here: http://en.wikipedia.org/wiki/Proxy_pattern
I would recommend using the observing pattern to inform your controller than new data as been fetched. When receiving the signal, your view controller could update its array of "restaurant review" (either by adding the old one and reordering it according to some sort descriptors of your flavor or by querying the DAO directly).
Let's say you are fetching your data from internet and populating a CoreData entity with the results. Once you got your downloaded data you can populate your core data "Review" entity.
In order to "listen" at the change happening in core data, your controller should, in the viewDidLoad body, register itself as an observer for the NSManagedObjectContextDidSaveNotification.
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(updateInfo:) name:NSManagedObjectContextDidSaveNotification object:nil];
Then in your updateInfo, you can get the changes
- (void) updateInfo:(NSNotification *)notification
{
self.reviews = [self.managedObjectContext performRequest:myFetchRequest error:nil];
}
Given a Core Data Entity called Book, what would be the best way to get a count of how many Books there are, in total, and display this total in a UILabel? The UILabel would need to update as book objects were created/deleted (potentially by other objects elsewhere in the app).
Actually counting the books isn't difficult. My question is more how do we get updates as this count changes?
Perhaps override willSave/delete/etc on the Book managed object?
KVO (but how do we do this on the entire Entity rather than a single object?)
Something else?
You can listen for the notification (NSManagedObjectContextObjectsDidChangeNotification) relating to core data changes and update your UI from that.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleDataModelChange:) name:NSManagedObjectContextObjectsDidChangeNotification object:myManagedObjectContext];
- (void)handleDataModelChange:(NSNotification *)notification;
{
NSSet *updatedObjects = notification.userInfo[NSUpdatedObjectsKey];
NSSet *deletedObjects = notification.userInfo[NSDeletedObjectsKey];
NSSet *insertedObjects = notification.userInfo[NSInsertedObjectsKey];
// update your UI with the new count
}
NB: dont forget to remove yourself,
- (void)dealloc;
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
You could create a NSFetchedResultsController to fetch book objects, then implement it's delegate methods. Reference:
NSFetchedResultsController
NSFetchedResultsControllerDelegate Protocol
As already suggested, you could subscribe to NSManagedObjectContextObjectsDidChangeNotification and try to analyze it. I see two disadvantages in this. First, this notification is called a lot and on any change in any object. Calls will not literally happen at the moment when the change happens, but still any change is reported in this notification. Second, the subscriber needs to know all the contexts where the new Book is created. You might think of subscribing to this notification with nil for context to receive this notification for all the contexts. But this is even more overwhelming and dangerous, and not recommended by Apple (you will receive this notification also for the contexts that you don’t own).
What I would recommend for each place that creates a Book to have its own notification. Like XYBookAddControllerDidAddBookNotification, XYBookListControllerDidDeleteBookNotification and so on. In this case:
The place that displays counts doesn’t have to know anything about the context where change was made.
The place that displays will only respond to specific add/remove events instead of analyzing heavy traffic of objects-did-change notification.
I use Core Data - and I have registered and is listening for NSManagedObjectContextDidSaveNotification:s I have a collection of data (from JSON), that I want to save, and after all objects is saved, I would like to get some kind of notification. It currently seems like this notification is sent after every object is saved. Is there some kind of built in solution for getting my desired notification? If not, how could/should I do it?
There's no built-in notification that gets posted after you've saved a specific batch of objects. Core Data really has no idea how many objects are in your collection, so it has no way to know that you've reached the end of it.
You'll get NSManagedObjectContextDidSaveNotification every time you call save: on the managed object context. You could just wait to save until you've handled all of the objects, which would mean just one NSManagedObjectContextDidSaveNotification.
A better solution is to post your own notification when you know that you've finished the collection. Define a string constant called something like JSONUpdatesCompleteNotification, and post a notification with that name after your last save: call.
NSString *JSONUpdatesCompleteNotification = #"JSONUpdatesCompleteNotification";
Then later, when you know you're done,
[[NSNotificationCenter defaultCenter] postNotificationName:JSONUpdatesCompleteNotification object:self];
Make sure you observe this notification anywhere you need to know about it, and you're done.
I am new to iOS development, so I would appreciate some feedback.
I am trying to build an iOS client for my web service. So far this is what I have done:
I am implementing two views (Utility-based app using Storyboard). In the main view, I use a text field and a search button, where the user can enter a query and then click the search button. Once the search button is clicked, my intention is to read the value of the text field, and use it in my Restful call to my web service. My web service replies back with a JSON file with the query results, which I parse and show to the secondary view's text area.
I know how to do the restful call in iOS and how to do the JSON parsing as well as displaying the results on the screen (at least the text stuff, but that's another different question). But my intention is to learn and implement MVC basics to my application.
According to MVC, the controller updates the view, and the model sends out a notification broadcast which the controller can listen to and know if there are any changes in the object. So this is what I would ideally like to do:
My Model - My model would handle the core RESTful call, get the JSON reply, parse it and get the resulting values that I want to display on the view.
My Controller - I would like my controller to listen to my model and obtain the resulting values from Model and display them on View.
Using a quick and dirty way, I can implement the RESTful call, JSON parsing and displaying resulting values - all inside the Controller, but with this technique, if my view changes tomorrow, then I have to re-write my code. Or if I want to add new features, then I have to change my controller. So ideally I would like to have a core Model that's not aware of how View looks like, and just let's the Controller take the results from Model and display them on View.
From what I have read from Google search results so far, two ways of doing this is by a) Key Value Observation and b) Notification center.
For last 2 days, I am trying to find a good decent way to implement Notification center or read more about it, I am not getting a good lead. Some of the questions I have is, can I send out the String results value using Notification center that my controller picks up? How does Notification Center really work with string values? Where can I find some good examples?
So any help regarding this will be very much appreciated.
Some of the questions I have is, can I send out the String results
value using Notification center that my controller picks up?
Yes, that would commonly done using the userInfo property of a NSNotification. userInfo is a plain NSDictionary that may contain instances of NSObject derived objects indexed by keys that are adhering to the NSCopying protocol (commonly NSString is used). Note that the dictionary (userInfo) will retain your parameter object/s.
How does Notification Center really work with string values?
Well, that depends on how you want it to work. But nitpicking aside, see below.
Where can I find some good examples?
Maybe this one helps...
Example
The receiver (controller) registers for the notification:
- (void)registerForNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(modelObjectUpdatedString:)
name:#"StringUpdated"
object:nil];
}
The sender (model) notifies the world:
- (void)stringUpdateWith:(NSString *)theString
{
self.string = theString;
[[[NSNotificationCenter defaultCenter] postNotificationName:#"StringUpdated"
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:self.string, #"String", nil]];
}
The receiver (controller) receives the notification within its handler:
- (void)modelObjectUpdatedString:(NSNotification *)notification
{
ModelObject *postingObject = [notification object];
NSString *string = [[notification userInfo]
objectForKey:#"String"];
...
}
You're thinking along the right path, but still not entirely. As Till "points out" in his comment, you should not handle the RESTful communication inside your model. If I were you, I would create a utility class responsible for fetching the information, and then a class responsible for holding the data(this last class is your model).
It would be clever to create a class method that allocates and initiates a new instance of this object, created from the JSON data fetched through your RESTful communicator class.
From your controller point of view:
RESTHelper *rest = [RESTHelper restHelperWithURL:yourRESTURL];
YourModel *model = [YourModel modelWithJSON:[rest fetchObjectWithID:1]];
// Present your models data in the view.
You may benefit from using CoreData here, and I strongly encourage you to look into that.