Difference between mutableArrayValueForKey and calling insertObject:inEmployeesAtIndex: directly - cocoa

I have a question regarding using KVO-compliant methods to insert/remove objects from an array. I'm working through Aaron Hillegass' Cocoa Programming for Mac OS X and I saw the following line of code (in the insertObject:inEmployeesAtIndex: method:
[[undoManager prepareWithInvocationTarget:self] removeObjectFromEmployeesAtIndex:index];
Correct me if I'm wrong, but I always thought it was better to call mutableArrayValueForKey: and then removeObjectAtIndex:...so I tried changing the above line to this:
[[undoManager prepareWithInvocationTarget:[self mutableArrayValueForKey:#"employees"]] removeObjectAtIndex:index];
And it didn't work. Can someone explain the difference and why the first line works but the second line doesn't?
UPDATE: My removeObjectFromEmployeesAtIndex:index method is implemented to make my collection class (an instance of NSMutableArray) KVC-compliant. So ultimately, calling [[self mutableArrayValueForKey:#"employees"] removeObjectAtIndex:index]; should end up calling [self removeObjectFromEmployeesAtIndex:index];

In your update you say:
calling [[self mutableArrayValueForKey:#"employees"] removeObjectAtIndex:index]; should end up calling [self removeObjectFromEmployeesAtIndex:index];
Unfortunately this is not correct not matter what is in your removeObjectFromEmployeesAtIndex: method as NSMutableArray will never call any methods in your class. Since you seem to be trying to get undo/redo functionality you have to use a method like removeObjectFromEmployeesAtIndex:. Otherwise when you hit undo for adding an employee you will have no way to 'redo' adding that employee. You also could have issues with undo/redo for edits to individual employees. If you wanted to you could change the line in the removeObjectFromEmployeesAtIndex: method that reads [employees removeObjectAtIndex:index]; to [[self valueForKey:#"employees"] removeObjectAtIndex:index]; or [self.employees removeObjectAtIndex:index]; but there is really no reason to go this route.

Yes. The first line (from the book) is basically equivalent to this:
id tmp = [undoManager prepareWithInvocationTarget:self];
[tmp removeObejctFromEmployeesAtIndex:index];
Your code, however, is basically equivalent to this:
id tmp1 = [self mutableArrayValueForKey:#"employees"];
id tmp2 = [undoManager prepareWithInvocationTarget:tmp1];
[tmp2 removeObjectAtIndex:index];
In other words, the target that you're preparing the invocation with is different in your code (unless self happens to be the same object as [self mutableArrayValueForKey:#"employees"], which is doubtful).

Related

Cocoa - calling method with multiple arguments

I'm a beginner on Cocoa and I have question about calling method with multiple arguments. I'm writing some data into bluetooth (in sync method) and wait for reply. On another method I'm trying to check that new data was received in buffer but I don't know how I can call this method. I tried many different methods and I can't :(
This method which I try to call is:
- (void) odczyt:(IOBluetoothRFCOMMChannel *)rfcommChannel data:(void *)dataPointer length:(size_t)dataLength
{
unsigned char *dataAsBytes = (unsigned char *)dataPointer;
while ( dataLength-- )
{
[self addThisByteToTheLogs:*dataAsBytes];
dataAsBytes++;
}
}
How I should call this method? This method is responsible for reading incoming string and rewriting it to a label. Without calling this method when the button is pressed (and request for data is sent to BT) I'm not able read the incoming data - the label is empty and works after next pushing the button (when the method is exited and the main thread is initialized).
Ok - because its the same class I tried to use:
[self odczyt:IOBluetoothRFCOMMChannel *) data:(void *) length:(size_t)];
but Im getting an error - expected expression!
What I did wrong?
I think you should relook at what you want to do.
More to the point, I would post more general questions in the future.
From your title, you wanna know how to call a method with multiple prameters.
If this is correct, then:
[self someMethod:pram1 whichAlsoTake:pram2 andHasLogginStat:pram3] // if the method is part of your current class
or
[SomeClass someMethod:pram1 whichAlsoTake:pram2 andHasLogginStat:pram3] //if part of another class and its public
or
SomeClass *somethingFromAClass = [[SomeClass alloc] init];
[somethingFromAClass someMethod:pram1 whichAlsoTake:pram2 andHasLogginStat:pram3] // on an instance of a class
Hope that helps

What issues could arise when using GCD dispatchAfter() in this use case

I'm going through a book on OS X programing as a refresher and have a document app set up with an array controller, tableView etc. The chapter calls for implementing undo support by hand using NSInvocation. In the chapter, they call for adding a create employee method and manually, adding outlets to the NSArrayController, and connecting my add button to the new method instead of the array controller.
Instead I did this with my method for inserting new objects:
-(void)insertObject:(Person *)object inEmployeesAtIndex:(NSUInteger)index {
NSUndoManager* undoManager = [self undoManager];
[[undoManager prepareWithInvocationTarget:self]removeObjectFromEmployeesAtIndex:index];
if (![undoManager isUndoing]) {
[undoManager setActionName:#"Add Person"];
}
[self startObservingPerson:object];
[[self employees]insertObject:object atIndex:index];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// Wait then start editing
[[self tableView]editColumn:0 row:index withEvent:nil select:YES];
});
}
This works ok (looks a bit silly), but I was wondering the what issues could arise from this. I've done this elsewhere in order to execute code after an animation finished (couldn't figure out a better way).
Thanks in advance.
Why are you delaying the invocation of -editColumn:row:withEvent:select:?
Anyway, the risks are that something else will be done between the end of this -insertObject:... method and when the dispatched task executes. Perhaps something that will change the contents of the table view such that index no longer refers to the just-added employee.

Handler blocks and NSArrays - how to add to an array in a block?

I want to add an NSObject to an NSMutableArray inside of a a block. In the code below, the NSLog line works fine and returns the number I expect. However, when I try to add that result to an NSArray, the array is always empty when I go to access it later.
CMStepQueryHandler stepQueryHandler = ^(NSInteger numberOfSteps,
NSError *error) {
NSLog(#"CMStepQueryHandler: Steps on day: %i", (int)numberOfSteps);
[stepsPerDay addObject:[NSNumber numberWithInt:numberOfSteps]];
};
How can I add an object to an NSMutableArray (in this case stepsPerDay) from inside of a block so that I can access it later?
The code looks fine.
I suspect you forgot to initialize stepsPerDay. It should be
NSMutableArray *stepsPerDay = [NSMutableArray array];
As a non-related advice, you may also consider a more modern syntax
[stepsPerDay addObject:#(numberOfSteps)];
The problem was that the block would always execute the addObject after I had tried to work with the contents of the array, I guess due to the fact that the block was executed asynchronously. The solution was to have the block call a function which worked with the array and that way I could guarantee the object would deb added to the array when the function was executed.

How to avoid copy and pasting?

I'd like to improve this method if possible: this is a small section whereby all of the textfield (eyepiece, objectivelenses etc) texts are saved. Unfortunately, having to do this lots of times for each part of my app is prone to error so I would like to improve it. I'm thinking some sort of fast enumeration with arguments for the method being the textfields etc. and I can have all the keys in a dictionary (which is already set up). Just a pointer to the right docs or, perhaps, some sort of process that has worked for you would be fantastic!
-(IBAction)saveUserEntries {
if (eyepiece.text != nil) {
eyepieceString = [[NSString alloc] initWithFormat:eyepiece.text];
[eyepiece setText:eyepieceString];
NSUserDefaults *eyepieceDefault = [NSUserDefaults standardUserDefaults];
[eyepieceDefault setObject:eyepieceString forKey:#"eyepieceKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"eyepieceKey"];
}
if (objectiveLenses.text != nil) {
objectiveLensString = [[NSString alloc] initWithFormat:objectiveLenses.text];
[objectiveLenses setText:objectiveLensString];
NSUserDefaults *objectiveDefault = [NSUserDefaults standardUserDefaults];
[objectiveDefault setObject:objectiveLensString forKey:#"objectiveKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"objectiveKey"];
}
Thank you for taking the time to read this!
I will attempt to answer this question based on a OOP solution.
Create a method that accepts whatever type object these textboxes are as an argument, send the reference of said object to the method, and save the entry in a similar method you do know. This will avoid the "copy and paste" errors you are worried about.
You should be able to loop through every instance of said object that exists, if a cocoa application, works like similar to Java and .NET ( I really don't know ). I just know there must be a way to loop through every instance of a single object within the application domain.
If this was .NET I simply would suggest TextBox.Name and TextBox.String to make this a generic method that could be used to save the properties of any TextBox sent to it. If this doesn't anwer your question ( was a little long for a comment ) then I aplogize.

How to subclass AtlasSpriteManager in Cocos2d?

I need to create some compound sprites that will all move and rotate together. Since it's possible to change the position and rotation of an AtlasSpriteManager I've been trying to subclass so I can create a bunch of shortcuts like
CompoundSprite *cSprite = [CompoundSprite spriteManagerWithFile:#"sprites.png"];
[cSprite makeComplexSprite];
internally, it looks a little like this
-(void)makeComplexSprite
{
AtlasSprite *sp1 = [[AtlasSprite spriteWithRect:CGRectMake(0, 0, 64, 64)
spriteManager:self] retain];
AtlasSprite *sp2 = [[AtlasSprite spriteWithRect:CGRectMake(0, 0, 64, 64)
spriteManager:self] retain];
[self addChild:sp1];
[self addChild:sp2];
[sp1 setPosition:CGPointMake(0,0)];
[sp2 setPosition:CGPointMake(64,0)];
}
However, when I run the applications, It crashes with the following exception
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -[AtlasSpriteManager makeComplexSprite]: unrecognized selector sent to
instance 0x107e1c0
Also, if I remove all the code inside 'MakeComplexSprite' and make it do nothing, I also get the same problem.
It's looking like AtlasSpriteManager just doesn't like to be sub classed. Is this the case? If so, why, and how could I work around it?
UPDATE:
I've found a workaround, by creating an NSObject that contains an atlasSpriteManager. It does the trick, but I would still like to subclass AtlasSpriteManager if possible. I appear to be implementing this exaclty as you describe. I'm creating an instance like this
CompoundSprite *cSprite = [CompoundSprite spriteManagerWithFile:#"file.png"];
[cSprite makeBox];
which... now I think about it, means that cSprite is still an AtlasSpriteManager since that's what is being returned. hmmmm. Ho do I change that?
Implement your own spriteManagerWithFile: or compoundSpriteWithFile: in CompoundSprite, which will return an instance of CompoundSprite.
Edit:
Or, you can do something like
[[ComplexSprite alloc] makeComplexSprite];
But then you need to do the 'spriteManagerWithFile:' part also. Like:
-(id)makeComplexSpriteWithFile:(NSString*)file
{
if (! (self = [super initWithSpriteManager:..capacity:..]))
return nil;
// do your ComplexSprite specific initializing here..
return self;
}
The runtime error you are seeing indicates that your program has tried to send the makeComplexSprite message to an object, but no such method has been defined for that object.
You appear to be sending the makeComplexSprite message to an instance of AtlasSpriteManager instead of an instance of your custom CompoundSprite class. Your example code looks correct, so how are you doing the subclassing? It should look something like this:
CompoundSprite.h:
#interface CompoundSprite : AtlasSpriteManager
{
}
- (void)makeComplexSprite;
#end
CompoundSprite.m:
#interface CompoundSprite
- (void)makeComplexSprite
{
...
}
#end
If you do have the subclassing set up properly, make sure you are actually calling makeComplexSprite on an instance of CompoundSprite and not some other object by accident.
Also:
Your code sample has a memory leak. You are creating two autoreleased sprites, then retaining them (which means your class takes ownership of them), and never releasing them. Since the AddChild: method will automatically retain the objects, you can simply lose the retain calls, and everything will be good.

Resources