I am attempting to instantiate classes from different XML sources. The cases of the class names are inconsistent (camel, upper, lower) across these sources. Does an equivalent to NSClassFromString exist for this? For example, something allowing for:
Person *person = [[NSClassFromCaseInsensitiveString("Person") alloc] init];
Person *person = [[NSClassFromCaseInsensitiveString("person") alloc] init];
Person *person = [[NSClassFromCaseInsensitiveString("PERSON") alloc] init];
make a NSString case insensitive by using any of the capitalization methods in NSString (-capitalizedString for instance) then use it as the param for NSClassFromString
Edit: here is code based on your Person example. (Assuming stringFromXML is a NSString that you parsed from your XML code that could contain any form of the word "person"
NSString *personString = stringFromXML;
Person *person = NSClassFromString([personString capitalizedString]);
You're probably stuck with searching the class list, obtainable from objc_getClassList
Please don't do this. You're letting arbitrary (I'm assuming downloaded-over-HTTP) XML instantiate arbitrary classes in your app. This is bad, and it's difficult to know how bad because it can instantiate any class loaded in your app (including ones in private frameworks).
Consider using something like
NSDictionary * classesByLowercaseString = [NSDictionary dictionaryWithObjectsAndKeys:
[Person class], #"person",
[Blah class[, #"blah",
nil];
[classesByLowercaseString objectForKey:[xmlClassName lowercaseString]];
Also note that XML tag names are supposed to be case-sensitive.
You could use the NSString capitalization methods, if you are guaranteed that the class name will look like that. A general function as you describe is not available, and likely won't be, for the simple reason that class names are not case insensitive.
#interface Person : NSObject
{
}
#end
#interface PERSON : NSObject
{
}
#end
will work and declare two different class types. Though in all honesty, having classes with identical names except case would just be bad style in the first place.
Related
I want to create a "business object" based on a NSDictionary. The reason for this is that I want implementations to be able to extend this object with arbitrary keys, and another reason is that I am persisting it using the convenient plist format (the objects stored are either integers, floats or strings).
The business object contains a number of predefined properties, e.g.
#property NSString* customerName;
#property NSString* productCode;
#property int count;
#property double unitPrice;
I want to serialize this, for example to a property list (this is not a strict requirement, it could be some other easy-to-use format). Preferably, the implementation of the class should be just
#synthesize customerName, productCode, count, unitPrice:
for the example above. To use this class, I want to do something like:
MyBusinessObject* obj = [MyBusinessObject businessObjectWithContentsOfFile:fileName];
obj.productCode = #"Example";
[obj setObject:#"Some data" forKey:#"AnExtendedProperty"];
[obj writeToFile:fileName atomically:YES];
You should make your class KVC complaint. KVC does the magic. Look here.Ex,
// assume inputValues contains values we want to
// set on the person
NSDictionary * inputValues;
YOURCLASS * person = [[YOURCLASS alloc] init];
[person setValuesForKeysWithDictionary: inputValues];
The "path of least resistance" turned out to be using NSCoding instead.
i have a class abcParser in which i have an array arrayXYZ. Now i need to use this array in a viewController.. when i do so it gives an error.. arrayXYZ undeclared
i m weak in inheritance, please help!
thanks!!
Declare a function in ABCParser that lets you retrieve the array, like so:
-(NSArray *)arrayXYZ:(NSString *)anArgument{
NSArray *array = [NSArray arrayWithObject:anArgument];
return array;
}
EDIT:
Alright, seems like we need a little lesson on instantiation etc...
Now, those Plus (+) and Minus (-) signs beside method names...
Plus is a class method. It can be used without instantiation, but understand that variables cannot be carried over when you use these.
Instance methods require you to instantiate a new object, then ask it to perform methods.
ABCParser *parser = [[ABCParser alloc] init];
That creates a new 'instance' of ABCParser and makes a pointer to it with the name 'parser'. That's all fine and dandy.
When we want this new parser object to do something, we tell it as normal:
NSArray *myNewArray = [parser arrayXYZ];
So - When you have an instance of your class, do this:
ABCParser *parser = [[ABCParser alloc] init];
[parser generateArrayXYZ];
NSArray *array = [parser getArrayXYZ];
This will give you an object that you can work with. It'll take a bit of getting used to, but you'll learn. If you need any further explanation, just post :)
Did you already add #property and #syntesize to your NSArray variable in abcParser?
I’m making a languages application, and I have a long list of vocabulary relating to that language (German, in case anyone was interested). I have the functionality in my app to switch between sorting the tableview by German words, or by english words.
When I use the following:
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:type];
NSString *string = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSArray *array = [[string componentsSeparatedByString:#"\n"] sortedArrayUsingSelector:#selector(compare:)];
it works absolutely perfectly - by which I mean, exactly as expected. What I would like to improve on this however, is that there are certain words, such as verbs or nouns, which are always preceded by prefixes, like “to”, as in “to do something”, or “the” in front of nouns. So what I would like to do is somehow exclude these from my sort, because otherwise I end up with all the verbs being sorted alphabetically under the “t” section in my array, which is not very user friendly.
I’ve looked through the Apple documentation about NSString and NSArray, as this is where the compare function is (unless I’m very much mistaken), and I haven’t found any way that makes sense to me. This is the first time I have done any data handling like this so I may be missing something simple, and so I would really appreciate some help.
Thanks very much
Michaeljvdw
You're on the right track. What you want to use instead of the (built-in) compare method is to write your own method, which can eliminate the "to" or "the" bits if they exist, and then use the existing compare method.
Your call would look something like this:
NSArray *array = [[string componentsSeparatedByString:#"\n"] sortedArrayUsingSelector:#selector(myCompare:)];
Using a custom category you give to NSString with the following methods:
// This method can be exposed in a header
- (NSComparisonResult)myCompare:(NSString*)aString
{
NSString* selfTrimmed = [self removeArticles];
NSString* aStringTrimmed = [s2 removeArticles];
return [self compare:aString];
}
// This method can be kept private in the .m implementation
- (NSString*)removeArticles
{
NSRange range = NSMakeRange(NSNotFound, 0);
if ([self hasPrefix:#"to "])
{
range = [self rangeOfString:#"to "];
}
else if ([self hasPrefix:#"the "])
{
range = [self rangeOfString:#"the "];
}
if (range.location != NSNotFound)
{
return [self substringFromIndex:range.length];
}
else
{
return self;
}
}
You might have some luck with localizedCompare: or localizedStandardCompare:, but I don't think that either of these will strip out articles and prepositions like you want. Instead, you will probably have to define a category on NSString that provides the specific style of sorting you're looking for:
#interface NSString (MySortAdditions)
- (NSComparisonResult)compareWithoutArticles:(NSString *)other;
#end
#implementation NSString (MySortAdditions)
- (NSComparisonResult)compareWithoutArticles:(NSString *)other {
NSMutableString *mutableSelf = [NSMutableString stringWithString:self];
[mutableSelf
replaceOccurrencesOfString:#"das"
withString:#""
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [mutableSelf length])
];
...
// delete articles from 'other' too
NSCharacterSet *trimSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSString *trimmedSelf = [mutableSelf stringByTrimmingCharactersInSet:trimSet];
NSString *trimmedOther = ...;
return [trimmedSelf localizedCaseInsensitiveCompare:trimmedOther];
}
#end
You can then use #selector(compareWithoutArticles:) as your sort selector for NSArray.
First, don't use compare:. Use localizedCompare: instead. This is important, because whether á appears just after a or after z as a separate letter depends on the language. localizedCompare: takes care of that.
--edit
As Justin says, localizedStandardCompare: is the selector to be used! I didn't know that method. As written in the documentation, localizedStandardCompare: does more than localizedCompare:, although the document doesn't say exactly what it does.
--end of edit
If you want more, you need to implement that yourself. You can use category for that purpose. First declare it
#interface NSString (MichaelsSuperCompareCategory)
-(NSComparisonResult)michaelsSuperCompare:(NSString*)string;
#end
and then implement it
#interface NSString (MichaelsSuperCompareCategory)
-(NSComparisonResult)michaelsSuperCompare:(NSString*)string{
...
}
#end
This way you can add methods to an existing class. Then you can use
NSArray *array = [[string componentsSeparatedByString:#"\n"]
sortedArrayUsingSelector:#selector(michaelsSuperCompare:)];
It is important to prefix the method name with something distinctive, not to accidentally crash with internal methods used by Apple.
As for the functionality, you need to implement that yourself, as far as I know. You can get the current locale with [NSLocale currentLocale]. You can implement a nicer behavior for the languages you know, and then default to localizedCompare: for unknown languages.
I would somehow do -replaceOccurancesOfStrings on all the data eg "To" -> "" - and then reload the data. (or this can in a text editor)
Another thing to think about is having eg 'to walk' changed to 'walk (to)' which can be done ahead of time (and will also create less confusion for the user as they are scrolling alphabetically).
I have the following array.
NSArray *arrayDisplay = [[NSArray alloc] initWithObjects:#"Daily",
#"Weekly", #"Monthly", nil];
I need to use it in two views, I'm concerned that I may make changes and forget to change the other in the future. So I'd like to declare it once and reuse it.
How should I do this?
You can keep it as a property in a common object such as the application delegate.
Assuming its nonatomic,retain type property then access it like:
myAppDelegate *del = [[UIApplication sharedApplication] delegate];
del.arrayDisplay = [NSArray arrayWithObjects:#"Daily",#"Weekly", #"Monthly", nil];
Although if you plan on changing it you might want an NSMutableArray.
Consider writing a class method or even a C function that lazily creates the array. For example, here's a class method that does what you want:
+ (NSArray *)frequencyChoices
{
static NSArray *choices;
if (choices == nil)
{
choices = [[NSArray alloc] initWithObjects:
#"Daily", #"Weekly", #"Monthly", nil];
}
return choices;
}
Writing the same functionality as a C function makes it even more general:
NSArray *frequencyChoices(void)
{
static NSArray *choices;
if (choices == nil)
{
choices = [[NSArray alloc] initWithObjects:
#"Daily", #"Weekly", #"Monthly", nil];
}
return choices;
}
The advantage of a class method though, is that you could override it in a subclass if that might ever prove handy.
If your application is simple, consider the Singleton model where the data you are accessing are accessible through the global instance of the singleton.
link text
Rather than using a Singleton approach, consider determining which object in your app's object hierarchy should own this array, and then pass that reference down to where it's needed (see: dependency injection).
You could save the array as a plist in your project and load it in each place where it's needed with
NSArray *arrayDisplay = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"myArrayName" ofType:#"plist"]];
This will work as long as you do not need to change the values in both place while the program is actually running.
If you don't mind global variables, you can instantiate them at load time:
#interface FooClass : ... {
...
}
static NSArray * FooClass_timescale;
#end
#implementation FooClass
+(void)load {
FooClass_timescale = [[NSArray alloc] initWithObjects:#"Daily", #"Weekly", #"Monthly", nil];
}
#end
I prefer doing this when there are a pile of things I want to instantiate (e.g. colours for a theme/skin/brand/whatever), since it's shorter than writing a function to return them. Of course, it's possible to accidentally modify the global variable, but I've never managed this (usually I just forget to retain).
I'm fetching some objects out of a data store but the results aren't what I'm expecting. I'm new to CoreData but I'm fairly certain this should work. What am I missing?
Note that User is a valid managed object and that I include its header file in this code, and that UserID is a valid property of that class.
NSFetchRequest *requestLocal = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"User" inManagedObjectContext:messageManagedObjectContext];
[requestLocal setEntity:entity];
// Set the predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY UserID IN %#", userList];
[requestLocal setPredicate:predicate];
// Set the sorting
... sorting details removed but exist and are fine ...
// Request the data
NSArray *fetchResults = [messageManagedObjectContext executeFetchRequest:requestLocal error:&error];
[requestLocal release];
for (int i; i < [fetchResults count]; i++) {
[fetchResults objectAtIndex:i].UserID = ...<----HERE
}
Isn't fetchResults an array of User objects? Wouldn't [fetchResults objectAtIndex:i] be a User object? Why do I get an error when building that "request for member 'UserID' in something not a structure or union"?
Sorry if this is a basic error, I'm clearly missing some basic concept. I've done a ton of searching and it seems like it should be right. (I also tried fast enumeration but it complained that fetchResults items weren't valid Objective C objects, effectively the same error, I think.)
Update:
(from comment below)
My goal is to update the object, calling saveAction after changing it.
Does the KVC method still refer to the actual object? I tried fast enumeration with:
for (User thisUser in fetchResults) {
... but it didn't like that.
I used the more generic version:
(id thisUser in fetchResults)
...but it won't let me set
[thisUser valueForKey:#"FirstName"] = anything
... insisting that there's no Lvalue.
Will:
[[thisUser valueForKey:#"FirstName"] stringWithString:#"Bob"]
... do the trick or is there a better way? Sorry, I know it's nearly a new question, but I still don't get what is in the fetchResults array.
Your fetchedResults variable contains a NSArray object. However, a NSArray can hold any arbitrary group of objects. Unlike a standard C array, there is no requirement that the NSArray objects all be of a single class.
The dot notation you are using here:
[fetchResults objectAtIndex:i].UserID =
... while a legal syntax, nevertheless confuses the compiler because the compiler has no idea what class of object is returned by [fetchResults objectAtIndex:i]. Without knowing the class it has no idea what the heck UserID is. Hence the error "request for member 'UserID' in something not a structure or union". At the very least you have to cast the return of [fetchResults objectAtIndex:i] to some class so that the complier has a clue as to what 'UserID' is.
However, you simply shouldn't use this construction even though it legal because it is dangerous. See below for the best practice form.
Understanding NSManagedObject and its subclasses can be tricky because NSManagedObject itself uses a trick called associative storage which allows any generic NSManagedObject instances to store any property of any entity defined in any model. This can confuse novices because there are multiple ways to refer to the same entities, instances and properties. Sometimes the examples use generic NSMangedObjects and setValue:forKey:/valueForKey: and other times they use objectInstance.propertyName.
Associative storage works like a dictionary attached to every instance of the NSManagedObject class. When you insert a generic NSManagedObject like this:
NSManagedObject *mo=[NSEntityDescription insertNewObjectForEntityForName:#"User"
inManagedObjectContext:self.managedObjectContext];
... you get an instance of the NSManageObject class whose associative storage keys are set to the properties of the User entity as defined in your data model. You can then set and retrieve the values using key-value coding (which has the same syntax as dictionaries) thusly:
[mo setValue:#"userid0001" forKey:#"UserID"];
NSString *aUserID=[mo valueForKey:#"UserID"];
Associative storage allows you represent any complex data model in code without having to write any custom NSManagedObject subclasses. (In Cocoa, it allows you to use bindings which let you create entire programs without writing any data management code at all.)
However, the generic NSManagedObject class is little better than a glorified dictionary whose saving and reading is handled automatically. If you need data objects with customized behaviors you need to explicitly define a NSManagedObject subclass. If you let Xcode generate the class from the entity in the data model you end up with a source file something like:
User.h
#interface User : NSManagedObject
{
}
#property (nonatomic, retain) NSString * firstName;
#property (nonatomic, retain) NSString * userID;
#property (nonatomic, retain) NSString * lastName;
#end
User.m
#import "User.h"
#implementation User
#dynamic firstName;
#dynamic userID;
#dynamic lastName;
#end
Now, you are no longer limited by to the key-value syntax of associative storage. You can use the dot syntax because the complier has a class to refer to:
User *aUser=[NSEntityDescription insertNewObjectForEntityForName:#"User"
inManagedObjectContext:self.managedObjectContext];
aUser.userID=#"userID0001";
NSString *aUserID=aUser.userID;
With all this in mind, the proper forms of reference to the fetchedResults array become clear. Suppose you want to set all userID properties to a single default value. If you use the generic NSManagedObject class you use:
for (NSManagedObject *aMO in fetchedResults) {
[aMO setValue:#"userid0001" forKey:#"UserID"];
NSString *aUserID=[aMO valueForKey:#"UserID"];
}
If you use a dedicated subclass you would use:
for (User *aUserin fetchedResults) {
aUser.userID=#"userID0001";
NSString *aUserID=aUser.userID;
}
(Note: you can always use the generic form for all NSManagedObject subclasses as well.)
Accessing your CoreData attributes by property Accessors (dot notation) will only work if you have defined a custom NSManagedObject subclass in your Model and defined properties on that class. The implementation should be #dynamic. You'll then have to cast the object to the proper class:
//Assume this exists:
#interface User : NSManagesObject
{
}
#property (nonatomic, retain) NSString* UserID;
#end
#implementation User
#dynamic UserID
#end
// You could do:
for (int i; i < [fetchResults count]; i++) {
((User*)[fetchResults objectAtIndex:i]).UserID = ... // This works
}
Or you may use KVC to access your models properties like this (without needing a class):
for (int i; i < [fetchResults count]; i++) {
[[fetchResults objectAtIndex:i] valueForKey:#"UserID"] = ... // This too
}
You would set the value using [object setValue:newValue forKey:#"UserID"] please note, that newValue needs to be an object in general and one of NSString, NSNumber, NSDate, NSSet for CoreData.
Two additional thoughts:
Your could and should use fast Enumeration on the results array:
for (id object in fetchResults) {
[object valueForKey:#"UserID"] = ...
}
I do not understand the ANY keyword in your predicate. "UserID IN %#" should do as well.
Your basic problem is that -objectAtIndex: returtns an object of type id. No accessors are defined for type id so when you use dot notation with the object returned by -objectAtIndex: the compiler assumes you mean to access a C structure member. id is a pointer type, not a structure type, hence the error you are getting.
The whole core data stuff is a red herring with regard to this issue. You'd get the same error if User was derived from NSObject and you had populated the array yourself manually.
The ways out of it are:
Use fast enumeration
for (User* aUser in theArray)
{
....
}
which is the preferred idiom if you need to iterate through the whole array
Cast the result of -objectAtIndex: to the correct type.
((User*)[theArray objectAtIndex: i]).userId;
Use the message sending syntax instead of dot notation
[[theArray objectAtIndex: i] setUserId: ...];
Personally, I'd go with 1 and 3.
for (User* aUser in theArray)
{
[aUser setUserId: ...]
}
Clearly any of the above are dangerous if you are not certain that the objects in the array are User objects. You can use -respondsToSelector: to make sure it will work if you like.