disableUndoRegistration is still allowing undo operations - ios

I want to disable undo registration for an operation on an NSManagedObject but it still records the operation even though I explicitly call disableUndoRegistration.
Is there something obvious I am missing?
I also tried to enable/disable in the viewWillAppear and viewWillDisappear methods, respectively.
Here is some example code...
#pragma mark -
#pragma mark NotesViewControllerDelegate methods
- (void)notesViewController:(NotesViewController *)controller didFinishWithSave:(BOOL)save
{
if (save)
{
[undoManager disableUndoRegistration];
[book setNotes:[controller getDataFromText]];
[undoManager enableUndoRegistration];
}
}

You have to call [managedObjectContext processPendingChanges]; before each of the calls that disable and enable the undo registration because Core Data queues changes to be able to do optimizations.
see
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdUsingMOs.html

Related

How to make app more responsible?

When launching app first, app do prefill its local persistent store from backend. It happens on the DISPATCH_QUEUE_PRIORITY_LOW, but it tears when user scroll in tableView meantime. What else can I do? Heavy stuff are already on lowest priority.
DISPATCH_QUEUE_PRIORITY_BACKGROUND has even lower priority. If that does not help I think you could:
Pre-fill on a serial queue. Most iOS devices have more than 2 cores and 1 of those should be able to handle table scrolling.
Pre-fill slower. It may be that you fill the memory-bandwidth or flush the L2-cache during your pre-fill. This could be hard to solve. Maybe you can periodically reload all visible table cells to keep that code from going stale, but it may also interfere with user scrolling.
You could do a couple of things I believe
1) you could start with an empty dataset and when you're done getting all the data use ''' self.tableview.reloadData() ''' in your building block.
2) if that's not possible, then you could always present a loader so the rest of the UI is disabled while the data is being created, this one is pretty easy to use https://github.com/jdg/MBProgressHUD
I have one idea, but here i suggest use NSOperation (if you have enough time to refactor some of your code). You can organize your download process throw NSOperationQueue.
For example,
- (NSOperationQueue *)downloadQueue {
if (!_downloadQueue) {
_downloadQueue = [[NSOperationQueue alloc] init];
_downloadQueue.name = #"Download Queue";
_downloadQueue.maxConcurrentOperationCount = 1;
}
return _downloadQueue;
}
Subclass NSOperation add override main func, where you can write your donwload code
- (void)main {
// 4
#autoreleasepool {
// download code here
}
Next step - check when user start scrolling tableview (or whatever user interaction you want) and start/ stop operations executing on that event). for example, for uitableview it will look like:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
// 1
[self.model suspendAllOperations];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// 2
[self.model resumeAllOperations];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self.model resumeAllOperations];
}
Model here is a NSObject subclass, that handles all download process (independently of UI) where you can suspend/resume operations.
- (void)suspendAllOperations {
[_downloadQueue setSuspended:YES];
}
- (void)resumeAllOperations {
[_downloadQueue setSuspended:NO];
}
- (void)cancelAllOperations {
[_downloadQueue cancelAllOperations];
}
So, when you expect heavy operations in your UI, you can stop your background process and resume it when you need. Also you can change maxConcurrentOperationCount for your best performance (this param you can set after some testing / measurement )
Hope this helps.

Implement a Debounced/Coalesced Pattern in Cocoa Touch like `layoutSubviews`

A number of Cocoa Touch classes leverage a design pattern of coalescing events. UIViews, for example, have a method setNeedsLayout which causes layoutSubviews to be called in the very near future. This is especially useful in situations where a number of properties influence the layout. In the setter for each property you can call [self setNeedsLayout] which will ensure the layout will be updated, but will prevent many (potentially expensive) updates to the layout if multiple properties are changed at once or even if a single property were modified multiple times within one iteration of the run loop. Other expensive operations like the setNeedsDisplay and drawRect: pair of methods follow the same pattern.
What's the best way to implement pattern like this? Specifically I'd like to tie a number of dependent properties to an expensive method that needs to be called once per iteration of the run loop if a property has changed.
Possible Solutions:
Using a CADisplayLink or NSTimer you could get something working like this, but both seem more involved than necessary and I'm not sure what the performance implications of adding this to lots of objects (especially timers) would be. After all, performance is the only reason to do something like this.
I've used something like this in some cases:
- (void)debounceSelector:(SEL)sel withDelay:(CGFloat)delay {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:sel object:nil];
[self performSelector:sel withObject:nil afterDelay:delay];
}
This works great in situations where a user input should only trigger some event when a continuous action, or things like that. It seems clunky when we want to ensure there is no delay in triggering the event, instead we just want to coalesce calls within the same run loop.
NSNotificationQueue has just the thing you're looking for. See the documentation on Coalescing Notifications
Here a simple example in a UIViewController:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(configureView:)
name:#"CoalescingNotificationName"
object:self];
[self setNeedsReload:#"viewDidLoad1"];
[self setNeedsReload:#"viewDidLoad2"];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setNeedsReload:#"viewWillAppear1"];
[self setNeedsReload:#"viewWillAppear2"];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setNeedsReload:#"viewDidAppear1"];
[self setNeedsReload:#"viewDidAppear2"];
}
- (void)setNeedsReload:(NSString *)context
{
NSNotification *notification = [NSNotification notificationWithName:#"CoalescingNotificationName"
object:self
userInfo:#{#"context":context}];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification
postingStyle:NSPostASAP
coalesceMask:NSNotificationCoalescingOnName|NSNotificationCoalescingOnSender
forModes:nil];
}
- (void)configureView:(NSNotification *)notification
{
NSString *text = [NSString stringWithFormat:#"configureView called: %#", notification.userInfo];
NSLog(#"%#", text);
self.detailDescriptionLabel.text = text;
}
You can checkout the docs and play with the postingStyle to get the behavior you desired. Using NSPostASAP, in this example, will give us output:
configureView called: {
context = viewDidLoad1;
}
configureView called: {
context = viewDidAppear1;
}
meaning that back-to-back calls to setNeedsReload have been coalesced.
I've implemented something like this using custom dispatch sources. Basically, you setup a dispatch source using DISPATCH_SOURCE_TYPE_DATA_OR as such:
dispatch_source_t source = dispatch_source_create( DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, dispatch_get_main_queue() );
dispatch_source_set_event_handler( source, ^{
// UI update logic goes here
});
dispatch_resume( source );
After that, every time you want to notify that it's time to update, you call:
dispatch_source_merge_data( __source, 1 );
The event handler block is non-reentrant, so updates that occur while the event handler is running will coalesce.
This is a pattern I use a fair bit in my framework, Conche (https://github.com/djs-code/Conche). If you're looking for other examples, poke around CNCHStateMachine.m and CNCHObjectFeed.m.
This borders on "primarily opinion based", but I'll throw out my usual method of handling this:
Set a flag and then queue processing with performSelector.
In your #interface put:
#property (nonatomic, readonly) BOOL needsUpdate;
And then in your #implementation put:
-(void)setNeedsUpdate {
if(!_needsUpdate) {
_needsUpdate = true;
[self performSelector:#selector(_performUpdate) withObject:nil afterDelay:0.0];
}
}
-(void)_performUpdate {
if(_needsUpdate) {
_needsUpdate = false;
[self performUpdate];
}
}
-(void)performUpdate {
}
The double check of _needsUpdate is a little redundant, but cheap. The truly paranoid would wrap all the relevant pieces in #synchronized, but that's really only necessary if setNeedsUpdate can be invoked from threads other than the main thread. If you're going to do that you also need to make changes to setNeedsUpdate to get to the main thread before calling performSelector.
It's my understanding that calling performSelector:withObject:afterDelay: using a delay value of 0 causes the method to be called on the next pass through the event loop.
If you want your actions to be queued up until the next pass through the event loop, that should work fine.
If you want to coalesce multiple different actions and only want one "do everything that accumulated since the last pass through the event loop" call, you could add single call to performSelector:withObject:afterDelay: in your app delegate (or some other single instance object) at launch, and invoke your method again at the end of each call. You could then add an NSMutableSet of things to do, and add an entry to the set each time you trigger an action that you want to coalesce. If you created a custom action object and overrode the isEqual (and hash) methods on your action object, you could set it up so there would only ever be a single action object of each type in your set of actions. Adding the same action type multiple times in a pass through the event loop would add one and only one action of that type).
Your method might look something like this:
- (void) doCoalescedActions;
{
for (CustomActionObject *aCustomAction in setOfActions)
{
//Do whatever it takes to handle coalesced actions
}
[setOfActions removeAllObjects];
[self performSelector: #selector(doCoalescedActions)
withObject: nil
afterDelay: 0];
}
It's hard to get into details on how to do this without specific details of what you want to do.

Message sent to a deallocated instance

Background:
All my OpenTok methods are in one ViewController that gets pushed into view, like a typical Master/detail VC relationship. The detailVC connects you to a different room depending on your selection. When I press the back button to pop the view away, I get a crash (maybe 1 out of 7 times):
[OTMessenger setRumorPingForeground] message sent to deallocated instance xxxxx
or
[OTSession setSessionConnectionStatus:]: message sent to deallocated instance 0x1e1ee440
I put my unpublish/disconnect methods in viewDidDisappear:
-(void)viewDidDisappear:(BOOL)animated{
//dispatch_async(self.opentokQueue, ^{
[self.session removeObserver:self forKeyPath:#"connectionCount"];
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
//});
[self doCloseRoomId:self.room.roomId position:self.room.position];
}
Here is a trace:
Here is the DetailViewController on Github: link here
How to reproduce:
Make a selection from the MasterVC, that takes you into the DetailVC which immediately attempts to connect to a session and publish
Go back to previous, MasterVC quickly, usually before the session has had an a chance to publish a stream
Try this several times and eventually it will crash.
If I slow down and allow the publisher a chance to connect and publish, it is less likely to cause a crash.
Expected result:
It should just disconnect from the session/unpublish and start a new session as I go back and forth between the Master/DetailVC's.
Other:
What is your device and OS version?
iOS 6
What type of connectivity were you on?
wifi
Zombies Enabled?
Yes
ARC Enabled?
Yes
Delegates set to nil?
Yes, as far as I know
Any help solving this crash would be greatly appreciated. Perhaps I'm missing something basic that I just can't see.
What seems to happen is that the OTSession object in the OpenTok library continues to to send messages to objects in that library that have since been deallocated by switching views. The library has a [session disconnect] method that works fine if you give it enough time, but it takes close to 2-3 seconds, and that's a long time to pause an app between views.
This might be a stupid question, but:
Is there anyway to stop all processes initiated by a certain VC?
Closing the session from viewWillDisappear() works if you can determine for sure that the view is going to be popped, not pushed or hidden. Some answers suggest putting this code in dealloc(). Regarding those suggestions, Apple says,
You should try to avoid managing the lifetime of limited resources using dealloc.
So, here is how you can determine for sure that your view will get popped. viewWillDisappear() is called when the view is popped from the stack, or is otherwise pushed somewhere else. This is the easiest way to determine which, and then unpublish/disconnect if it is truly popped. You can test for this with isMovingFromParentViewController. Also, here is where you can remove specific observers.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated]
// This is true if the view controller is popped
if ([self isMovingFromParentViewController])
{
NSLog(#"View controller was popped");
// Remove observer
[[NSNotificationCenter defaultCenter] removeObserver:self.session];
...
//dispatch_async(self.opentokQueue, ^{
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
//});
[self doCloseRoomId:self.room.roomId position:self.room.position];
}
else
{
NSLog(#"New view controller was pushed");
}
}
Ref: Testing for Specific Kinds of View Transitions
Looks like OpenTok have a bug with usage NSNotificationCenter inside of OTSession and OTMessenger classes. You can see these classes in call-stack are separated with NSNotificationCenter calls:
You can manually unsubscribe your OTSession object when dealloc (hope OpenTok uses defaultCenter):
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self.session];
}
You need to check if this code (dealloc) is really executed. If not - you need to fix problem of UIViewController deallocation. A lot of other answers contains tips how to help UIViewController to be deallocated.
-(void)viewDidDisappear:(BOOL)animated is called whenever the view is hidden, not only when it is popped from the view stack.
So if you push a view over it, viewWillDisappear will be called and your objects deleted.
This is specially problematic if you load these same objects from viewDidLoad: instead of viewDidAppear:.
Perhaps you should put your unpublish/disconnect code in -(void)dealloc.
This is what Apple suggests:
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
But this is only the last resort to remove observers, still often a good habit to always add it to make sure everything is cleand up on dealloc to prevent crashes.
It's still a good idea to remove the observer as soon as the object is no longer ready (or required) to receive notifications.
I most of the time put such a code in the viewWillDisappear, but I guess that doesn't really matter.
I believe the issue is that your session delegate is not set to nil. Just add the following in your viewDidDisappear:
self.session.delegate=nil;
You must call [super viewDidDisappear:animate]; at the beginning. May be it will fix your issue.
And better cleanup your session and subscriber in dealloc method:
- (void) dealloc {
[self.session removeObserver:self forKeyPath:#"connectionCount"];
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
[self doCloseRoomId:self.room.roomId position:self.room.position];
//[super dealloc]; //for non-ARC
}
According to the stack trace you have posted, the notification center reaches out to an OTSession instance that is still alive. Afterwards, this instance provokes a crash calling methods on deallocated objects.
Adding to that the two different deallocated instance messages, we know there are asynchronous events occuring after the death of some objects that trigger the random crash you are having.
As ggfela suggested, you should make sure to nil out the delegates you have connected to the OpenTok framework. I strongly suggest you do that in the dealloc method as we want to make sure that after that point, no one has any dangling references to your object :
- (oneway void)dealloc
{
self.session.delegate = nil;
self.publisher.delegate = nil;
self.subscriber.delegate = nil;
}
Another odd thing in the code is that your handler for sessionDidConnect: creates a new dispatch_queue every time it is being called in order to call doPublish:. This means that you have concurrent threads sharing the SROpenTokVideoHandler instance which makes it prone to race conditions.

Auto-save not working with NSUndoManager on UIManagedDocument

Resolution
NSUndoManager must only be used in a child NSManagedObjectContext (when used with Core Data). This is because the UIManagedDocument may auto-save at any point in time, after which an undo will have no effect. Therefore there is no point using NSUndoManager to just achieve save/cancel functionality, since a child context will give you the same result.
Bit sad really, because NSUndoManager is a lot easier to implement than a child context (for the latter I have to call existingObjectWithID to copy objects from the parent to the child - painful). Personally I would have thought the document should not auto-save if groupingLevel != 0. Rant finished.
Original Question
I have a table view controller that loads data using Core Data into a UIManagedDocument. It segues to a view controller to edit each row in the table. In that view controller I have cancel and save buttons. I am implementing the cancel capability using NSUndoManager through a category on my NSManaged object (self.list below).
- (void)viewDidLoad
{
[super viewDidLoad];
[self.list beginEdit];
}
- (IBAction)cancel:(id)sender
{
[self.list cancelEdit];
[self close];
}
- (IBAction)save:(id)sender
{
[self.list endEdit];
[self close];
}
The category implements beginEdit, endEdit and cancelEdit which is intended to handle the NSUndoManager stuff. In the code below, useUndo is a constant that I set to NO or YES to see the impact of using NSUndoManager.
- (void)beginEdit
{
if (useUndo)
{
NSUndoManager *undoManager = [[NSUndoManager alloc] init];
self.managedObjectContext.undoManager = undoManager;
[undoManager beginUndoGrouping];
}
}
- (void)endEdit
{
[self.managedObjectContext save:nil];
if (useUndo)
{
NSUndoManager *undoManager = self.managedObjectContext.undoManager;
[undoManager endUndoGrouping];
self.managedObjectContext.undoManager = nil;
}
}
- (void)cancelEdit
{
if (useUndo)
{
NSUndoManager *undoManager = self.managedObjectContext.undoManager;
[undoManager endUndoGrouping];
[undoManager undo];
}
}
I can see the Core Data debug messages showing it is committing the changes if I save an object and click the Home button when useUndo = NO. However, with useUndo = YES, it does not auto-save when I click on the Home button. I have waited a couple of minutes, and it still doesn't autosave. Is there some way I can force an auto-save?
Can anybody explain why using undoManager causes this change in behaviour?
I suspect either I am going about this the wrong way, or have some simple problem in the code. Any help would be appreciated.
I'm not sure if it's correct but other answers on stackoverflow have mentioned that an NSUndoManager clears the undo stack when the context saves. That means that using an undo manager with auto-save would at most be useful for a couple of seconds (whatever the auto-save interval is). There might be a connection there, I'm trying to find out more...

Is it good approach to use [self release], [self retain]?

I created DownloadAndParseBook class. It will not autorelesed before it gеt any data or network error.
I used [self release], [self retain]. Is it good approach to use [self release], [self retain]? Is DownloadAndParseBook contain any potential bugs?
#implementation GetBooks
-(void) books
{
for(int i =0; i<10; i++)
{
DownloadAndParseBook *downloadAndParseBook =
[[[DownloadAndParseBook alloc] init]autorelease];
[downloadAndParseBook startLoadingBook];
}
}
#end
#implementation DownloadAndParseBook
- (id)initWithAbook:(int)bookID
{
if(self = [super init])
{
[self retain];
}
return self;
}
- (void)startLoadingBook
{
[NSURLConnection connectionWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[self release];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self saveResultToDatabase];
[self release];
}
#end
Self retaining is very occasionally an appropriate pattern. It's rare, but sometimes in certain kinds of multi-threaded code its important to make sure that you don't vanish in the middle of processing something. That said, this is not one of those times. I'm having trouble imagining a case where your current approach would be helpful. If someone creates your object and then never calls startLoadingBook, then it leaks. If someone calls startLoadingBook, then your object is retained anyway, because NSURLConnection retains its delegate until it finishes.
That said, I believe much of your problem is coming from the fact that your object model is wrong. Neither GetBooks nor DownloadAndParseBook make sense as classes. What you likely mean is BookManager (something to hold all the books) and BookDownloadController (something to manage the downloading of a single book). The BookManager should keep track of all the current BookDownloadControllers (in an NSSet or NSArray ivar). Each BookDownloadController should keep track of its NSURLConnection (in an ivar). You should not just create connections and have them "hang on themselves" (i.e. self-retain). This feels convenient, but it makes the code very hard to deal with later. You have no way to control how many connections you're making. You have no way to cancel connections. It becomes a mess really quickly.
No it is not a best practice.
Retaining / releasing your object should be done by the "owner" of your object.
For your particular example, the owner of your DownloadAndParseBook object is the object that does the alloc/init. That should be the oen retaining/releasing your DownloadAndParseBook instance.
Best practice here would be alloc/init for DownloadAndParseBook, retain done by the owner, all your download/parse logic, then sending a callback to the owner that all the operations are done (through a delegate for example), at which point, the ower sends a release message to your object.
The question would be: Why does an object require to retain itself? You may want to implement your class like a singleton.
Unlike the other responders I would say that your pattern might work. See also Is calling [self release] allowed to control object lifetime?
There are some other issues in your code however:
In -(void) books I guess you want to send the startLoadingBook message to downloadAndParseBook and not to self
If you create a initWithAbook method it will not be called when you init your book with the standard init method. In the current code above [self retain] will be never called
In your code above bookID will not be saved
I would not use "init" pattern here, but everything in a static function thus the caller can not make mistake with the ownership of the class.
Code:
- (id) initWithId:(int)bookId {
self = [super init];
if (self) {
// save bookId here
}
return self;
}
+ (void) startLoadingBookWithID:(int)bookId {
DownloadAndParseBook* book = [[DownloadAndParseBook alloc] initWithId:bookId];
[NSURLConnection connectionWithRequest:request delegate:book];
}
// release self when it finished the operation
// and document well that its behaviour
If you think well, NSURLConnection itself should work exactly the same way: when you don't release an NSURLConnection when it finished its work, it does it itself. However in the connectionWithRequest it also can not autorelease itself since it has to be alive until the request is served. So the only way it can work is the pattern described above
Never use [self release]. The only possible exception would be in an singleton class/object. The methods release and retain should only be sent by the owner of an object. This usually means, whichever object created the object in question, should also be the one to release it.

Resources