IOS Object respawn on timer after I set its ID to nil - ios6

DemoViewController is responsible for showing tutorial to the user. Contains animations and timer to repeat gesture demo while ignored by the user. Is instantiated form DataViewController. Is nil-ed, but later respawns on its internal timer. I need it to be completely gone so it is not created again when user returns to the first page.
dataViewController.h
#import "DemoViewController.h"
#property (strong,nonatomic) DemoViewController *demoController;
dataViewController.h
-(void) viewWillAppear:(BOOL)animated {
// demoPageNumber is 0
if ((self.demoController== nil) && ([_pageNumber isEqualToNumber:demoPageNumber])){
self.demoController = [[DemoViewController alloc] initWithView:self.view];
}
}
-(void) viewWillDisappear:(BOOL)animated{
[self.demoController free]; // invalidate timer, nil all internal objects
self.demoController=nil; // should free object
}
DemoViewController.m
-(void) free{
[animationRespawnTimer invalidate];
animationRespawnTimer=nil;
}
-(void) respawnDemoWithSelector:(SEL)selector{
NSLog(#"Timer fired %#", self);
[self resetTimer];
animationRespawnTimer = [NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:selector
userInfo:nil
repeats:NO];
}
-(void) showScrollDemo{
NSLog(#"showScrollDemo fired");
[self stopPreviousAnimations];
scrollHandView.frame = CGRectMake(315.0, 700.0, 100, 100);
scrollHandView.hidden=NO;
scrollHandView.alpha=1;
[UIView animateWithDuration:3.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionRepeat )
animations:^{
[UIView setAnimationRepeatCount:3];
scrollHandView.frame = CGRectMake(315.0, 300.0, 100, 100);
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
scrollHandView.alpha=0;
}
completion:^(BOOL finished){
scrollHandView.hidden=YES;
[self respawnDemoWithSelector: #selector(showScrollDemo)];
}
];
}
];
}
When the page is loaded and if this is the first page, demoController is instantiated, and on exit from the page nil-ed after clean-up (custom free method). According to my understanding this should delete the demoController object with all its contents including the timer. And debug area shows exactly that! Until when on the new page the demoController timer mystically respawns from nowhere with previous object ID.
17:59:14.041 viewWillAppear begin (self.demoController null)
18:00:05.346 viewWillAppear, <DemoViewController: 0x7580310> //demoController created
18:00:15.786 in the demoController method the "showScrollDemo" is fired
18:00:19.834 viewWillAppear end <DemoViewController: 0x7580310>
Page was loaded, demo performed fine. Now I'm flipping the page. viewWillDisappear event is fired.
18:01:17.966 viewWillDisappear begin, send "free" message to demoController
18:01:17.966 "free" was performed from <DemoViewController: 0x7580310>
18:01:34.059 viewWillDisappear end (self.demoController null)
So, the "self.demoController" is null. Then the demoController respawns itself with previous ID
18:02:36.514 Timer fired <DemoViewController: 0x7580310>
Why? Timer can not respawn, it is set to repeats:NO.

I assume that it is the completion block of the animation that calls respawnDemoWithSelector and creates a new timer.
According to this answer: https://stackoverflow.com/a/9676508/1187415, you can stop all running
animations with
[self.view.layer removeAllAnimations];
Alternatively, you can add a boolean property done to the DemoViewController which is set
to YES in the free method, and checked in the completion block of the animation:
if (!self.done)
[self respawnDemoWithSelector: #selector(showScrollDemo)];
UPDATE: The animation blocks capture a strong reference to self, thus preventing
the object from being deallocated. The standard solution to that "retain-cycle" problem
(assuming that you use ARC) is to use a weak reference to self. That would look like this:
__weak typeof(self) weakSelf = self;
[UIView animateWithDuration:3.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionRepeat )
animations:^{
[UIView setAnimationRepeatCount:3];
weakSelf.scrollHandView.frame = CGRectMake(315.0, 300.0, 100, 100);
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
weakSelf.scrollHandView.alpha=0;
}
completion:^(BOOL finished){
weakSelf.scrollHandView.hidden=YES;
[weakSelf respawnDemoWithSelector: #selector(showScrollDemo)];
}
];
}
];
weakSelf does not hold a strong reference to the DemoViewController and is set to nil
automatically if the object it points to is deallocated. In that case, all message sent to weakSelf inside the blocks do just nothing.

Related

UIProgressView does not update

If it matters:
- I am using storyboards
- Core data
- xcode 4.6
My app has a UITableViewController for a specific view. On that view, if the user clicks a button the software goes through a few processes where data is downloaded from an Internet API and saved into core data. I'm confident this is a resource hog, which is why I am attempting to complete those processes in separate threads.
Note:
- There is an order of operations because legs are dependent on exchanges. Exchanges are dependent on the race and positions. Positions are dependent on the race. Otherwise I would have executed everything asynchronously.
Issues:
- This is my first time working with Grand Central Dispatch. I'm not sure I am doing it correctly.
- If I comment out the data processing the UIProgressView is visible and updates as expected. With the data processing in place the system seems too bogged down to even display the UIProgressView.
The methods managing the downloads and progress is below.
- (IBAction)downloadNow:(id)sender {
[progressView setHidden:NO];
[progressView setProgress:0.1 animated:YES];
dispatch_sync(backgroundQueue, ^(void){
[self saveRace];
[self updateProgress:0.2];
});
dispatch_sync(backgroundQueue, ^(void){
[self savePositions];
[self updateProgress:0.3];
});
dispatch_sync(backgroundQueue, ^(void){
[self downloadExchanges];
[self saveExchanges];
[self updateProgress:0.4];
});
dispatch_sync(backgroundQueue, ^(void){
[self downloadLegs];
[self saveLegs];
[self updateProgress:0.5];
});
dispatch_sync(backgroundQueue, ^(void){
Utilities *utilities = [[Utilities alloc] init];
[utilities calculateStartTimes:race with:managedObjectContext];
[self updateProgress:1.0];
});
}
(void)updateProgress:(double)completedPercentage {
if (completedPercentage == 1.0) {
[self goHome];
} else if ([importExchanges count] > 0) {
[progressView setProgress:completedPercentage animated:YES];
}
}
Any help is greatly appreciated.
The method dispatch_sync will block the calling thread, which I believe in your case is the main thread. So I think it is better to wrap those dispatch_sync blocks into one dispatch_async block.
As an example:
dispatch_async(backgroundQueue, ^(void){
[self saveRace];
[self updateProgress:0.2];
[self savePositions];
[self updateProgress:0.3];
[self downloadExchanges];
[self saveExchanges];
[self updateProgress:0.4];
[self downloadLegs];
[self saveLegs];
[self updateProgress:0.5];
Utilities *utilities = [[Utilities alloc] init];
[utilities calculateStartTimes:race with:managedObjectContext];
[self updateProgress:1.0];
});
After that you can wrap the updating of the progressView to be done in main thread because it is a UI update.
- (void)updateProgress:(double)completedPercentage {
if (completedPercentage == 1.0) {
[self goHome];
} else if ([importExchanges count] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[progressView setProgress:completedPercentage animated:YES];
});
}
}

Use core data entity in a countdown timer

I have 2 entities in core data to create countdown timers. Timer has an attribute called timerName and entity Blinds(changed from 'Times') has an attribute called duration.
Entities called
Timer <---->> Blind
and attributes called
timerName <---->> duration
with relationships called
blinds <---->>timer
I need to place the various durations into a countdown timer one at a time. When the first duration reaches 0 the next duration is fetched from core data and that is counted down to zero etc.
I am very new to Objective-C and core data but I know I need a loop and fetch request but don't know where to start. Any code examples would be appreciated. Thanks
EDIT
I have setup a fetchrequest in my model.m
- (NSFetchedResultsController *)frc_newTimer
{
if (_frc_newTimer) return _frc_newTimer;
// Otherwise, create a new frc, and set it as the property (and return it below)
_frc_newTimer = [_cdStack frcWithEntityNamed:#"Timer"
withPredicateFormat:nil
predicateObject:nil
sortDescriptors:#"timerName,YES"
andSectionNameKeyPath:nil];
return _frc_newTimer;
}
Then in my view controller.h
#import <UIKit/UIKit.h>
#import "Timer.h"
#import "Blind.h"
#interface BlindTimerViewController : UIViewController <NSFetchedResultsControllerDelegate>
{
IBOutlet UILabel *lblCountDown;
NSTimer *countdownTimer;
int secondsCount;
}
- (IBAction)StartTimer:(id)sender;
- (IBAction)ResetTimer:(id)sender;
#property (assign, nonatomic) NSInteger currentTimeIndex;
#property (nonatomic, strong) Model *model;
#property (nonatomic, strong) Timer *myTimer;
#end
then in view controller.m
#interface BlindTimerViewController ()
#end
#implementation BlindTimerViewController
#synthesize model = _model;
and
-(void) timerRun
{
secondsCount = secondsCount -1;
int minutes = secondsCount / 60;
int seconds = secondsCount - (minutes * 60);
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%.2d", minutes, seconds];
lblCountDown.text = timerOutput;
//need to add a label for the next blind in the coredata list and update it while in a loop......
if (secondsCount == 0) {
[countdownTimer invalidate];
countdownTimer = nil;
}
}
-(void) setTimer{
// Configure and load the fetched results controller
self.model.frc_newTimer.delegate = self;
self.model.frc_newTimer.fetchRequest.predicate = [NSPredicate predicateWithFormat:#"timerName LIKE %#", #"Sample Timer"];
//add code to get the first coredata item in the blinds list
secondsCount = 240; // i need to insert the CoreData Blinds HERE
countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerRun) userInfo:nil repeats:YES];
}
and buttons (yet to be fully sorted) to start actions
- (IBAction)StartTimer:(id)sender
{
[self setTimer];
}
- (IBAction)ResetTimer:(id)sender {
[countdownTimer invalidate];
countdownTimer = nil;
secondsCount = 0;
lblCountDown.text = #"00:00";
}
I'm assuming that you're running the countdown for a known Timer. In this case you don't need a fetch request as you have a relationship from the Timer to its set of Times, we can access it directly:
NSSet *times = self.myTimer.times;
We want to sort it so you can run the durations in some order:
(you might also want to check that the count of times > 0)
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"duration" ascending:YES];
NSArray *orderedTimes = [times sortedArrayUsingDescriptors:#[ sortDescriptor ]];
Next, we're going to need an instance variable to track where we are:
#property (assign, nonatomic) NSInteger currentTimeIndex;
With these parts, you can manage the process, and use an NSTimer to actually do the work. When the timer fires you go back to the time, get and sort the times, increment the index we're using, check the index is in range, get the duration and start the timer.
I'm going to be cheeky and say that if the expiring timer is nil, that means we're starting the process from scratch (it would be better to take the first case out into a specific method):
- (void)timerFired:(NSTimer *)expiringTimer
{
[expiringTimer invalidate];
NSInteger index = (expiringTimer != nil ? (self.currentTimeIndex + 1) : 0);
NSSet *times = self.myTimer.times;
if (times.count < index) {
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"duration" ascending:YES];
NSArray *orderedTimes = [times sortedArrayUsingDescriptors:#[ sortDescriptor ]];
double duration = [[[orderedTimes objectAtIndex:index] duration] doubleValue];
[NSTimer scheduledTimerWithTimeInterval:duration target:self selector:#selector(timerFired:) userInfo:nil repeats:NO];
} else {
// deal with the error
}
}
Now you can start the countdown with [self timerFired:nil];
You haven't said what you're doing while the timers are running, that could change things quite a bit (like you want to display an update of the time on screen each second)...
If you need to fetch the timer from your Core Data DB, that's where the fetch request comes in:
NSManagedObjectContext *context = <#Managed object context#>;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Timer"];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"timerName LIKE %#", #"Sample Timer"]];
NSArray *timers = [context executeFetchRequest:fetchRequest error:nil]; // should really add the error...
Timer *myTimer = nil;
if (timers.count == 1) {
myTimer = [timers lastObject];
} else {
// we didn't find the timer, agh!
}

Problems with iOS 6 and UIActivityIndicator in a UINavigationBar titleView

been having problems with this since iOS 6. I can't really figure out whats changed that would cause this behavior. This used to work perfectly in 5. Now the activity indicator doesn't come up timely or at all. Any help would be much appreciated.
-(void)myMethod
{
UIView *currentTitleView = [[self navigationItem] titleView];
// Create an activity indicator and start it spinning in the nav bar
UIActivityIndicatorView *aiview = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[[self navigationItem] setTitleView:aiview];
[aiview startAnimating];
// Start of Block code
void (^block)(arg1, arg2) =
^(arg1, arg2)
{
block code;
[aiview stopAnimating];
[[self navigationItem] setTitleView:currentTitleView];
};
// End of Block code
}
It sounds like myMethod is being called from a background thread. As a general rule, all interactions with UIKit elements (which includes UIActivityIndicatorView) should always be done on the main thread. Try using GCD to move the UI code to the main queue (a.k.a. main thread).
-(void)myMethod {
dispatch_async(dispatch_get_main_queue(), ^{
UIView *currentTitleView = [[self navigationItem] titleView];
// Create an activity indicator and start it spinning in the nav bar
UIActivityIndicatorView *aiview = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[[self navigationItem] setTitleView:aiview];
[aiview startAnimating];
});
// Start of Block code
void (^block)(arg1, arg2) =
^(arg1, arg2)
{
block code;
dispatch_async(dispatch_get_main_queue(), ^{
[aiview stopAnimating];
[[self navigationItem] setTitleView:currentTitleView];
});
};
// End of Block code
}

NSFetchedResultsController delegate methods called infinite number of times upon insertNewObjectForEntityForName:inManagedObjectContext:

[EDIT] I am changing this to more concisely explain what my problem was, after having more precisely pinpointed the issue.
I am working on core data for my app, but am stumped. It hangs in this method every time. No crashes, no logs, no nothing. It just hangs.
- (void)insertNewObject:(id)sender
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
Section *newSection = (Section *)newObject;
newSection.title = #"inserted";
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
I discovered that if I put NSLogs in these two delegate methods:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
they just keep getting called infinite number of times.
Ok, I figured it out. I was creating an infinite loop.
This delegate method gets called:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
Then this eventually gets called because I called [self.tableView beginUpdates]; in the delegate method.
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Section *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
object.title = [NSString stringWithFormat:#"Chapter %i", indexPath.row];
cell.textLabel.text = object.title;
}
Then this delegate method:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
The the problem is that I was actually changing the NSManagedObject's attributes while it was updating the content
object.title = [NSString stringWithFormat:#"Chapter %i", indexPath.row];
this caused controllerWillChangeContent: to be called once again creating a loop that just goes round and round.

CoreData leaks with fetched results arrays

I'm using CoreData to store objects like cars, trips, data recorded from GPS, etc.
When I fetch what I want to show a list of trips, some stats for a trip, or add a new car in my settings view controller, I use pretty much this kind of request:
- (void)getDataTrip
{
// Fetched data trips.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"DataTrip" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Set predicate and sort orderings...
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timestamp" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"idTrip = %#", self.idTrip];
[fetchRequest setPredicate:predicate];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"failed with error: %#", error);
}
// Set the array.
[self setDataTripArray:mutableFetchResults];
// Memory management.
[fetchRequest release];
[mutableFetchResults release];
}
Sometimes, I have leaks when I do the [self setDataTripArray:mutableFetchResults]; and sometimes not. In this case, when I get the data for a trip, it leaks all the time when I use the navigation controller to come back to the root view controller (displaying a list of trips), and/or when I change tab.
Anyway, it just leaks and it's all the time coming from fetching data from CoreData, and give this array to my local array var.
Please let me know if you see how to fix this! It made the app crash after a while.
Thanks!
SOLUTION
I found that I do a retain on my object dataTripArray object when creating another UIViewController that I use to create graphs for my scroll view.
- (void)loadScrollViewWithPage:(int)page
{
if (page < 0)
return;
if (page >= kNumberOfPages)
return;
// Replace the placeholder if necessary.
GraphController *controller = [self.graphControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{
controller = [[GraphController alloc] initWithPageNumber:page data:[self.dataTripArray retain]];
[self.graphControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// Add the controller's view to the scroll view.
if (controller.view.superview == nil)
{
CGRect frame = _scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[self.scrollView addSubview:controller.view];
}
}
I just removed the retain and the leak is no longer coming up. Fixed!

Resources