Dear community.
I have NSOperation subclass with property:
#property(readwrite, getter=isCancelled) BOOL cancelled;
from where i was create object of subclass NSObject (from init):
database = [[[MySQLIXC alloc] initWithQuene:iQuene andCarrier:startForCarrier withState:cancelled] retain];
In this custom object i try to declare local iVar:
#interface MySQLIXC : NSObject {
BOOL _currentQueueStatus;
In init:
- (id)initWithQuene:(NSUInteger)quene andCarrier:(NSString *)carrierName withState:(BOOL)currentQueueStatus;
_currentQueueStatus = currentQueueStatus;
But currentQueueStatus allways null.
Can somebody suggest a problem location?
1) you should avoid re-declaring implemented subclass interface for your additions (e.g. -[NSOperation isCancelled] exists)
2) it is very unusual to start with two retain counts:
database = [[MySQLIXC alloc] initWithQuene:iQuene andCarrier:startForCarrier withState:cancelled];
[otherThingThatHoldsAReference setDatabase:database];
instead of:
database = [[[MySQLIXC alloc] initWithQuene:iQuene andCarrier:startForCarrier withState:cancelled] retain];
3) _currentQueueStatus is not null it's a BOOL, which is a signed char. it should be 0 (NO) or 1 (YES).
4) what is currentQueueStatus? more code would help you receive more specific answers.
EDIT: updated for clarification in response to comments
/* things would look different if you subclass MONOperation */
#interface MONOperation : NSOperation
{
#private
MySQLIXC * sqlIxc;
BOOL didCancelDatabaseRequest;
}
/*
do not use isCancelled for your name - NSOperation declares this method, and
its implementation is well defined. this would override the implementation
and likely cause runtime errors.
specifically, NSOperation/NSOperationQueue uses cancel and isCancelled in its
interface and state. if you must cancel, then call cancel from cancelDatabaseRequest.
you may override cancel, but call [super cancel] in your implementation.
*/
#property (readonly) BOOL didCancelDatabaseRequest;
#property (retain) MySQLIXC * sqlIxc;
#end
#implementation MONOperation
/* ... */
- (BOOL)didCancelDatabaseRequest
{
return didCancelDatabaseRequest;
}
- (void)cancelDatabaseRequest /* in this example, there is no sense making cancelDatabaseRequest publicly visible */
{
/* for example */
assert(self.sqlIxc);
self.sqlIxc = nil;
didCancelDatabaseRequest = YES;
[self cancel]; /* do this rather than overriding NSOperation's cancellation interfaces in your subclass */
}
- (void)main
{
NSAutoreleasePool * pool = [NSAutoreleasePool new];
assert(self.sqlIxc);
self.sqlIxc.doStuff;
if (self.sqlIxc.didTimeout || self.sqlIxc.couldNotAccessFile) {
[self cancelDatabaseRequest];
}
else {
/* use the result */
}
[pool release];
}
#end
Related
i dont know what is wrong with code below
#import "ViewController.h"
#interface ViewController ()
{
NSMutableArray * buttons;
NSMutableArray * centers;
int counter;
int index;
}
#end
#implementation ViewController
-(void)viewDidLoad
{
[super viewDidLoad];
buttons = [NSMutableArray new];
centers = [NSMutableArray new];//error here expected identifier or '('
}
//other methods
#end
i am getting two arrays, xcode works normally for buttons but it gives error for centers when creating. What may be the problem?
note: i tried deleting derived data.
Okay, I know this isn't the question you asked, but I would like to suggest an alternative to instantiating variables in viewDidLoad. It's something called "lazy instantiation", and it looks like this:
- (NSMutableArray *)buttons
{
if (!_buttons) _buttons = [NSMutableArray new];
return _buttons;
}
When you need to reset the array, just set it to nil. It won't reallocate the memory until you call it again.
P.S. Whatever you do, don't call self.buttons within that method, or you will create an infinite loop.
I want to add a KVO observation that removes itself after it fires once. I have seen lots of folks on StackOverflow doing stuff like this:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"myKeyPath"])
{
NSLog(#"Do stuff...");
[object removeObserver:self forKeyPath:#"isFinished"];
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
This seems plausible, but I'm aware that calling -removeObserver:forKeyPath: from within -observeValueForKeyPath:... can be lead to non-deterministic crashes that are hard to debug. I also want to be sure this observation only gets called once (or not at all if the notification is never sent). What's a good way to do this?
I'm answering my own question here because I've seen the pattern in the question all over the place, but haven't had a reference to a good example of a better way. I've lost days, if not weeks, of my life to debugging problems ultimately found to be caused by adding and removing observers during the delivery of KVO notifications. Without warranty, I present the following implementation of a one-shot KVO notification that should avoid the problems that come from calling -addObserver:... and -removeObserver:... from inside -observeValueForKeyPath:.... The code:
NSObject+KVOOneShot.h:
typedef void (^KVOOneShotObserverBlock)(NSString* keyPath, id object, NSDictionary* change, void* context);
#interface NSObject (KVOOneShot)
- (void)addKVOOneShotObserverForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context block: (KVOOneShotObserverBlock)block;
#end
NSObject+KVOOneShot.m: (Compile with -fno-objc-arc so we can be explicit about retain/releases)
#import "NSObject+KVOOneShot.h"
#import <libkern/OSAtomic.h>
#import <objc/runtime.h>
#interface KVOOneShotObserver : NSObject
- (instancetype)initWithBlock: (KVOOneShotObserverBlock)block;
#end
#implementation NSObject (KVOOneShot)
- (void)addKVOOneShotObserverForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context block: (KVOOneShotObserverBlock)block
{
if (!block || !keyPath)
return;
KVOOneShotObserver* observer = nil;
#try
{
observer = [[KVOOneShotObserver alloc] initWithBlock: block];
// Tie the observer's lifetime to the object it's observing...
objc_setAssociatedObject(self, observer, observer, OBJC_ASSOCIATION_RETAIN);
// Add the observation...
[self addObserver: observer forKeyPath: keyPath options: options context: context];
}
#finally
{
// Make sure we release our hold on the observer, even if something goes wrong above. Probably paranoid of me.
[observer release];
}
}
#end
#implementation KVOOneShotObserver
{
void * volatile _block;
}
- (instancetype)initWithBlock: (KVOOneShotObserverBlock)block
{
if (self = [super init])
{
_block = [block copy];
}
return self;
}
- (void)dealloc
{
[(id)_block release];
[super dealloc];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
KVOOneShotObserverBlock block = (KVOOneShotObserverBlock)_block;
// Get the block atomically, so it can only ever be executed once.
if (block && OSAtomicCompareAndSwapPtrBarrier(block, NULL, &self->_block))
{
// Do it.
#try
{
block(keyPath, object, change, context);
}
#finally
{
// Release it.
[block release];
// Remove the observation whenever...
// Note: This can potentially extend the lifetime of the observer until the observation is removed.
dispatch_async(dispatch_get_main_queue(), ^{
[object removeObserver: self forKeyPath: keyPath context: context];
});
// Don't keep us alive any longer than necessary...
objc_setAssociatedObject(object, self, nil, OBJC_ASSOCIATION_RETAIN);
}
}
}
#end
The only potential hitch here is that the dispatch_async deferred removal may marginally extend the lifetime of the observed object by one pass of the main run loop. This shouldn't be a big deal in the common case, but it's worth mentioning. My initial thought was to remove the observation in dealloc, but my understanding is that we don't have a strong guarantee that the observed object will still be alive when the -dealloc of KVOOneShotObserver is called. Logically, that should be the case, since the observed object will have the only "seen" retain, but since we pass this object into API whose implementation we can't see, we can't be completely sure. Given that, this feels like the safest way.
I am trying to save a one-to-many relation in core data. There is a user decision involved to determine whether the new child list object needs to be attached to a new parent object. In the other case an existing database entry is used as a parent object. Under certain circumstances after saving, the app crashes.
FINAL EDIT: Sorry if you mind me keeping all of the edits, I still will. The process of enlightenment was quite convoluted. After all I started out thinking it was a data conflict... Thanks again to Tom, who helped point me in the right direction: I am still using a relation for sorting and grouping core data entities with an NSFetchedResultsController. I have written a valid compare: method for my entity class now and so far from what I can see it is working. I am about to write an answer for my question. I will still be very grateful for any information or warnings from you concerning this!
EDIT 3: The save procedure and the user alert seem to be incidental to the problem. I have zoomed in on the NSFetchedResultsController now and on the fact that I am using a relation ('examination') as sectionNameKeyPath. I will now try to write a compare: method in a category to my Examination entity class. If that does not work either, I will have to write a comparable value into my Image entity class in addition to the relation and use that for sections. Are y'all agreed?
EDIT 1: The crash only occurs after the user has been asked whether she wants a new examination and has answered YES. The same method is also entered when there was no user prompt (when the creation of a new examination has been decided by facts (no examination existing = YES, existing examination not timed-out = NO). In these cases the error does NOT occur. It must be that the view finishes loading while the alert view is open and then the collection view and its NSFetchedResultsController join the fun.
EDIT 2: Thanks to Tom, here is the call stack. I did not think it was relevant, but the view controller displays images in a collection view with sections of images per examination descending. So both the section key and the sort descriptor of the NSFetchedResultsController are using the examination after the MOCs change notification is sent. It is not the save that crashes my app: it is the NSSortDescriptor (or, to be fair, my way to use all of this).
The code for the NSFetchedResultsController:
#pragma mark - NSFetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController
{
if (m_fetchedResultsController != nil) {
return m_fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:NSStringFromClass([Image class]) inManagedObjectContext:[[HLSModelManager currentModelManager] managedObjectContext]];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key, identical sort to section key path must be first criterion
NSSortDescriptor *examinationSortDescriptor = [[NSSortDescriptor alloc] initWithKey:kexaminationSortDescriptor ascending:NO];
NSSortDescriptor *editDateSortDescriptor = [[NSSortDescriptor alloc] initWithKey:keditDateSortDescriptor ascending:NO];
NSArray *sortDescriptors =[[NSArray alloc] initWithObjects:examinationSortDescriptor, editDateSortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[[HLSModelManager currentModelManager] managedObjectContext] sectionNameKeyPath:kSectionNameKeyPath cacheName:NSStringFromClass([Image class])];
aFetchedResultsController.delegate = self;
m_fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
HLSLoggerFatal(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return m_fetchedResultsController;
}
#pragma mark - NSFetchedResultsControllerDelegate - optional
/* Asks the delegate to return the corresponding section index entry for a given section name. Does not enable NSFetchedResultsController change tracking.
If this method isn't implemented by the delegate, the default implementation returns the capitalized first letter of the section name (seee NSFetchedResultsController sectionIndexTitleForSectionName:)
Only needed if a section index is used.
*/
- (NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName
{
return sectionName;
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
// In the simplest, most efficient, case, reload the table view.
[[self collectionView] reloadData];
}
/* THE OTHER DELEGATE METHODS ARE ONLY FOR UITableView! */
The code for saving examination (existing or new) and the new image:
-(BOOL)saveNewImage
{
BOOL done = NO;
// remove observer for notification after alert
[[NSNotificationCenter defaultCenter] removeObserver:self name:kExaminationTimedoutAlertDone object:nil];
Examination * currentExamination = [self getCurrentExamination];
if ([self userWantsNewExamination] == YES)
{ // if an examination was found
if (currentExamination != nil)
{ // if the found examination is not closed yet
if ([currentExamination endDate] == nil)
{ // close examination & save!
[currentExamination closeExamination];
NSError *savingError = nil;
[HLSModelManager saveCurrentModelContext:(&savingError)];
if (savingError != nil)
{
HLSLoggerFatal(#"Failed to save old, closed examination: %#, %#", savingError, [savingError userInfo]);
return NO;
}
}
}
currentExamination = nil;
}
// the examination to be saved, either new or old
Examination * theExamination = nil;
// now, whether user wants new examination or no current examination was found - new examination will be created
if (currentExamination == nil)
{
// create new examination
theExamination = [Examination insert];
if (theExamination == nil)
{
HLSLoggerFatal(#"Failed to create new examination object.");
currentExamination = nil;
return NO;
}
// set new examinations data
[theExamination setStartDate: [NSDate date]];
}
else
{
theExamination = currentExamination;
}
if (theExamination == nil)
{ // no image without examination!
HLSLoggerFatal(#"No valid examination object.");
return NO;
}
Image *newImage = [Image insert];
if (newImage != nil)
{
// get users last name from app delegate
AppDelegate * myAppDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
// set image data
[newImage setEditUser: [[myAppDelegate user] lastName]];
[newImage setEditDate: [NSDate date]];
[newImage setExamination: theExamination];
[newImage setImage: [self stillImage]];
[newImage createImageThumbnail];
// update edit data
[theExamination setEditUser: [[myAppDelegate user] lastName]];
[theExamination setEditDate: [NSDate date]];
// unnecessary! CoreData does it automatically! [theExamination addImagesObject:newImage];
//! Important: save all changes in one go!
NSError *savingError = nil;
[HLSModelManager saveCurrentModelContext:(&savingError)];
if (savingError != nil)
{
HLSLoggerFatal(#"Failed to save new image + the examination: %#, %#", savingError, [savingError userInfo]);
}
else
{
// reload data into table view
[[self collectionView] reloadData];
return YES;
}
}
else
{
HLSLoggerFatal(#"Failed to create new image object.");
return NO;
}
return done;
}
The error:
2013-05-22 17:03:48.803 MyApp[11410:907] -[Examination compare:]: unrecognized selector sent to instance 0x1e5e73b0
2013-05-22 17:03:48.809 MyApp[11410:907] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[Examination compare:]: unrecognized selector sent to instance 0x1e5e73b0 with userInfo (null)
2013-05-22 17:03:48.828 MyApp[11410:907] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Examination compare:]: unrecognized selector sent to instance 0x1e5e73b0'
And here are the entity class files, too:
//
// Examination.h
// MyApp
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class Image;
#interface Examination : NSManagedObject
#property (nonatomic, retain) NSDate * editDate;
#property (nonatomic, retain) NSString * editUser;
#property (nonatomic, retain) NSDate * endDate;
#property (nonatomic, retain) NSDate * startDate;
#property (nonatomic, retain) NSSet *images;
#end
#interface Examination (CoreDataGeneratedAccessors)
- (void)addImagesObject:(Image *)value;
- (void)removeImagesObject:(Image *)value;
- (void)addImages:(NSSet *)values;
- (void)removeImages:(NSSet *)values;
#end
//
// Examination.m
// MyApp
//
#import "Examination.h"
#import "Image.h"
#implementation Examination
#dynamic editDate;
#dynamic editUser;
#dynamic endDate;
#dynamic startDate;
#dynamic images;
#end
This error had nothing to do with the saving of data to the MOC.
Because the saving of the new image data is triggered in the prepareForSegue of the previous view controller and the user alert gives the next view controller the time to finish loading, also creating the NSFetchedResultsController and its connection to its delegate, the exception was raised in the temporary context of the save to the MOC and only after the user alert.
The NSFetchedResultsController had started listening for changes of the MOC only in this case. It seems that if it gets alerted to an MOC change it will fetch only the changes and only then it needs to compare the new data with the existing data. Further information on this would be very welcome!
Then, because I had set a sort descriptor (and also the sectionNameKeyPath) to a relation and not provided the means to sort the entity objects in my core data entity class, the NSFetchedResultsController could not continue. Looking back it seems all so easy and natural, I really become suspicious of the simplicity of my solution...
I find it interesting that it could fetch the initial data in one go, when no change interfered. After all it was using the same NSSortDescriptor. Any ideas?
This is my solution:
//
// MyCategoryExamination.m
// MyApp
//
#import "MyCategoryExamination.h"
#implementation Examination (MyCategoryExamination)
- (NSComparisonResult)compare:(Examination *)anotherExamination;
{
return [[self startDate] compare:[anotherExamination startDate]];
}
#end
Please tell me if there is something wrong with this.
So here's the deal: I have a custom UIViewController named FieldFormVC. In order to present it, I call this code:
FieldFormVC *fieldFormVC = [[FieldFormVC alloc] initWithFieldForm:(FieldForm *)[self getForm] andManagedObjectContext: managedObjectContext];
fieldFormVC.trackManager = self;
fieldFormVC.thisTrackNumber = currentScreenIndex;
fieldFormVC.textSize = textSize;
[navigationController pushViewController:fieldFormVC animated:YES];
Where [self getForm] returns a FieldForm object. The code for the initWithFieldForm: andManagedObjectContext: method is:
-(id)initWithFieldForm: (FieldForm *) f andManagedObjectContext: (NSManagedObjectContext *) moc
{
if (self = [super init])
{
fieldForm = f;
managedObjectContext = moc;
}
return self;
}
I set up some breakpoints, and when the initWithFieldForm: andManagedObjectContext: is called, the parameters "f" and "moc" contain actual values. At the end of the method, fieldFormVC has all the values it needs.
Now when it goes back to the first chunk of code and calls
fieldFormVC.trackManager = self;
All the values in fieldFormVC go to 0x00000000. All the properties of the fieldFormVC are set with #property (nonatomic, retain) and they are #synthesize'd as well.
The strange thing is that I have used similar initWith.. methods that have turned out great, and I've never seen this issue before. If it helps, I am using Core Data in my project, and FieldForm is a certain entity in my model. Thanks for any advice and help!
Update:
The getForm method's code:
-(NSObject *) getForm
{
WrapperForm *wrapperForm = (WrapperForm *)[[_fetchedResultsController fetchedObjects]objectAtIndex:currentScreenIndex.intValue];
FieldForm *fieldForm = wrapperForm.fieldForm;
PictureForm *pictureForm = wrapperForm.pictureForm;
if (fieldForm != nil)
{
NSLog(#"Class: %#", [wrapperForm.fieldForm.class description]);
return fieldForm;
}else if(pictureForm != nil)
{
NSLog(#"Class: %#", [wrapperForm.pictureForm.class description]);
return pictureForm;
}
return nil;
}
You are casting to FieldForm although your method getForm should actually return a FieldForm class anyway. Most likely the getForm method does not return the correct object.
I have an application with UITabBarController, where the first tab contains a ViewController of an HomePage. What I have to do, is to switch between the tabs (this is pretty simple: [[self tabBarController] setSelectedIndex:index]), AND to navigate through outlets of the selectedTab from the "HomePage".
Just to explain myself: TabElement1--->TabElementX---->UISegmentedController:segmentY
The problem is that the UISegmentedController is nil because it is not initialized yet (at least the first time I do the operation). How should I fix this problem? The tab elements are loaded with nibs.
EDIT-- Here's some code:
#implementation HomeViewController // Tab Indexed 0
// ...
- (void)playVideoPreview {
NSArray *array;
array = [[self tabBarController] viewControllers];
// This is a test where I programmatically select the tab AND the switch.
[[[array objectAtIndex:2] switches] setSelectedSegmentIndex:1];
[[self tabBarController] setViewControllers:array];
}
#end
#implementation TGWebViewController // Tab Indexed 2
// ...
#synthesize switches; // In .h file: #property (nonatomic, retain) IBOutlet UISegmentedControl switches; Properly linked within the XIB.
- (IBAction)switchHasChangedValue {
// Foo operations.
}
Now the first time I fire playVideoPreview I manage to get Into the Tab Indexed 2, TGWebViewController, but switches doesn't exists yet, so I find myself with the segmentedControl named "switches" with the first segment selected. If I get back to HomeViewController, then I fire again playVideoPreview, I get the correct behaviour.
I've fixed the problem using the delegates and a boolean. Now, when the loading of the ViewController at index 2 of TabBar is finished, it sends a message to its delegate which tells what segment has to be selected.
EDIT Here's the code (hope it helps):
// Method in the first View that asks to tab the tab bar to launch the other
// view controller
- (void)playVideoPreview {
NSArray *array;
array = [[self tabBarController] viewControllers];
if ( ![[array objectAtIndex:2] catSwitch] ) {
[[array objectAtIndex:2] setDelegate:self];
[[array objectAtIndex:2] setHasBeenLaunchedByDelegate:YES];
} else {
[self selectTab];
}
[[self tabBarController] setViewControllers:array];
[[self tabBarController] setSelectedIndex:2];
}
// Now the operations performed by the second View Controller
- (void)somewhereInYourCode {
if ( hasBeenLaunchedByDelegate ) {
[[self delegate] selectTab];
}
}
// In the First View Controller this is the delegate method,
// launched from the Second View Controller
- (void)selectTab {
NSArray *array;
array = [[self tabBarController] viewControllers];
[[[array objectAtIndex:2] catSwitch] setSelectedSegmentIndex:[[bannerPreview pageControl] currentPage]];
}
// Some declaration
#protocol SecondViewControllerDelegate;
class SecondViewController : ViewController {
id<TGWebViewControllerDelegate> delegate;
}
#end
#protocol SecondViewControllerDelegate
- (void)selectTab;
#end
// Meanwhile in the first view
class FirstViewController : ViewController <SecondViewControllerDelegate> {
// ...
}