Could someone help me understand the primitive accessors with this example : i don't understand what is automatically set and the order of those methods :
1.after a person is created, is willSave the first method called? (i guess so, because save: is called after we create a person with insertNewObjectForEntityForName )
2.in RootViewController (the second chunk of code), we then call the getter of eyeColor with : person.eyeColor :
a) in eyeColor, we call : [self eyeColorData] ,
b) but setPrimitiveEyeColorData is in willSave, which is accessible only if primitiveEyeColor exists,
c) but setPrimitiveEyeColor is in eyeColor and only called if [self eyeColorData] exists. So, i'm a bit confused with this code, could someone help me?
here's the code about eyeColor and eyeColorData :
#dynamic eyeColorData;
#dynamic eyeColor;
#interface AWPerson (PrimitiveAccessors)
- (UIColor *)primitiveEyeColor;
- (void)setPrimitiveEyeColor:(UIColor *)value;
- (NSData *)primitiveEyeColorData;
- (void)setPrimitiveEyeColorData:(NSData *)value;
#end
+ (id)personInManagedObjectContext:(NSManagedObjectContext *)moc {
return [NSEntityDescription
insertNewObjectForEntityForName:#"Person"
inManagedObjectContext:moc];
}
+ (id)randomPersonInManagedObjectContext:(NSManagedObjectContext *)moc {
AWPerson *randomPerson = [self personInManagedObjectContext:moc];
//...
randomPerson.eyeColor = [self randomColor]; //setter eyeColor
return randomPerson;
}
+ (UIColor *)randomColor {
static NSArray *colorsArray = nil;
if( !colorsArray ) {
colorsArray = [[NSArray alloc] initWithObjects:
[UIColor lightGrayColor],
[UIColor blueColor],
[UIColor greenColor], nil];
}
int randomIndex = arc4random() % [colorsArray count];
return [colorsArray objectAtIndex:randomIndex];
}
- (void)willSave {
UIColor *color = [self primitiveEyeColor];
if( color ) {
[self setPrimitiveEyeColorData:
[NSKeyedArchiver archivedDataWithRootObject:color]];
} else {
[self setPrimitiveEyeColorData:nil];
}
[super willSave];
}
- (UIColor *)eyeColor {
[self willAccessValueForKey:#"eyeColor"];
UIColor *tmpValue = [self primitiveEyeColor];
[self didAccessValueForKey:#"eyeColor"];
if( tmpValue ) return tmpValue;
NSData *colorData = [self eyeColorData];
if( !colorData ) return nil;
tmpValue = [NSKeyedUnarchiver unarchiveObjectWithData:colorData];
[self setPrimitiveEyeColor:tmpValue];
return tmpValue;
}
in RootViewController :
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
AWPerson *person = [[self fetchedResultsController] objectAtIndexPath:indexPath];
[cell setBackgroundColor:person.eyeColor];
}
Thanks
EDIT - Added info on willSave
To answer your first question, willSave is called whenever the object is saved (using the save method). So the first method called will be one of the class methods (used to create the object) or init and then, since you said that the object is saved just after it is created, willSave gets called.
I think the key to understanding this is to realize that eyeColor, primitiveEyeColor, and their setters are all ultimately interacting with the same variable in memory (the iVar named eyeColor). The difference is whether or not the code in the setter/getter (in this case the - (UIColor *)eyeColor { function) is called.
There are just a few different ways to interact with it:
[self primitiveEyeColor]; - This reads the value of the iVar directly.
[self setPrimitiveEyeColor:tmpValue]; - This sets the value of the iVar directly.
[self eyeColor] - This calls the - (UIColor *)eyeColor method in your class (which should ultimately retrieve the iVar or a representation of it).
[self setEyeColor:value] - This calls the - (void)setEyeColor:(UIColor *)newColor method in your class. Note that in this case it doesn't exist so it simply calls the primitive method (and does the KVO magic).
In this particular code, they are using a "non-standard persistent attribute" because NSManagedObject does not support UIColor's. Read about it here.
EDIT 2
To answer your other questions:
a) The color in randomPerson.eyeColor = [self randomColor] is
accessible with [self primitiveEyeColor] (in willSave)?
Yes, once eyeColor is set (either via the setEyeColor method or the setPrimitiveEyeColor method), you can read it from primitiveEyeColor and it will return the same value.
Note that once it is set, eyeColor and primitiveEyeColor return the same value and can be called from anywhere in your class (not just willSave).
b) So if [self primitiveEyeColor] != nil : in eyeColor, the line :
if( tmpValue ) return tmpValue; should therefore always be true...
when can we unarchive eyeColorData if UIColor *tmpValue = [self
primitiveEyeColor] is always returned in -(UIColor *)eyeColor?
This method only looks at eyeColorData (which was stored during the last call to willSave) if eyeColor is nil. This is an optimization because we could skip all of this and just unarchive eyeColorData every time if we wanted to. In this case, once a value is unarchived or set to a new value, it always stores that value and returns it so that we don't have to call unarchive again.
Also, there is really what I believe to be an error here (although it could be by design). Let's say that we perform the following steps:
Set eyeColor to a random color (let's say blue).
save the object.
Set eyeColor to nil
Now, if you check the color using [self eyeColor] it will see that primitiveEyeColor is nil and unarchive eyeColorData again, therefore returning the blue color that was stored previously. You should probably be over-riding the set function so that it sets eyeColorData to nil when eyeColor is set to nil. That way, checking the value of eyeColor after setting it to nil will return nil as expected.
Related
I'm trying to add a bindable property to a custom NSPopUpButton subclass.
I've created a "selectedKey" property, which is meant to store a NSString associated with selected menu item.
In control init, I set self as button target and an action for the button (valueChanged:), which in turn sets "selectedKey" in accordance with user selection:
#interface MyPopUpButton : NSPopUpButton {
NSMutableDictionary *_items;
NSString *_selectedKey;
}
#property(nonatomic, readwrite, copy) NSString* selectedKey;
- (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key;
#end
#implementation MyPopUpButton
- (instancetype)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
if (self) {
_items = [NSMutableDictionary new];
[NSObject exposeBinding:#"selectedKey"];
[super setTarget:self];
[super setAction:#selector(valueChanged:)];
}
return self;
}
- (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key {
[super addItemWithTitle:title];
[_items setValue:title forKey:key];
}
- (void)valueChanged:(id)sender {
for (NSString *aKey in [_items allKeys]) {
if ([[_items valueForKey:aKey] isEqualToString:[self titleOfSelectedItem]]) {
self.selectedKey = aKey;
}
}
}
- (void)setSelectedKey:(NSString *)selectedKey {
[self willChangeValueForKey:#"selectedKey"];
_selectedKey = selectedKey;
[self didChangeValueForKey:#"selectedKey"];
[self selectItemWithTitle:[_items valueForKey:selectedKey]];
}
#end
This seems to work as expected: "selectedKey" property is changed when user changes PopUpButton selection.
Unfortunately, trying to bind this property, doesn't work.
[selectButton bind:#"selectedKey" toObject:savingDictionary withKeyPath:key options:#{NSContinuouslyUpdatesValueBindingOption : #YES }]
When selection is changed bind object is not updated accordingly.
What am I doing wrong?
I've created a "selectedKey" property, which is meant to store an NSString associated with selected menu item.
Bindings is definitely the way to go here, but your use of bind:toObject:withKeyPath:options is incorrect.
The value that you pass to the first argument must be one of the predefined values made available by Apple for that particular control. For NSPopUpButton objects, the available values are documented in the NSPopUpButton Bindings Reference. When you look through this document you'll see that there is no selectedKey option. There is however a selectedValue which has the following description:
An NSString that specifies the title of the selected item in the NSPopUpButton.
Thus the correct way to set up the binding is as follows:
[self.btn bind:#"selectedValue"
toObject:self
withKeyPath:#"mySelectedString"
options:nil];
This is all you need to do: when the action selector is fired the property stored at the keyPath you passed in as the third argument will already have been updated. This means that you can (i) get rid of the setSelectedKey method entirely, (ii) remove exposeBinding line, and (iii) remove the code within valueChanged: - Cocoa has already done this bit.
The example below implements just two methods, but, if I've understood your intentions, they should be all you need:
- (void)awakeFromNib {
self.btn.target = self;
self.btn.action = #selector(popUpActivity:);
[self.btn bind:#"selectedValue"
toObject:self
withKeyPath:#"mySelectedString"
options:nil];
// I've added a couple of additional bindings here; they're
// not required, but I thought they'd be instructive.
[self.btn bind:#"content"
toObject:self
withKeyPath:#"myItems"
options:nil];
[self.btn bind:#"selectedIndex"
toObject:self
withKeyPath:#"mySelectedIndex"
options:nil];
// Now that you've set the bindings up, use them!
self.myItems = #[#"Snow", #"Falling", #"On", #"Cedars"];
self.mySelectedIndex = #3; // "Cedars" will be selected on startup
// no need to set value of mySelectedString, because it will be
// updated automatically by the selectedIndex binding.
NSLog("%#", self.mySelectedString) // -> "Cedars"
}
- (void)popUpActivity:(id)sender {
NSLog(#"value of <selectedIndex> -> %#", self.mySelectedIndex);
NSLog(#"value of <selectedString> -> %#", self.mySelectedString);
}
A final point worth making is that none of the above should be a part of an NSPopUpButton subclass. It looks like you can - and therefore should - do everything you need to do without a custom subclass of this control. In my demo-app the code above belongs to the ViewController class, you should try doing this also.
In my NSManagedObject subclass, I've written a set of custom accessor methods to expose a public CMTime property called videoDuration which is backed by an NSData attribute called videoDurationData...
- (CMTime)videoDuration
{
[self willAccessValueForKey:#"videoDuration"];
NSValue *videoDurationValue = [self primitiveVideoDuration];
[self didAccessValueForKey:#"videoDuration"];
if (nil == videoDurationValue) {
NSData *videoDurationData = [self videoDurationData];
if (nil != videoDurationData) {
videoDurationValue = [NSValue valueWithCMTimeData:videoDurationData];
[self setPrimitiveVideoDuration:videoDurationValue];
}
}
return [videoDurationValue CMTimeValue];
}
- (void)setVideoDuration:(CMTime)videoDuration
{
[self willChangeValueForKey:#"videoDuration"];
NSValue *videoDurationValue = [NSValue valueWithCMTime:videoDuration];
[self setPrimitiveVideoDuration:videoDurationValue];
[self didChangeValueForKey:#"videoDuration"];
[self setVideoDurationData:[NSData dataWithValue:videoDurationValue]];
}
- (NSValue *)primitiveVideoDuration
{
NSData *videoDurationData = [self videoDurationData];
if (nil != videoDurationData) {
NSValue *videoDurationValue = [NSValue valueWithCMTimeData:videoDurationData];
return videoDurationValue;
}
return nil;
}
- (void)setPrimitiveVideoDuration:(NSValue *)primitiveVideoDuration
{
if (nil != primitiveVideoDuration) {
NSData *videoDurationData = [NSData dataWithValue:primitiveVideoDuration];
[self setVideoDurationData:videoDurationData];
}
else {
[self setVideoDurationData:nil];
}
}
After adding in undo/redo support, my app crashes on -[NSManagedObjectContext save:] with a EXC_BAD_ACCESS memory exception. However, if I comment out the primitive accessor methods (and have the -primitiveVideoDuration method simply return nil, then everything works as expected. I'm guessing that this has something to do with CMTime not being a key-value compliant scalar structure? (see update below)
UPDATE -- 5/8/12:
This thread and the key-value coding programming guide seem to suggest that KVC now supports arbitrary struct data, which would lead me to believe that what I'm trying to do is indeed possible?
UPDATE 2 -- 5/8/12:
Evidently, by simply not implementing the primitive accessors, everything works correctly. Why this is the case is still baffling to me...
For some reason in my below code, the replies array is NSLogging the correct description, but the comment.replies array is NSLogging null.
I immediately presumed that this was due to a memory management issue within my code, but I don't believe that that is true.
Please can you tell me why this is occurring?
- (TBComment *) dictionaryToComment:(NSDictionary *)dict {
TBComment *comment = [[TBComment alloc] init];
[comment setBody:[dict objectForKey:#"body"]];
[comment setCommentID:[dict objectForKey:#"id"]];
[comment setCreated_at:[dict objectForKey:#"created_at"]];
[comment setUpdated_at:[dict objectForKey:#"updated_at"]];
[comment setUser:[self dictionaryToUser:[dict objectForKey:#"user"]]];
NSMutableArray *replies = nil;
if ([[dict allKeys] containsObject:#"replies"]) {
replies = [[NSMutableArray alloc] init];
for (NSDictionary *reply in [dict objectForKey:#"replies"]) {
NSLog(#"in");
[replies addObject:[self dictionaryToComment:reply]];
}
}
if (replies != nil) {
[comment setReplies:replies];
NSLog(#"COMMENT REPLIES = %#", comment.replies);
NSLog(#"REPLIES = %#", replies);
[replies release];
}
return [comment autorelease];
}
Console ->
2011-11-30 21:25:14.980 Timbrr[2379:f803] in
2011-11-30 21:25:14.980 Timbrr[2379:f803] COMMENT REPLIES = (null)
2011-11-30 21:25:14.980 Timbrr[2379:f803] REPLIES = (
"<TBComment: 0x68dbeb0>"
)
- (void) setReplies:(NSArray *)_replies {
hasReplies = (_replies == nil ? NO : ([_replies count] == 0 ? NO : YES));
//replies is synthesised
}
After seeing your implementation of setReplies:, I don't think you quite understand how #synthesize works.
#synthesize replies; will generate a getter and a setter for this instance variable. BUT since you're overriding it (and improperly) the synthesized setter is being tossed aside. (In fact, no setter is being created for you at all, since you wrote one yourself.)
The root issue is that in your implementation of setReplies:, you're not actually assigning the value of your replies instance variable to the parameter of the setter.
What I think you want is:
- (void) setReplies:(NSArray *)_replies {
hasReplies = (_replies == nil ? NO : ([_replies count] == 0 ? NO : YES));
// How is your ivar defined in the header file? As _replies, or replies?
if (replies != _replies) {
[replies release];
replies = [_replies retain];
}
}
I would suspect that either comment is nil (though this would require explicit nil-returning code in TBComment, which is possible, but unusual), or that -replies or -setReplies: are incorrectly implemented. Do you have custom implementations for those?
Your implementation of setReplies: never sets _replies.
The replies property is never set - when you define setReplies:, the #synthesize directive does not create any setter method.
Hi im trying to retrieve an object of a specific class from an NSMutableArray, and then add it to self: eg:
- (void) init{
_Objects = [[NSMutableArray alloc]init];
Psychicing *psy = [[Psychicing alloc]init];
[psy startPsychic];
[_Objects addObject: psy];
[psy release];
}
This creates an object of class Psychicing, then runs the [psy startPsychic] method to create the internals of the class object. Then I add the psy object to _Objects NSMutableArray.
-(void)startPsychic{
id psychicParticle = [CCParticleSystemQuad ......]; //is Synthesised with (assign)
//Other things are set here such as position, gravity, speed etc...
}
When a touch is detected on screen, I want to take the psy object from the _Objects array and add it to self: Something like this (Although this gives runtime error)
-(void) Touches.....{
for (Psychicing *psy in _Objects){
[self addChild: psy.psychicParticle];
}
}
I hope i have explained it clearly enough, if you need more clarification let me know.
So basically:
[MainClass Init] -> [Psychicing startPsychic] -> [MainClass add to array] -> [MainClass add to self]
I'm assuming the _Objects (which should be a lowercase o to follow conventions) is storing objects other than the Psychicing object and you're trying to pull just the Psychicing object out of it in the -(void)Touches... method (which also should be lowercase). If so, you could do:
for (id obj in _Objects)
{
if ([obj isMemberOfClass:[Psychicing class]])
[self addChild:obj.psychicParticle];
}
That will cause only the Psychicing objects in the array to be added as a child to self.
It looks like you do have another error though if the code you pasted in is your real code. Init should be defined as:
- (void) init{
_Objects = [[NSMutableArray alloc]init];
Psychicing *psy = [[Psychicing alloc]init];
[psy startPsychic];
[_Objects addObject: psy];
[psy release];
}
with _Objects defined as an instance variable (or property) in the class's interface. As you wrote it, it's a method variable in the init method and is leaking. So when you try to access _Objects in -touches, _Objects is most likely nil.
Okay, with the help of McCygnus I got it working, the only thing missing with a pointer to the id object:
for (id obj in _Objects){
if ([obj isMemberOfClass:[Psychicing class]]){
Psychicing *apsy = obj;
[apsy.psychicParticle setPosition:location];
[self addChild:apsy.psychicParticle];
}
}
I need to drag a reference to an NSManagedObject between two table views of my application. What's the preferred NSPasteboard type to store a reference to an NSManagedObject?
My current solution is to store the URIRepresentation of the object's NSManagedObjectID in a NSPasteboardTypeString. I suspect there's a more elegant solution out there.
There is no standard type for all model objects since your model objects and how they're handled are unique to your application. If there was one pasteboard type for all then there'd be no telling them apart. Your own custom object should have its own drag type.
Just use a string that makes sense (maybe a #define so you can find it with auto-complete in Xcode) like "MyObjectPboardType" that resolves to "com.yourcompany.yourapp.yourobjecttype".
Use NSPasteboard's -declareTypes:owner: to declare your new type, then use -setString:forType: or one of the other -set?:forType: methods to set the information for your object's type. In your case, the use of the object ID is a perfectly acceptable identifier. Just remember managed objects' object IDs change when they're new versus persisted.
If you are dragging within tables in the same application you might as well put in pasteboard the rowIndexes (indexPaths in case you are dragging from an outlineView) of the objects in the tableView (outlineView). This might as well spare you from some unneeded CoreData access if the dataSource of the tableViews are NSArrayController (NSTreeController for outlineView).
You can then easily retrieve the dragged objects when accepting the drop since the ‘info‘ object passed to both methods ‘tableView:validateDrop:proposedRow: proposedDropOperation:‘ and to ‘tableView:acceptDrop:row:dropOperation:‘ will have a reference to the tableView originating the drag under ‘draggingSource‘ key path.
Here's a simple implementation:
extern NSString *const kMyLocalDragType = #"com.whatever.localDragType";
#implementation MyArrayControllerDataSource
.
.
.
#pragma mark - NSTableViewDataSource (Drag & Drop)
+ (NSArray *)dragTypes {
// convenience method returning all class's supported dragTypes
return #[kMyLocalDragType];
}
- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard {
[pboard declareTypes:[[self class] dragTypes] owner:self];
for (NSString *aDragType in [[self class] dragTypes]) {
if (aDragType == kMyLocalDragType) {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes]; // we are supporting drag&drop of multiple items selected
[pboard setData:data forType:aDragType];
}
.
. // logic for other dragTypes
.
}
return YES;
}
- (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id<NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)dropOperation {
NSArray *dragTypes = [info draggingPasteboard] types];
for (id aDragType in dragTypes) {
if (aDragType == kMyLocalDragType) {
return NSDragOperationCopy;
}
}
.
.// Other logic for accepting drops/affect drop operation
.
}
- (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id<NSDraggingInfo>)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)dropOperation {
if ([info draggingPasteboard] types] containsObject:kMyLocalDragType]) {
// Retrieve the index set from the pasteboard:
NSData *data = [[info draggingPasteboard] dataForType:kMyLocalDragType];
NSIndexSet *rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSArray *droppedObjects = [self retrieveFromTableView:tableView objectsAtRows:rowIndexes];
// droppedObjects contains dragged and dropped objects, do what you
// need to do with them, then add them to this dataSource:
[self.content insertObjects:droppedObjects];
[tableView reloadData];
[tableView deselectAll:nil];
return YES;
}
.
. // other logic for accepting drops of other dragTypes supported.
.
}
#pragma mark - Helpers
- (NSArray <NSManagedObject *> *)retrieveFromTableView:(NSTableView *)tableView objectsAtRowIndexes:(NSIndexSet *)rowIndexes {
id dataSource = [tableView dataSource];
if ([dataSource respondsToSelector:#selector(content)]) {
if ([dataSource.content respondsToSelector:#selector(objectsAtIndexes:)]) {
return [datasource content] objectsAtIndexes:rowIndexes];
}
}
return #[]; //We return an empty array in case introspection check failed
}