Why does NSOperation disable automatic key-value observing? - key-value-observing

When working with a custom NSOperation subclass I noticed that the automatic key-value observing is disabled by the [NSOperation automaticallyNotifiesObserversForKey] class method (which returns NO at least for some key paths). Because of that the code inside of NSOperation subclasses is littered by manual calls to willChangeValueForKey: and didChange…, as visible in many code samples on the web.
Why does NSOperation do that? With automatic KVO support people could simply declare properties for the operation lifecycle flags (isExecuting etc.) and trigger the KVO events through the accessors, ie. the following code:
[self willChangeValueForKey:#"isExecuting"];
executing = NO;
[self didChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
finished = YES;
[self didChangeValueForKey:#"isFinished"];
…could be replaced by this:
[self setIsExecuting:NO];
[self setIsFinished:YES];
Is there a catch somewhere? I just overrode the automaticallyNotifiesObserversForKey to return YES and things seem to work fine.

The most likely explanation is that the kvo keys don't match the standard conventions. Normally one has methods like -isExecuting and -setExecuting:, where the key path is #"executing". In the case of NSOperation, the key path is #"isExecuting" instead.
The other possibility is that most NSOperations don't actually have a method named -setIsExecuting: to change that value. Instead, they base the executing/finished flags on other internal state. In this case, one absolutely needs to use the explicit willChange/didChange notifications. For example, if I have an NSOperation that wraps an NSURLConnection, I may have 2 ivars, one named data that holds the downloaded data, and one named connection which holds the NSURLConnection, and I may implement the getters like so:
- (BOOL)isExecuting {
return (connection != nil);
}
- (BOOL)isFinished {
return (data != nil && connection == nil);
}
Now my -start method can use
[self willChangeValueForKey:#"isExecuting"];
data = [[NSMutableData alloc] init]; // doesn't affect executing, but is used later
connection = [[NSURLConnection connectionWithRequest:request delegate:self] retain];
[self didChangeValueForKey:#"isExecuting"];
to start executing, and
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
[connection cancel];
[connection release];
connection = nil;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
to finish.

While I agree that overriding automaticallyNotifiesObserversForKey appears to work, but I personally forgo the isExecuting and isFinished properties altogether and instead define executing and finished properties, which, as Kevin suggests, is more consistent with modern conventions:
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
I then write custom setters for these two properties, which do the necessary isExecuting and isFinished notifications:
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
This yields:
a more customary BOOL property declaration;
custom setters satisfy the strange notifications that NSOperation requires; and
I can now just use the executing and finished setters throughout my operation implementation, without littering my code with notifications.
I must confess that I like the elegance of overriding automaticallyNotifiesObserversForKey, but I just worry about unintended consequences.
Note, if doing this in iOS 8 or Yosemite, you will also have to explicitly synthesize these properties in your #implementation:
#synthesize finished = _finished;
#synthesize executing = _executing;

I don't know why you are talking about NSOperation can not use automatic KVO. But I just try to verify that, therefore it can use KVO.
[self addObserver:self
forKeyPath:#"isReady"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
context:&ctxKVO_CSDownloadOperation];
[self addObserver:self
forKeyPath:#"isExecuting"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
context:&ctxKVO_CSDownloadOperation];
[self addObserver:self
forKeyPath:#"isFinished"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
context:&ctxKVO_CSDownloadOperation];
[self addObserver:self
forKeyPath:#"isCancelled"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
context:&ctxKVO_CSDownloadOperation];
...
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == &ctxKVO_CSDownloadOperation) {
NSLog(#"KVO: %#", keyPath);
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
The result:
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] isReady : 1
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] KVO: isReady
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] isExecuting : 0
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] KVO: isExecuting
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] isFinished : 0
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] KVO: isFinished
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] isCancelled : 0
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] KVO: isCancelled
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] isReady : 1
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] KVO: isReady
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] isExecuting : 0
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] KVO: isExecuting
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] isFinished : 0
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] KVO: isFinished
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] isCancelled : 0
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] KVO: isCancelled
2017-08-02 14:29:58.834 CSDownloader[77366:5089399] isReady : 1
2017-08-02 14:29:58.834 CSDownloader[77366:5089399] KVO: isReady
2017-08-02 14:29:58.834 CSDownloader[77366:5089399] isExecuting : 0
So I am really confused about this question and answers...

The NSOperationQueue is not observing isFinished or isExecuting, it is observing finished and executing.
isFinished is simply the the synthesized get accessor for the property finished. Automatic key-value observing notifications will be sent for this property unless your subclass has specifically opted out of automatic KVO notifications by implementing +automaticallyNotifiesObserversForKey or +automaticallyNotifiesObserversOf<Key> to return NO. If you have not opted out of automatic KVO notifications, you do not need to perform manual notifications using will/DidChangeValueForKey:. In your case, you were sending manual notifications for isFinished and isExecuting, which are not the key paths that NSOperationQueue observes.
TL;DR: These aren't the key paths that NSOperationQueue is looking for.
executing and finished are the correct key paths, and they should be sending automatic KVO notifications.
If you are truly paranoid about KVO and want to send notifications for the get accessor key paths such as isFinished, register your property as a dependency of the key path:
+ (NSSet *) keyPathsForValuesAffectingIsFinished {
NSSet *result = [NSSet setWithObject:#"finished"];
return result;
}

Related

Managed object created in child context not reflected in main thread

I have a moc (self.managedObjectContext) which was created with NSMainQueueConcurrencyType.
Now, for a method invoked this way -
ManagedObjectType1 *obj1 = [self createAnObject];
With the implementation for createAnObject being -
- (ManagedObjectType1 *) createAnObject {
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
childContext.parentContext = self.managedObjectContext;
ManagedObjectType1 *obj1 = //..initialize in childContext
return obj1
}
obj1 is nil after the method returns (at the place where it was invoked) and yet obj1 has data in the method implementation at the time of being returned.
What could be going wrong here. I have tried assigning childContext with NSPrivateQueueConcurrencyType but that hasn't helped either.
This worked. But is this a good way to do it.
- (ManagedObjectType1 *) createAnObject {
__block ManagedObjectType1 *obj1;
[self.managedObjectContext performBlockAndWait:^{
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
childContext.parentContext = self.managedObjectContext;
obj1 = //..initialize in childContext
}];
return obj1
}

NSFetchedResultsController: NSSortDescriptor with relation as key sends unrecognized selector compare: to core data entity object

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.

How to prevent race conditions when merging changes across threads?

A typical setup: we have a main thread with a mainMOC and a background thread with its own backgroundMOC. The background thread performs read-only operations on the backgroundMOC by dispatching blocks to a backgroundQueue.
The backgroundMOC needs to merge the changes from the mainMOC so we register for NSManagedObjectContextDidSaveNotification and then do something like
- (void)mainMocDidSave:(NSNotification *)notification {
dispatch_async(backgroundQueue, ^{
[backgroundMoc mergeChangesFromContextDidSaveNotification:notification];
});
}
Let's say the user deletes an object in the mainMOC. The code above does not seem safe to me, since the merge will be done at some point in the future. Until the merge is done, there might still be blocks on the backgroundQueue that are trying to use the deleted object.
The obvious solution would be to use dispatch_sync instead (or performBlockAndWait, performSelector:OnThread:...) instead. From the code snippets I see on the interwebs, this seems to be what everybody is doing. But I'm not comfortable with this solution either.
The name NSManagedObjectContextDidSaveNotification implies that the save has already happened when the notification is delivered. So the corresponding row has already been deleted from the underlying database (assuming an sqlite store). dispatch_sync will have to wait for other blocks on the queue to finish before it can merge the changes, and these other blocks could still try to work with the deleted object, leading to an NSObjectInaccessibleException.
It seems to me that the correct way to merge changes from one thread/queue to another would be to
Subscribe to NSManagedObjectContextWillSaveNotification and NSManagedObjectContextDidSaveNotification on the background thread.
On NSManagedObjectContextWillSaveNotification: empty the backgroundQueue and suspend any operations that dispatch new blocks to the queue.
On NSManagedObjectContextDidSaveNotification: merge the changes synchronously.
Resume normal operation on the background queue.
Is this the correct approach or am I missing something?
I use the following structure in two projects in which I experienced similar troubles as you did. First of all I use singleton service to ensure there is only one background thread merging and reading changes.
AppDelegate.m
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
// It is crucial to use the correct concurrency type!
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
- (void)saveContext {
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
else {
[[NSNotificationCenter defaultCenter] postNotificationName:#"ParentContextDidSaveNotification" object:nil];
}
}
}
BackgroundService.m
- (id)init {
self = [super init];
if (self) {
[self managedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(parentContextDidSave) name:#"ParentContextDidSaveNotification" object:nil];
}
return self;
}
- (NSManagedObjectContext *)managedObjectContext {
if (!_managedObjectContext) {
// Again, make sure you use the correct concurrency type!
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_managedObjectContext setParentContext:[(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]];
}
return _managedObjectContext;
}
- (BOOL)saveContext {
#synchronized(self) {
BOOL successful = YES;
// Bad practice, process errors appropriately.
[[self managedObjectContext] save:nil];
[[[self managedObjectContext] parentContext] performBlock:^{
[(AppDelegate *)[[UIApplication sharedApplication] delegate] saveContext];
}];
return successful;
}
}
- (void)parentContextDidSave {
[[self managedObjectContext] reset];
[[NSNotificationCenter defaultCenter] postNotificationName:#"ManagedObjectContextResetNotification" object:nil];
}

Core Data Transient Calculated Attributes

I have an entity that contains lastName and firstName attributes. For reasons beyond the scope of this question, I want a fullName attribute that gets calculated as a concatenation of firstName + space + lastName.
Because this is purely a calculated value, with no need for redo/undo or any other of the more sophisticated aspects of transient attributes (merging, etc.), my gut tells me to just override the getter method to return said calculated value. Reading suggests that, if I do this, my only concern would be whether it's KVO compliant, which I can address by using keyPathsForValuesAffectingVolume to ensure changes to firstName or lastName trigger notifications for anyone observing on fullName.
Am I missing anything? I'm checking because I'm a beginner to this environment.
I'm also new to this, so I'm not completely sure about my answer, but as I understand it you are correct.
- (NSString *)fullName
{
[self willAccessValueForKey:#"fullName"];
NSString *tmp = [self primitiveFullName];
[self didAccessValueForKey:#"fullName"];
if (!tmp) {
tmp = [NSString stringWithFormat:#"%# %#", [self firstName], [self lastName]];
[self setPrimitiveFullName:tmp];
}
return tmp;
}
- (void)setFirstName:(NSString *)aFirstName
{
[self willChangeValueForKey:#"firstName"];
[self setPrimitiveFirstName:aFirstName];
[self didChangeValueForKey:#"firstName"];
[self setPrimitiveFullName:nil];
}
- (void)setLastName:(NSString *)aLastName
{
[self willChangeValueForKey:#"lastName"];
[self setPrimitiveLastName:aLastName];
[self didChangeValueForKey:#"lastName"];
[self setPrimitiveFullName:nil];
}
+ (NSSet *)keyPathsForValuesAffectingFullName
{
return [NSSet setWithObjects:#"firstName", #"lastName", nil];
}

access to property of another class from NSOperation object

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

Resources