Apple's documentation on -setPrimitiveValue:forKey: is vague in two ways when using it to manage to-many relationships.
First they state:
If you try to set a to-many relationship to a new NSMutableSet object, it will (eventually) fail.
Eventually?! What does that even mean? Will it fail later during -[NSManagedObjectContext save:]? When an managed object is turned into a fault and then paged back in? When? Can I write a test case to consistently recreate the failure on-demand?
Second, providing sample code to correctly handle this case, they write:
first get the existing set using primitiveValueForKey: (ensure the method does not return nil)
What should I do if/when the method does return nil? assert() it and fail immediately because that means the entire object graph is corrupted and saving will lead to data loss? NSAssert() on it as a warning to the caller but press on (silently doing nothing)?
Right now I'm simply directly assigning my desired NS[Mutable]Set in that case, like so:
- (void)setChildren:(NSSet*)value_ {
NSMutableSet *mutableRelationshipSet = [[[self primitiveValueForKey:#"children"] mutableCopy] autorelease];
if (mutableRelationshipSet) {
[mutableRelationshipSet setSet:value_];
[self setPrimitiveValue:mutableRelationshipSet forKey:#"children"];
} else {
[self setPrimitiveValue:value_ forKey:#"children"];
}
}
Is that wrong?
Just mutate the return value of -primitiveValueForKey: as a mutable set, trusting that its return value will do the right thing.
Be sure to also use -willChangeValueForKey:withSetMutation:usingObjects: and -didChangeValueForKey:withSetMutation:usingObjects: around your manipulation; you didn't show this in your above code.
- (void)setChildren:(NSSet *)value {
[self willChangeValueForKey:#"children" withSetMutation:NSKeyValueSetSetMutation usingObjects:value];
NSMutableSet *primitiveValue = [self primitiveValueForKey:#"children"];
[primitiveValue setSet:value];
[self didChangeValueForKey:#"children" withSetMutation:NSKeyValueSetSetMutation usingObjects:value];
}
If you can target Leopard, you don't have to worry about this; you can use Core Data's built-in property support instead:
#import <CoreData/CoreData.h>
#interface Folder : NSManagedObject
#property (readwrite, retain) NSSet *children;
#end
#implementation Folder
#dynamic children;
#end
Core Data will generate not only a getter/setter pair for the children property but the mutable-set accessors as well, so you can use -mutableSetValueForKey: on it and manipulate the result with a minimum of overhead.
Related
If we want to override [dictionary objectForKey:#"key"] it can be done by subclassing NSDictionary class. How to override dictionary[#"key"]? In this context I want to know how dictionary[#"key"] is implemented.
Thanks!
Edit:
I wanted to find a scalable way to parse an API response while preventing [NSNull null] from crashing my app. I have written category for NSDictionary, but I wanted a way to parse in this syntax: data[#"key"]
So, I was evaluating the feasibility of subclassing NSDictionary.
When you use dictionary[#"key"] this gets converted into a call to [dictionary objectForKeyedSubscript:#"key"]. objectForKeyedSubscript: has the same behaviour as objectForKey:.
If you want to change the behaviour of dictionary[#"key"] then you will need to override objectForKeyedSubscript:.
Apple's NSDictionary API Reference has a little more information.
EDIT: This question is pretty old now, but it just recently came to my attention again, so I thought I would add an example of wrapping NSDictionary as I would recommend. This could be made more robust and feature rich, but it's an okay place to start.
#interface NilSafeDictionary: NSObject
-(instancetype)initWithDictionary:(NSDictionary*)dictionary;
#end
/** Category on NSDictionary to create a nil-safe wrapper. */
#interface NSDictionary <NilSafeConvenience>
- (NilSafeDictionary*)nilSafe;
#end
#implementation NilSafeDictionary {
NSDictionary* _internalDict;
}
- (instancetype)initWithDictionary:(NSDictionary*)dictionary {
self = [super init];
if (self) {
_internalDict = [dict copy];
}
return self;
}
- (id)objectForKeyedSubscript:(id<NSCopying>)key {
id obj = _internalDict[key];
if ([obj isKindOfClass:[NSNull class]]) {
return nil;
}
return obj;
}
#end
#implementation NSDictionary <NilSafeConvenience>
- (NilSafeDictionary*)nilSafe {
return [[NilSafeDictionary alloc] initWithDictionary:self];
}
#end
CAVEAT: You don't explain here what problem you're trying to solve, but for anyone who comes across this question in the future, subclassing NSDictionary is almost certainly never what you want.
NSDictionary is a class cluster. Class clusters are tricky to subclass correctly with guidance, plus Apple's documentation explicitly tells you not to subclass, so there's no guidance on how to do it correctly.
Even if you're able to use your subclass and have it "work correctly" for your purposes, every other piece of code in the system will still be returning the original NSDictionary class, not your subclass, so it doesn't benefit you.
If you're hell bent on modifying the way NSDictionary works, create your own wrapper class that has an NSDictionary inside and create your own objectForKeyedSubscript: implementation.
Yes, you can add subscript indexing to your own custom class just like NSDictionary!
dictionary[#key] is short hand for calling [dictionary objectForKeyedSubscript:#"key"]. Therefore to override the functionality of dictionary[#"key"] you can subclass NSDictionary.
#interface MyDictionary : NSDictionary
- (id) objectForKeyedSubscript:(id)key;
#end
#implementation MyDictionary
- (id) objectForKeyedSubscript:(id)key
{
/*
...
*/
}
#end
Original answer using categories, however this approach isn't advised as it will override the original method removing access to super and potential of clashes should multiple categories be loaded overriding the same method. The last category to be loaded will be used at runtime, therefore you cannot guarantee which that will be.
dictionary[#key] is short hand for calling [dictionary objectForKeyedSubscript:#"key"]. Therefore to override the functionality of dictionary[#"key"] you would create a new category for NSDictionary.
//Category in NSDictionary+CustomKeyedSubscript.h and NSDictionary+CustomKeyedSubscript.m
#interface NSDictionary (CustomKeyedSubscript)
- (id) objectForKeyedSubscript:(id)key;
#end
#implementation NSDictionary (CustomKeyedSubscript)
- (id) objectForKeyedSubscript:(id)key
{
/*
...
*/
}
#end
I don't have too much experience with iOS, but I am working on some legacy code. In the project, we use an object as the key of a dictionary:
NSMutableDictionary * dict;
RoleContainer * role = [Class getRole];
[dict setObject:[Class getData] forKey:role];
We have the role passed to another function. When we try to retrieve the data:
data = [dict objectForKey:role];
Sometimes the return value is empty. It happens about 10% of time. I stepped through the code and found out after passing role to the function the reference of the "role" object had been changed! For example, from 0x002bf500 to 0x00222bad.
Why?
In order to play nicely with NSMutableDictionary your RoleContainer class must implement a hash and isEqual methods. Otherwise, equal roles may get recorded twice in the dictionary, or querying by a valid key may fail.
Here is a brief sample of how you could implement your hash/isEqual when your class has an identifying member:
#interface RoleContainer : NSObject
#property (nonatomic, readonly) NSString *name;
- (NSUInteger)hash;
- (BOOL)isEqual:(id)anObject;
#end
#implementation RoleContainer
#synthesize name = _name;
- (NSUInteger)hash {
return [name hash];
}
- (BOOL)isEqual:(id)anObject {
if (![anObject isKindOfClass:[RoleContainer class]]) {
return NO;
}
RoleContainer *other = (RoleContainer*)anObject;
return [_name isEqual:[other name]];
}
#end
In that code, dict is going to be nil, so you're sending messages to nothing. Is it actually pointing to something later on?
I'm assuming your RoleContainer responds to the stringValue method, which might be a good place to look at what is going on if it's overloaded.
If it's using the standard string value, then it's returning the class and memory location. This may not be reliable if someone down the line is resetting objects to keys.
You may also have an issue where the getData object is being released somewhere where it shouldn't be touched. Try enabling NSZombieEnabled in the debugger, or enable ARC.
try
NSMutableDictionary * dict = [NSMutableDictionary dictionary];
Also it would make sense to have a look at your Class. Is it a class, and getRole a class-method? is it an object?
I have a situation where it seems like I need to add instance variables to a category, but I know from Apple's docs that I can't do that. So I'm wondering what the best alternative or workaround is.
What I want to do is add a category that adds functionality to UIViewControllers. I would find it useful in all my different UIViewControllers, no matter what specific UIViewController subclass they extend, so I think a category is the best solution. To implement this functionality, I need several different methods, and I need to track data in between them, so that's what led me to wanting to create instance methods.
In case it's helpful, here's what I specifically want to do. I want to make it easier to track when the software keyboard hides and shows, so that I can resize content in my view. I've found that the only way to do it reliably is to put code in four different UIViewController methods, and track extra data in instance variables. So those methods and instance variables are what I'd like to put into a category, so I don't have to copy-paste them each time I need to handle the software keyboard. (If there's a simpler solution for this exact problem, that's fine too--but I would still like to know the answer to category instance variables for future reference!)
Yes you can do this, but since you're asking, I have to ask: Are you absolutely sure that you need to? (If you say "yes", then go back, figure out what you want to do, and see if there's a different way to do it)
However, if you really want to inject storage into a class you don't control, use an associative reference.
Recently, I needed to do this (add state to a Category). #Dave DeLong has the correct perspective on this. In researching the best approach, I found a great blog post by Tom Harrington. I like #JeremyP's idea of using #property declarations on the Category, but not his particular implementation (not a fan of the global singleton or holding global references). Associative References are the way to go.
Here's code to add (what appear to be) ivars to your Category. I've blogged about this in detail here.
In File.h, the caller only sees the clean, high-level abstraction:
#interface UIViewController (MyCategory)
#property (retain,nonatomic) NSUInteger someObject;
#end
In File.m, we can implement the #property (NOTE: These cannot be #synthesize'd):
#implementation UIViewController (MyCategory)
- (NSUInteger)someObject
{
return [MyCategoryIVars fetch:self].someObject;
}
- (void)setSomeObject:(NSUInteger)obj
{
[MyCategoryIVars fetch:self].someObject = obj;
}
We also need to declare and define the class MyCategoryIVars. For ease of understanding, I've explained this out of proper compilation order. The #interface needs to be placed before the Category #implementation.
#interface MyCategoryIVars : NSObject
#property (retain,nonatomic) NSUInteger someObject;
+ (MyCategoryIVars*)fetch:(id)targetInstance;
#end
#implementation MyCategoryIVars
#synthesize someObject;
+ (MyCategoryIVars*)fetch:(id)targetInstance
{
static void *compactFetchIVarKey = &compactFetchIVarKey;
MyCategoryIVars *ivars = objc_getAssociatedObject(targetInstance, &compactFetchIVarKey);
if (ivars == nil) {
ivars = [[MyCategoryIVars alloc] init];
objc_setAssociatedObject(targetInstance, &compactFetchIVarKey, ivars, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[ivars release];
}
return ivars;
}
- (id)init
{
self = [super init];
return self;
}
- (void)dealloc
{
self.someObject = nil;
[super dealloc];
}
#end
The above code declares and implements the class which holds our ivars (someObject). As we cannot really extend UIViewController, this will have to do.
I believe it is now possible to add synthesized properties to a category and the instance variables are automagically created, but I've never tried it so I'm not sure if it will work.
A more hacky solution:
Create a singleton NSDictionary which will have the UIViewController as the key (or rather its address wrapped as an NSValue) and the value of your property as its value.
Create getter and setter for the property that actually goes to the dictionary to get/set the property.
#interface UIViewController(MyProperty)
#property (nonatomic, retain) id myProperty;
#property (nonatomic, readonly, retain) NSMutableDcitionary* propertyDictionary;
#end
#implementation UIViewController(MyProperty)
-(NSMutableDictionary*) propertyDictionary
{
static NSMutableDictionary* theDictionary = nil;
if (theDictionary == nil)
{
theDictioanry = [[NSMutableDictionary alloc] init];
}
return theDictionary;
}
-(id) myProperty
{
NSValue* key = [NSValue valueWithPointer: self];
return [[self propertyDictionary] objectForKey: key];
}
-(void) setMyProperty: (id) newValue
{
NSValue* key = [NSValue valueWithPointer: self];
[[self propertyDictionary] setObject: newValue forKey: key];
}
#end
Two potential problems with the above approach:
there's no way to remove keys of view controllers that have been deallocated. As long as you are only tracking a handful, that shouldn't be a problem. Or you could add a method to delete a key from the dictionary once you know you are done with it.
I'm not 100% certain that the isEqual: method of NSValue compares content (i.e. the wrapped pointer) to determine equality or if it just compares self to see if the comparison object is the exact same NSValue. If the latter, you'll have to use NSNumber instead of NSValue for the keys (NSNumber numberWithUnsignedLong: will do the trick on both 32 bit and 64 bit platforms).
This is best achieved using the built-in ObjC feature Associated Objects (aka Associated References), in the example below just change to your category and replace associatedObject with your variable name.
NSObject+AssociatedObject.h
#interface NSObject (AssociatedObject)
#property (nonatomic, strong) id associatedObject;
#end
NSObject+AssociatedObject.m
#import <objc/runtime.h>
#implementation NSObject (AssociatedObject)
#dynamic associatedObject;
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, #selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, #selector(associatedObject));
}
See here for the full tutorial:
http://nshipster.com/associated-objects/
It mentioned in many document's online that you can't create create new variable in category but I found a very simple way to achieve that. Here is the way that let declare new variable in category.
In Your .h file
#interface UIButton (Default)
#property(nonatomic) UIColor *borderColor;
#end
In your .m file
#import <objc/runtime.h>
static char borderColorKey;
#implementation UIButton (Default)
- (UIColor *)borderColor
{
return objc_getAssociatedObject(self, &borderColorKey);
}
- (void)setBorderColor:(UIColor *)borderColor
{
objc_setAssociatedObject(self, &borderColorKey,
borderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
self.layer.borderColor=borderColor.CGColor;
}
#end
That's it now you have the new variable.
Why not simply create a subclass of UIViewController, add the functionality to that, then use that class (or a subclass thereof) instead?
Depending on what you're doing, you may want to use Static Category Methods.
So, I assume you've got this kind of problem:
ScrollView has a couple of textedits in them. User types on text edit, you want to scroll the scroll view so the text edit is visible above the keyboard.
+ (void) staticScrollView: (ScrollView*)sv scrollsTo:(id)someView
{
// scroll view to someviews's position or some such.
}
returning from this wouldn't necessarily require the view to move back, and so it doesn't need to store anything.
But that's all I can thinkof without code examples, sorry.
I believe it is possible to add variables to a class using the Obj-C runtime.
I found this discussion also.
Early warning - code sample a little long...
I have a singleton NSMutableArray that can be accessed from anywhere within my application. I want to be able to reference the NSMutableArray from multiple NIB files but bind to UI elements via NSArrayController objects. Initial creation is not a problem. I can reference the singleton NSMutableArray when the NIB gets loaded and everything appears fine.
However, changing the NSMutableArray by adding or removing objects does not kick off KVO to update the NSArrayController instances. I realize that "changing behind the controller's back" is considered a no-go part of Cocoa-land, but I don't see any other way of programmatically updating the NSMutableArray and letting every NSArrayController be notified (except it doesn't work of course...).
I have simplified classes below to explain.
Simplified singleton class header:
#interface MyGlobals : NSObject {
NSMutableArray * globalArray;
}
#property (nonatomic, retain) NSMutableArray * globalArray;
Simplified singleton method:
static MyGlobals *sharedMyGlobals = nil;
#implementation MyGlobals
#synthesize globalArray;
+(MyGlobals*)sharedDataManager {
#synchronized(self) {
if (sharedMyGlobals == nil)
[[[self alloc] init] autorelease];
}
return sharedMyGlobals;
}
-(id) init {
if(self = [super init]) {
self.globals = [[NSMutableArray alloc] init];
}
return self
}
// ---- allocWithZone, copyWithZone etc clipped from example ----
In this simplified example the header and model for objects in the array:
Header file:
#interface MyModel : NSObject {
NSInteger myId;
NSString * myName;
}
#property (readwrite) NSInteger myId;
#property (readwrite, copy) NSString * myName;
-(id)initWithObjectId:(NSInteger)newId objectName:(NSString *)newName;
#end
Method file:
#implementation MyModel
#synthesize myId;
#synthesize myName;
-(id)init {
[super init];
myName = #"New Object Name";
myId = 0;
return self;
}
#end
Now imagine two NIB files with appropriate NSArrayController instances. We'll call them myArrayControllerInNibOne and myArrayControllerInNib2. Each array controller in the init of the NIB controller sets the content of the array:
// In NIB one init
[myArrayControllerInNibOne setContent: [[MyGlobals sharedMyGlobals].globalArray];
// In NIB two init
[myArrayControllerInNibTwo setContent: [[MyGlobals sharedMyGlobals].globalArray];
When each NIB initializes the NSArrayController binds correctly to the shared array and I can see the array content in the UI as you would expect. I have a separate background thread that updates the global array when content changes based on an external event. When objects need to be added in this background thread, I simply add them to the array as follows:
[[[MyGlobals sharedMyGlobals].globalArray] addObject:theNewObject];
This is where things fall apart. I can't call a willChangeValueForKey and didChangeValueForKey on the global array because the shared instance doesn't have a key value (should I be adding this in the singleton class?)
I could fire off an NSNotification and catch that in the NIB controller and either do a [myArrayControllerInNibOne rearrangeObjects]; or set the content to nil and reassign the content to the array - but both of these seems like hacks and. moreover, setting the NSArrayController to nil and then back to the global array causes a visual flash within the UI as the content is cleared and re-populated.
I know I could add directly to the NSArrayController and the array gets updated, but I don't see a) how the other NSArrayController instances would be updated and b) I don't want to tie my background thread class explicitly to a NIB instance (nor should I have to).
I think the correct approach is to either fire off the KVO notification somehow around the addObject in the background thread, or add something to the object that is being stored in the global array. But I'm at a loss.
As a point of note I am NOT using Core Data.
Any help or assistance would be very much appreciated.
Early warning - answer a little long…
Use objects that model your domain. You have no need for singletons or globals, you need a regular instance of a regular class. What Objects are your storing in your global array? Create a class that represents that part of your model.
If you use an NSMutableArray as storage it should be internal to your class and not visible to outside objects. eg if you are modelling a zoo, don't do
[[[MyGlobals sharedMyGlobals].globalArray] addObject:tomTheZebra];
do do
[doc addAnimal:tomTheZebra];
Dont try to observe a mutable array - you want to observe a to-many property of your object. eg. instead of
[[[MyGlobals sharedMyGlobals].globalArray] addObserver:_controller]
you want
[doc addObserver:_controller forKeyPath:#"animals" options:0 context:nil];
where doc is kvo compliant for the to-many property 'anaimals'.
To make doc kvo compliant you would need to implement these methods (Note - you don't need all these. Some are optional but better for performance)
- (NSArray *)animals;
- (NSUInteger)countOfAnimals;
- (id)objectInAnimalsAtIndex:(NSUInteger)i;
- (id)AnimalsAtIndexes:(NSIndexSet *)ix;
- (void)insertObject:(id)val inAnimalsAtIndex:(NSUInteger)i;
- (void)insertAnimals:atIndexes:(NSIndexSet *)ix;
- (void)removeObjectFromAnimalsAtIndex:(NSUInteger)i;
- (void)removeAnimalsAtIndexes:(NSIndexSet *)ix;
- (void)replaceObjectInAnimalsAtIndex:(NSUInteger)i withObject:(id)val;
- (void)replaceAnimalsAtIndexes:(NSIndexSet *)ix withAnimals:(NSArray *)vals;
Ok, that looks pretty scary but it's not that bad, like i said you don't need them all. See here. These methods dont need to be part of the interface to your model, you could just add:-
- (void)addAnimal:(id)val;
- (void)removeAnimal:(id)val;
and write them in terms of the kvc accessors. The key point is it's not the array that sends notifications when it is changed, the array is just the storage behind the scenes, it is your model class that send the notifications that objects have been added or removed.
You may need to restructure your app. You may need to forget about NSArrayController altogether.
Aaaaaannnnnyyywaaayyy… all this gets you nothing if you do this
[[[MyGlobals sharedMyGlobals].globalArray] addObject:theNewObject];
or this
[doc addAnimal:tomTheZebra];
from a background thread. You can't do this. NSMutableArray isn't thread safe. If it seems to work then the best that will happen is that the kvo/binding notification is delivered on the background as well, meaning that you will try to update your GUI on the background, which you absolutely cannot do. Making the array static does not help in any way i'm afraid - you must come up with a strategy for this.. the simplest way is performSelectorOnMainThread but beyond that is another question entirely. Threading is hard.
And about that static array - just stop using static, you don't need it. Not because you have 2 nibs, 2 windows or anything. You have an instance that represents your model and pass a pointer to that to you viewControllers, windowControllers, whatever. Not having singletons/static variables helps enormously with testing, which of course you should be doing.
I'm new in Core Data, and i got a problem i can't get my head around how to do "the right way"
I'll try and examplify my problem.
I got a entity Car. And a list of all the cars in my program. The cars have some attributes, but they are not predefined. So for each car i want to be able to define some properties.
Therefore i have defined a new entity CarProperty, with a one to many relation with the car.
In the nscollectionview i would like to show some of the properties from the car, more specefic the number of kilometer (numKm) it has driven (if that property exist). So i want to bind it to a label. But how to do?
I can't say representedObject.properties.numKm, or representedObject.numKm.
How should I get around this?
Hope it makes sense.
This isn't an easy problem. The thing is, Core Data doesn't know anything about numKm as a property. How is it supposed to know that numKm corresponds to a particular CarProperty object?
The fundamental problem you're describing is key-value coding compliance. Cocoa's going to look for a method called numKm on the properties object. Not finding one, it'll try sending [properties valueForKey:#"numKm"]; Since valueForKey: doesn't know what to do with numKm, you get an error, but not before it calls [properties valueForUndefinedKey:#"numKm"]
But here's the catch: properties is an NSSet generated by Core Data, so you can't subclass it to override valueForUndefinedKey:. What you can do is create your own object that's KVC-compliant for your arbitrary properties and use that instead.
One solution is to subclass NSDictionary and make it act as a proxy. The primitive methods are count, objectForKey: and keyEnumerator. If you override these three methods, you can create an NSDictionary that's linked to your Car object and returns the appropriate CarProperty objects. For example:
#interface PropertyProxy : NSDictionary {
}
#property (nonatomic, readonly, assign) Car *car;
- (id)initWithCar:(Car *)car
#end
#implementation PropertyProxy
#synthesize car = _car;
- (id)initWithCar:(Car *)car {
if (!(self = [super init]))
return nil;
_car = car;
return self;
}
- (NSInteger)count {
return [car.properties count];
}
- (id)objectForKey:(NSString *)key {
return [[car.properties filteredSetUsingPredicate:[NSPredicate predicateWithFormt:#"key == %#", key]] anyObject];
}
- (NSEnumerator *)keyEnumerator {
return [[car valueForKeyPath:#"properties.key"] objectEnumerator];
}
#end
Then, in your Car class, do this:
#interface Car : NSManagedObject {
// other stuff
}
#property (nonatomic, readonly) NSDictionary *carProperties;
// other stuff
#end
#implementation Car
// other stuff
- (NSDictionary *)carProperties {
return [[[PropertyProxy alloc] initWithCar:self] autorelease];
}
#end
(Disclaimer: I just typed this into my web browser, so no guarantees this actually compiles :-))
As you can see, it's not the easiest thing in the world to do. You'll be able to set up key paths like this:
representedObject.carProperties.numKm;
Keep in mind that, while this is key-value coding compliant, it is not key-value observing compliant. So if numKm changes, you won't be able to observe that. You would need to do some extra work to make that happen.