Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I'd appreciate some feedback on a particular approach I'm thinking of using. The scenario is below.
I have an object (lets call it MObject) that has a number of properties, say, x and y coordinates, height and width. The properties are named according to the KVC guidelines (MObject.x; MObject.height, etc). My next task, is to read in an XML file that describes this MObject. Unfortunately, the XML elements are named differently -- X and Y, Height and Width (note the capitalization).
Ideally, the XML elements would match up with MObject's properties. In this case, I could use KVC and avoid a whole whack of code:
for (xmlProperty in xmlElement)
{
[MObject setValue:xmlProperty.value forKey:xmlProperty.name].
}
One way of approaching this would be to make use of case-insensitive keys. Where would I start with that? Are there any other, better solutions?
Suggestions very much appreciated.
Don't override -[NSObject valueForKey:] and -[NSObject setValue:forKey:] if you can at all help it.
Your best bet would be to convert the keys you get from the XML file on the fly. Use a separate method to do the conversion and you can also maintain a cache of names to property keys, so you only need to do each conversion once.
- (NSString *)keyForName:(NSString *)name {
// _nameToKeyCache is an NSMutableDictionary that caches the key
// generated for a given name so it's only generated once per name
NSString *key = [_nameToKeyCache objectForKey:name];
if (key == nil) {
// ...generate key...
[_nameToKeyCache setObject:key forKey:name];
}
return key;
}
- (void)foo:xmlElement {
for (xmlProperty in xmlElement) {
[myObject setValue:xmlProperty.value forKey:[self keyForName:xmlProperty.name]].
}
}
You can use NSString's lowercaseString to convert the XML key name to lowercase, if that helps.
Override -valueForUndefinedKey: and -setValue:forUndefinedKey:
If you find a key with a different capitalization use it, otherwise call up to super.
Override -valueForKey: and -setValue:forKey:.
You should probably only accept keys (element/attribute names) you recognize, and call up to super for other keys.
So I implemented Chris Hanson's suggestion and here's what I ended up with. I put this in my Utils class. It keeps a dictionary for each class that we lookup. It could probably use a little refactoring but it has worked very well for me so far.
static NSMutableDictionary *keyCache;
+ (NSString *)keyForClass:(Class)klass column:(NSString *)column {
if (!keyCache) { keyCache = [NSMutableDictionary dictionary]; }
NSString *className = NSStringFromClass(klass);
NSMutableDictionary *tableKeyCache = [keyCache objectForKey:className];
if (!tableKeyCache) {
tableKeyCache = [NSMutableDictionary dictionary];
unsigned int numMethods = 0;
Method *methods = class_copyMethodList(klass, &numMethods);
NSMutableArray * selectors = [NSMutableArray array];
for (int i = 0; i < numMethods; ++i) {
SEL selector = method_getName(methods[i]);
[selectors addObject:NSStringFromSelector(selector)];
}
[tableKeyCache setValue:selectors forKey:#"allSelectors"];
free(methods);
[keyCache setValue:tableKeyCache forKey:className];
}
NSString *keyToReturn = [tableKeyCache valueForKey:column];
if (!keyToReturn) {
for (NSString *columnKey in [tableKeyCache valueForKey:#"allSelectors"]) {
if ( [column caseInsensitiveCompare:columnKey] == NSOrderedSame) {
[tableKeyCache setValue:columnKey forKey:column];
keyToReturn = columnKey;
break;
}
}
}
if (!keyToReturn) { // Taking a guess here...
NSLog(#"Selector not found for %#: %# ", className, column);
keyToReturn = [Utils keyForClass:[klass superclass] column:column];
}
return keyToReturn;
}
Related
I have an array NSMutableArray where I save an MKuserlocation type - locationArray.
anyway now I want to get the data from this array and save it to an array from type CLLocationCoordinate2D.
but since everything I save in locationArray is from id type how can I get the coordinates from this and save it to the second array?
CLLocationCoordinate2D* coordRec = malloc(pathLength * sizeof(CLLocationCoordinate2D));
for(id object in locationArray){
for (int i = 0; i < pathLength; i++)
?????
I dont know if this even possible!
Thanks
Why do you need a c-style array of CLLocationCoordinate2D objects?
Here you go:
NSArray* userLocations; // contains your MKUserLocation objects...
CLLocationCoordinate2D* coordinates = malloc( userLocations.count * sizeof( CLLocationCoordinate2D) );
for ( int i = 0 ; i < userLocations.count ; i++ )
{
coordinates[i] = [[[userLocations objectAtIndex: i] location] coordinate];
}
Refering to Apple docs
You should certainly use CLLocationCoordinate2DMake function
with data from MKUserLocation or directly extract infos from MKUserLocation:
object.location.coordinate // it's a CLLocationCoordinate2D from your 'object' example
or
CLLocationCoordinate2DMake(object.location.coordinate.latitude, object.location.coordinate.longitude)
Hope this help.
The typical solution is to create a NSObject subclass and define a single property, a CLLOcationCoordinate2D. Instantiate and add those objects to your array.
#interface Coordinate : NSObject
#property (nonatomic) CLLocationCoordinate2D coordinate;
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate;
#end
#implementation Coordinate
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate
{
self = [super init];
if (self) {
_coordinate = coordinate;
}
return self;
}
#end
And then, because your locationArray is an array of MKUserLocation (which, itself, conforms to MKAnnotation), you can do:
NSMutableArray *path;
path = [NSMutableArray array];
for (id<MKAnnotation> annotation in locationArray)
{
// determine latitude and longitude
[path addObject:[[Coordinate alloc] initWithCoordinate:annotation.coordinate]];
}
Or make an array of existing object type, such as CLLocation or MKPinAnnotation or whatever.
Or if this array is a path to be drawn on the map, you might want to avoid using your own array, and instead make a MKPolyline.
NSInteger pathLength = [locationArray count];
CLLocationCoordinate2D polylineCoordinates[pathLength]; // note, no malloc/free needed
for (NSInteger i = 0; i < pathLength; i++)
{
id<MKAnnotation> annotation = locationArray[i];
polylineCoordinates[i] = annotation.coordinate;
}
MKPolyline *polyline = [MKPolyline polylineWithCoordinates:polylineCoordinates count:pathLength]
[self.mapView addOverlay:polyline];
It depends upon what the purpose of this is. But if you can use one of the previous constructs that avoids malloc and free, that's probably ideal. These techniques leverage Objective-C patterns which make it harder to leak, use an invalid pointer, etc.
I am a newbie so I apologise if I am missing something obvious.
I am trying to write an app in Xcode 4 to produce stats for my local sports team.
This is the relevant detail of the problem area of my programme:
NSError *error;
NSArray *games = [context executeFetchRequest:fetchRequest error:&error];
if (games == nil) {
NSLog(#"There was an error!");
}
int noOfBatOuts = games.count;
int noOfNotOuts=0;
for (NSManagedObject *oneMatch in games) {
if ([oneMatch valueForKey:#"batOut"]==#"NO") {
noOfBatOuts = noOfBatOuts - 1;
noOfNotOuts = noOfNotOuts + 1;
NSLog(#"Not Out!");
}
NSLog(#"How out %#",[oneMatch valueForKey:#"batOut"]);
}
notOuts.text=[NSString stringWithFormat:#"%d",(noOfNotOuts)];
NSLog(#"No of Not Outs is %#",notOuts.text);
When I run with data that has a not out string - NO, - the NSLog(#"Not Out!") is never called and the final NSLog(#"No of Not Outs...) reports zero. However I know that the data is there and it is identified in the middle NSLog(#"How Out....).
I am starting to tear my hair out in frustration and not having the knowledge yet to know the answer. Can anybody help please?
I would assume that batOut is a Boolean rather than string type, in which case you should be checking for truth rather than string equality.
If batOut really is a string, then you still can't compare strings this way. You need to use isEqual: or isEqualToString:. For example:
if ([[oneMatch valueForKey:#"batOut"] isEqual:#"NO"]) {
Normally you'd use isEqualToString: for string comparisons, but -valueForKey: is an id so isEqual: is safer.
The == operator checks that two pointers have the same value. Two strings can have the same contents but not be stored in the same memory.
You can't compare strings with ==. Use NSString's isEqualToString:
I often use Transformable for Core Data attributes, so I can change them later.
However, it seems like, if I want to use NSPredicate to find a NSManagedObject, using "uniqueKey == %#", or "uniqueKey MATCHES[cd] %#", it's not working as it should.
It always misses matching objects, until I change the attributes of the uniqueKey of the matching object to have specific class like NSString, or NSNumber.
Can someone explain the limitation of using NSPredicate with Transformable attributes?
Note: I'm not sure when/if this has changed since 5/2011 (from Scott Ahten's accepted answer), but you can absolutely search with NSPredicate on transformable attributes. Scott correctly explained why your assumptions were broken, but if Can someone explain the limitation of using NSPredicate with Transformable attributes? was your question, he implied that it is not possible, and that is incorrect.
Since the is the first google hit for "Core Data transformable value search nspredicate" (what I searched for trying to find inspiration), I wanted to add my working answer.
How to use NSPredicate with transformable properties
Short, heady answer: you need to be smart about your data transformers. You need to transfrom the value to NSData that contains what I'll call "primitive identifying information", i.e. the smallest, most identifying set of bytes that can be used to reconstruct your object. Long answer, ...
Foremost, consider:
Did you actual mean to use a transformable attribute? If any supported data type -- even binary data -- will suffice, use it.
Do you understand what transformable attributes actually are? How they pack and unpack data to and from the store? Review Non-Standard Persistent Attributes in Apple's documentation.
After reading the above, ask: does custom code that hides a supported type "backing attribute" work for you? Possibly use that technique.
Now, past those considerations, transformable attributes are rather slick. Frankly, writing an NSValueTransformer "FooToData" for Foo instances to NSData seemed cleaner than writing a lot of adhoc custom code. I haven't found a case where Core Data doesn't know it needs to transform the data using the registered NSValueTransformer.
To proceed simply address these concerns:
Did you tell Core Data what transformer to use? Open the Core Data model in table view, click the entity, click the attribute, load the Data Model Inspector pane. Under "Attribute Type: Transformable", set "Name" to your transformer.
Use a default transformer (again, see the previous Apple docs) or write your own transformer -- transformedValue: must return NSData.
NSKeyedUnarchiveFromDataTransformerName is the default transformer and may not suffice, or may draw in somewhat-transient instance data that can make two similar objects be different when they are equal.
The transformed value should contain only -- what I'll call -- "primitive identifying information". The store is going to be comparing bytes, so every byte counts.
You may also register your transformer globally. I have to do this since I actually reuse them elsewhere in the app -- e.g. NSString *name = #"FooTrans"; [NSValueTransformer setValueTransformer:[NSClassFromString(name) new] forName:name];
You probably don't want to use transforms heavily queried data operations - e.g. a large import where the primary key information uses transformers - yikes!
And then in the end, I simply use this to test for equality for high-level object attributes on models with NSPredicates -- e.g. "%K == %#" -- and it works fine. I haven't tried some of the various matching terms, but I wouldn't be surprised if they worked sometimes, and others not.
Here's an example of an NSURL to NSData transformer. Why not just store the string? Yeah, that's fine -- that's a good example of custom code masking the stored attribute. This example illustrates that an extra byte is added to the stringified URL to record if it was a file URL or not -- allowing us to know what constructors to use when the object is unpacked.
// URLToDataTransformer.h - interface
extern NSString *const kURLToDataTransformerName;
#interface URLToDataTransformer : NSValueTransformer
#end
...
// URLToDataTransformer.m - implementation
#import "URLToDataTransformer.h"
NSString *const kURLToDataTransformerName = #"URLToDataTransformer";
#implementation URLToDataTransformer
+ (Class)transformedValueClass { return [NSData class]; }
+ (BOOL)allowsReverseTransformation { return YES; }
- (id)transformedValue:(id)value
{
if (![value isKindOfClass:[NSURL class]])
{
// Log error ...
return nil;
}
NSMutableData *data;
char fileType = 0;
if ([value isFileURL])
{
fileType = 1;
data = [NSMutableData dataWithBytes:&fileType length:1];
[data appendData:[[(NSURL *)value path] dataUsingEncoding:NSUTF8StringEncoding]];
}
else
{
fileType = -1;
data = [NSMutableData dataWithBytes:&fileType length:1];
[data appendData:[[(NSURL *)value absoluteString] dataUsingEncoding:NSUTF8StringEncoding]];
}
return data;
}
- (id)reverseTransformedValue:(id)value
{
if (![value isKindOfClass:[NSData class]])
{
// Log error ...
return nil;
}
NSURL *url = nil;
NSData *data = (NSData *)value;
char fileType = 0;
NSRange range = NSMakeRange(1, [data length]-1);
[data getBytes:&fileType length:1];
if (1 == fileType)
{
NSData *actualData = [data subdataWithRange:range];
NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
url = [NSURL fileURLWithPath:str];
}
else if (-1 == fileType)
{
NSData *actualData = [data subdataWithRange:range];
NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
url = [NSURL URLWithString:str];
}
else
{
// Log error ...
return nil;
}
return url;
}
#end
Transformable attributes are usually persisted as archived binary data. As such, you are attempting to compare an instance of NSData with an instance of NSString or NSNumber.
Since these classes interpret the same data in different ways, they are not considered a match.
you can try this way
NSExpression *exprPath = [NSExpression expressionForKeyPath:#"transformable_field"];
NSExpression *exprKeyword = [NSExpression expressionForConstantValue:nsdataValue];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:exprPath rightExpression:exprKeyword modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];
I'm trying to compare two strings
NSString strOne = #"Cat, Dog, Cow";
NSString strTwo = #"Cow";
How do I determine if strOne contains strTwo
Try using rangeOfString:
NSRange result = [strOne rangeOfString:strTwo];
From the documentation:
Returns an NSRange structure giving the location and length in the receiver of the first occurrence of aString. Returns {NSNotFound, 0} if aString is not found or is empty (#"").
For anyone needing the code to check is a string exists within a string, here's my code thanks to fbrereto. This example checks to see if any string contained in an array of strings (stringArray) can be found within a string (myString):
int count = [stringArray count];
for (NSUInteger x = 0; x < count; ++x) {
NSRange range = [self.myString rangeOfString:[stringArray objectAtIndex:x]];
if (range.length > 0) {
// A match has been found
NSLog(#"string match: %#",[stringArray objectAtIndex:x]);
}
}
I believe this is the correct syntax for checking if the range exists (correcting response from Kendall):
range.location != NSNotFound
Gradually straying off topic, but I always explode my strings, which would mean just exploding it using your search string as a key and you can use the array count to see how many instances you have.
Just incase anyone is coming from a code language that uses "explode" to blow a string up into an array like me, I found writing my own explode function tremendously helpful, those not using "explode" are missing out:
- (NSMutableArray *) explodeString : (NSString *)myString key:(NSString*) myKey
{
NSMutableArray *myArray = [[NSMutableArray alloc] init];
NSRange nextBreak = [myString rangeOfString:myKey];
while(nextBreak.location != NSNotFound)
{
[myArray addObject: [myString substringToIndex:nextBreak.location]];
myString = [myString substringFromIndex:nextBreak.location + nextBreak.length];
nextBreak = [myString rangeOfString:myKey];
}
if(myString.length > 0)
[myArray addObject:myString];
return myArray;
}
works like this:
[self explodeString: #"John Smith|Age: 37|Account Balance: $75.00" key:#"|"];
which will return this array:
[#"John Smith", #"Age: 37", #"Account Balance: $75.00"];
This lets you quickly pull out a specific value in a tight space, Like if you have a client and you want to know how much money he has:
[[self explodeString: clientData key: pipe] objectAtIndex: 1];
or if you wanted specifically the dollar amount as a float:
[[[self explodeString: [[self explodeString: clientData key: pipe] objectAtIndex: 1] key: #": "] objectAtIndex: 2] floatValue];
anyway I find arrays way easier to work with and more flexible, so this is very helpful to me. Additionally with a little effort you could make an "explodable string" data type for your private library that lets you treat it like a string or return an index value based on the key
ExplodableString *myExplodableString;
myExplodableString.string = #"This is an explodable|string";
NSString *secondValue = [myExplodableString useKey: #"|" toGetValue: index];
I have a single NSDictionary object which contains a large number of custom objects. The objects will either be of class B or of class C, both of which inherit from class A. If the objects are of type B, they will have an internal flag (kindOfCIsh) which will be used for future grouping.
How can I, at different times in my program, get an NSDictionary (or NSArray) that contains different groupings of those objects? In one case, I will want all of B, but another time I will want all of the C objects, plus the B objects that satisfy (kindOfCIsh == true).
Is there a simple way to get access to these subsets? Perhaps using filter predicates? I can, of course, loop through the entire dictionary and build the required subset manually, but I have a feeling that there is a better way.
Any help is appreciated.
[[myDictionary allValues] filteredArrayUsingPredicate: pred];
You can use categories
the code is something like this
#interface NSDictionary (dictionaryForClass)
-(NSMutableDictionary *) dictionaryWithObjectsKindOfClass:(Class)myClass;
#end
#implementation NSDictionary (dictionaryForClass)
-(NSMutableDictionary *) dictionaryWithObjectsKindOfClass:(Class)myClass;
{
NSMutableDictionary *ret = [[[NSMutableDictionary alloc] init] autorelease];
for (id object in self) {
if ([object isKindOfClass:myClass]) {
[ret addObject:object];
}
}
return ret;
}
#end