I have following situation in my project (in which I use Core Data): I have an entity which has two BOOL properties: isCompleted and isNonVisit. It also has third property: NSNumber *status - the value of the property depends on both isCompleted and isNonVisit values.
When either of the BOOL property changes, I want status property to be actualised automatically.
All three properties must be present in underlying database, since I use fetchedResultsController that makes use of status property (as sort descriptor and as sectionNameKeyPath).
I came up with following solution:
in .h file:
#property (nonatomic, retain) NSNumber *isCompleted;
#property (nonatomic, retain) NSNumber *isNonVisit;
#property (nonatomic, retain) NSNumber *status;
- (NSNumber *)calculateStatus; //Returns proper status value based on isCompleted and nonVisit property values.
in .m file:
#dynamic isCompleted;
#dynamic isNonVisit;
#dynamic status;
- (void)setIsCompleted:(NSNumber *)newValue
{
[self willChangeValueForKey:#"isCompleted"];
[self setPrimitiveValue:newValue forKey:#"isCompleted"];
[self didChangeValueForKey:#"isCompleted"];
self.status = [self calculateStatus];
}
- (void)setIsNonVisit:(NSNumber *)newValue
{
[self willChangeValueForKey:#"isNonVisit"];
[self setPrimitiveValue:newValue forKey:#"isNonVisit"];
[self didChangeValueForKey:#"isNonVisit"];
self.status = [self calculateStatus];
}
The solution seems to work.
So, my question is: Is it OK? Am I violating some rules of CoreData or KVO?
Thanks for any suggestions.
Your method seems sound.
The only suggestion I would have is to reduce the redundancy by extracting the boolean information from the status with accessor methods rather than storing them. You still should be able to use the desired predicates for your fetch requests just using the status variable. But the overhead of storing this extra information should be minimal.
Related
I used to add a NSString *type property to UIButton,today however,I want to add a BOOL type property isScrolling to UIScrollView to indicate whether the scrollView is scrolling in the same way but there showed something wrong,here is my code:
#import <UIKit/UIKit.h>
#interface UIScrollView (Util)
#property (nonatomic,assign) BOOL isScrolling;
#end
#import <objc/objc-runtime.h>
#interface UIScrollView ()<UIScrollViewDelegate>
#end
#implementation UIScrollView (Util)
static void *strKey = &strKey;
- (void)setIsScrolling:(BOOL)isScrolling{
objc_setAssociatedObject(self, & strKey, isScrolling, OBJC_ASSOCIATION_ASSIGN);
}
- (BOOL)isScrolling{
return objc_getAssociatedObject(self, &strKey);
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
self.isScrolling = YES;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{
self.isScrolling = NO;
}
#end
And the error is:
Is there any way to deal with thses errors and can we use category and runtime to achieve the goal of adding a BOOL property to UIScrollView to indicate whether the scrollView is scrolling?
Hope someone can give me some advice,thanks a lot.
An associated object must be just that, an object, and so a value of the non-object BOOL type won't work unless wrapped as an object. Fortunately that is pretty easy:
In the call to objc_setAssociatedObject change isScrolling to #(isScrolling) and change OBJC_ASSOCIATION_ASSIGN to OBJC_ASSOCIATION_RETAIN_NONATOMIC. This will create and pass an NSNumber object, the second change requesting that this object's lifetime be tied to that of the first parameter, self.
In isScrolling change objc_getAssociatedObject(self, &strKey) to [objc_getAssociatedObject(self, &strKey) boolValue]. This will extract the BOOL value from the stored NSNumber object.
HTH
Try this :
Try to convert boolean to nsnumber.
-(void)setIsScrolling:(BOOL)isScrolling{
objc_setAssociatedObject(self, & strkey), [NSNumber numberWithBool:isScrolling], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
As the error is showing implicit conversion of BOOL to id, you need to send a object instead of primitive types.
The method signature for objc_setAssociatedObject is
/**
* Sets an associated value for a given object using a given key and association policy.
*
* #param object The source object for the association.
* #param key The key for the association.
* #param value The value to associate with the key key for object. Pass nil to clear an existing association.
* #param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*
* #see objc_setAssociatedObject
* #see objc_removeAssociatedObjects
*/
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
Above you can see the value should be object.
Change your code
#property (assign, nonatomic) BOOL isScrolling; to #property (strong, nonatomic) NSNumber *scrolling;
And change OBJC_ASSOCIATION_ASSIGN to OBJC_ASSOCIATION_RETAIN_NONATOMIC in your case objc_setAssociatedObject(self, &strkey, scrolling, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
And use [_scrolling boolValue] for checking.
You can't set the primitive data type as an AssociatedObject, It's an Object. Convert bool to NSNumber when you are saving the data. Convert NSNumber to bool while reading the data.
OBJC_ASSOCIATION_ASSIGN - Specifies a weak reference to the associated object.
OBJC_ASSOCIATION_RETAIN_NONATOMIC - Specifies a strong reference to the associated object, and that the association is not made atomically.
.h File :
#import <UIKit/UIKit.h>
#interface UIScrollView (ScrollViewCategory)
#property (nonatomic, strong)NSNumber *isScrolling;
#end
.m File
#interface UIScrollView ()
#end
#import <objc/runtime.h>
#implementation UIScrollView (ScrollViewCategory)
#dynamic isScrolling;
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, #selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, #selector(associatedObject));
}
Is it possible to set a handler for undefined keys in Core Data?
I'm asking because despite defining valueForUndefinedKey: my implementation of that method is never called if valueForKey: is invoked on a managed object that doesn't have an attribute with that key.
This is needed for a synchronization system I'm currently writing where an object can be marked as locallyCreated or locallyDeleted but at the same time not all objects are editable so I want to avoid defining these properties for all entities in my model (around ~25 entities).
Although it seems tempting to create a single parent entity for that purpose I would like to avoid doing that since that will put all objects in one giant SQLite table which as far as I know will have negative impact on performance.
Currently I have a base "entity" class called RemoteObject that defines some common attributes like remoteID, locallyCreated, locallyDeleted, as suggested in another answer on SO, which all other entities inherit in code like this:
#interface RemoteObject : NSManagedObject
#property (nonatomic) NSString *remoteID;
#property (nonatomic) BOOL locallyCreated;
#property (nonatomic) BOOL locallyDeleted;
#end
#implementation RemoteObject
#dynamic remoteID;
#dynamic locallyCreated;
#dynamic locallyDeleted;
#end
#interface Project : RemoteObject
// custom properties
#end
What I want is to inspect any given RemoteObjet and see if it was locally create or deleted. However, as I said above, not all of the entities have corresponding attributes, so Core Data will throw an exception.
I found a workaround that allows me to avoid those errors - define a class method instead:
- (id)valueForKeyIfExists:(NSString *)key {
if (self.entity.attributesByName[key] != nil) {
return [self valueForKey:key];
}
return nil;
}
+ (BOOL)objectIsLocallyCreated:(RemoteObject *)object {
return [[object valueForKeyIfExists:#"locallyCreated"] boolValue];
}
But I was wondering if it would be possible to refactor this into object properties instead, catching undefined keys with valueForUndefinedKey: like this:
- (id)valueForUndefinedKey:(NSString *)key {
if ([key isEqualToString:LocallyCreatedKey]
|| [key isEqualToString:LocallyDeletedKey]) {
return #(NO);
}
return [super valueForUndefinedKey:key];
}
- (BOOL)locallyDeleted {
return [[self valueForKey:LocallyDeletedKey] boolValue];
}
It would be better to move those 2 flag attributes into a different class and then anything which doesn't have them is a subclass of RemoteObject and anything which does is a subclass of the new class, perhaps TrackedRemoteObject. Then in your algorithm you can class test to determine conformance.
I have a project using CoreData. I use Mogenerator to generate the subclasses.
When I set the value of a property, this value isn't actually assigned. Each subsequent time I try to set the value, the previous value I set it to was not assigned.
This worked fine as my underlying data framework was Mantle, but since moving to CoreData, this stopped working. I rely on KVO to keep some UIView objects up-to-date with the model.
Again, the ivars of a CoreData NSManagedObject subclass do not seem to take the values I assign them.
Consider the following interface:
#interface Light : _Light{}
/**
Light / Color Properties
*/
#property (nonatomic, assign) CGFloat brightness; // 0...1
#property (nonatomic, assign) CGFloat hue; // 0...1
#property (nonatomic, assign) CGFloat saturation; // 0...1
#property (nonatomic, assign, getter = isEnabled) BOOL enabled;
#property (nonatomic, readonly) UIColor *color; // derived from the above
- (void)setHue:(CGFloat)hue saturation:(CGFloat)saturation; // it often makes sense to set these together to generate fewer KVO on the color property.
#end
and the following .m file:
#interface Light ()
{
CGFloat _hue, _saturation, _brightness;
UIColor *_color;
}
#property (nonatomic, assign) BOOL suppressColorKVO;
#property (nonatomic, readwrite) UIColor *color;
#end
#implementation Light
#synthesize suppressColorKVO = _suppressColorKVO;
- (void)setHue:(CGFloat)hue saturation:(CGFloat)saturation
{
BOOL dirty = NO;
if (saturation != _saturation) {
// clamp its value
[self willChangeValueForKey:#"saturation"];
_saturation = MIN(MAX(saturation, 0.0f), 1.0f);
[self didChangeValueForKey:#"saturation"];
dirty = YES;
}
if (hue != _hue) {
[self willChangeValueForKey:#"hue"];
_hue = MIN(MAX(hue, 0.0f), 1.0f);
[self didChangeValueForKey:#"hue"];
dirty = YES;
}
if (dirty) {
if (!_suppressColorKVO) {
[self setColor: self.color];
}
}
}
// other stuff... the color accessors are also custom. Derived from the h, s, b values.
#end
I assume I'm not playing nice with CoreData, but I have no idea what's wrong. These hue, saturation, brightness are all 'transient' (not in the core data sense) because they are constantly updated by some hardware we are interfacing with so there's no need to save their state.
If hue and saturation are properties in your model then you should be setting their values using setPrimitiveValue:forKey: (or the associated generated primitive methods).
That said, your code all looks custom as model attributes would be NSNumber instances and mogenerator would create value methods for you. So I'm going to guess that these attributes you have aren't backed in the model and that's why they aren't being stored.
So, add the attributes to the model and access the values using the appropriate methods.
In the end it had nothing to do with CoreData. This approach above DOES work with CoreData objects. You can have in a subclass some "transient" properties that exist outside of the CoreData NSManagedObject and you can create your own ivars for them, and your own accessors, and it cooperates.
In relation to this question, I have a complex system that also sends some commands to some hardware, and the hardware returns a response whether it accepted the command with the current status. It turns out I had a bug in that handler which was setting these values back to some unexpected value.
What helped me debug it were using watchpoints in the debugger. Really handy feature! You set a watchpoint and it will break in the debugger whenever the memory address of your variable is set with a new value. (A tiny bit more on that here)
I made few classes via Core Data. And I need some additional #propertys for one of that classes in runtime. This #propertys are responsible for download progress and I don't want to store them in Core Data DB. I tried to use a separate extension class:
#interface MyClass ()
{
CGFloat _downloadProgress;
NSInteger _downloadErrorCounter;
BOOL _downloadAllStuff;
BOOL _downloadUserCanceled;
}
#property (nonatomic, assign) CGFloat downloadProgress;
#property (nonatomic, assign) NSInteger downloadErrorCounter;
#property (nonatomic, assign) BOOL downloadAllStuff;
#property (nonatomic, assign) BOOL downloadUserCanceled;
#end
But private variables are not visible out of MyClass, and #propertys compile all right, but in runtime i get -[MyClass setDownloadErrorCounter:]: unrecognized selector sent to instance.
Can anyone suggest me some solution?
The easiest solution (if you don't want to modify the Xcode generated class files) is to add the properties to the Core Data model and define the
properties as transient. Transient properties are not saved to the store file.
Another option is to use a tool like "mogenerator", which generates two class files for each
entity, one for the Core Data properties (which is overwritten if the the model changes),
and one for your custom properties (which is not overwritten).
Update: Starting with Xcode 7, Xcode creates both a class and
a category for each managed object subclass, compare NSManagedObject subclass property in category. Custom properties can be added to the class
definition which is not overwritten when the model changes.
Just add
#synthesize downloadErrorCounter = _downloadErrorCounter;
...
in #implementation. Note, not #dynamic.
When trying to use the #synthesize solution i got the error:
#synthesize not allowed in a category's implementation.
Solution was to use associated objects as described in this blog: http://kaspermunck.github.io/2012/11/adding-properties-to-objective-c-categories/
MyManagedObject+Additions.h
#property (strong, nonatomic) NSString *test;
MyManagedObject+Additions.m
NSString const *key = #"my.very.unique.key";
- (void)setTest:(NSString *)test
{
objc_setAssociatedObject(self, &key, test, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)test
{
return objc_getAssociatedObject(self, &key);
}
I have two core data models with int64_t properties. One of them works fine while the other throws EXC_BAD_ACCESS when I try to assign a non-zero value to the integer field. I've read the answers that say to recreate the NSManagedObject child class and I have done with no success. The broken class looks like this:
#interface NoteObject : NSManagedObject
#property (nonatomic) int64_t remoteID;
#property (nonatomic) int64_t remoteArticleID;
#property (strong, nonatomic) ArticleObject *article;
#property (strong, nonatomic) NSString *status;
#property (strong, nonatomic) NSString *token;
#property (strong, nonatomic) NSString *title;
#property (strong, nonatomic) NSString *noteContent;
#property (strong, nonatomic) NSDate *pubDate;
#property (strong, nonatomic) NSDate *modDate;
#end
#implementation NoteObject
#dynamic remoteID;
#dynamic remoteArticleID;
#dynamic article;
#dynamic status;
#dynamic token;
#dynamic title;
#dynamic noteContent;
#dynamic pubDate;
#dynamic modDate;
#end
The offending line is in this block:
_noteObject = [NSEntityDescription insertNewObjectForEntityForName:#"Note" inManagedObjectContext:self.managedObjectContext];
_noteObject.remoteArticleID = 0; // this works
_noteObject.remoteArticleID = 1; // this crashes
What really has me stumped is that in another model I have the same fields with the same types and they will accept non-zero values without any trouble:
bookmarkObject = [NSEntityDescription insertNewObjectForEntityForName:#"Bookmark" inManagedObjectContext:self.managedObjectContext];
bookmarkObject.remoteArticleID = 0; // this works
bookmarkObject.remoteArticleID = 1; // this works, too
Is there anything in my .xcdatamodeld file that could be causing this?
EDIT
My data models look like this:
I had exactly the same problem.
It appears that xcode (or perhaps the compiler, or perhaps the two between them) sometimes gets confused when you manually edit properties in the NSManagedObject - it ends up treating our integers as pointers and trying to access memory directly - hence the EXC_BAD_ACCESS.
Anyway, as this question explains: SO Question, the solution is to delete your old class (obviously copy out any custom code so you can paste it back again later) and then get xcode to regenerate it for you (select the entity in the data model and select "Editor / Create NSManagedObject subclass..."). In the dialogue that appears, make sure "Use scalar properties for primitive data types" is ticked.
You may have to manually edit the resulting class to turn some non scalar properties back into objects (I had a date object which it turned into something other than NSDate - I forget exactly what, but it accepted the manually made edit back to NSDate).
It worked for me. Hope it works for you.
Ali
Well, in case anyone else is having this issue, I never found a satisfactory answer for why one entity was working and the other wasn't. My workaround was to refactor the properties to use NSNumber wrappers instead of primitive int64_t values.
#property (strong, nonatomic) NSNumber *remoteID;
#property (strong, nonatomic) NSNumber *remoteArticleID;
Of course, that means boxing/unboxing the integer values.
_noteObject.remoteArticleID = [NSNumber numberWithInt:1];
int intVar = [_noteObject.remoteArticleID intValue];
In your model file, check that the entity's "Class" property is set to the appropriate class, and not the default NSManagedObject.
If you leave it as NSManagedObject, Core Data will create properties itself on a custom NSManagedObject subclass it generates itself, rather than using your own subclass. Most getters and setters will appear to work, but you may have issues with non-boxed primitive properties and custom getters and setters.