I have a .plist file like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>key1</key>
<string>variable1</string>
<key>key2</key>
<array>
<string>foobar</string>
<string>variable2</string>
</array>
</dict>
</plist>
My code looks like this:
NSString *variable1 = #"Hello";
NSString *variable2 = #"World!";
NSMutableDictionary *myDict = [NSMutableDictionary dictionaryWithContentsOfFile:#"/path/to/file.plist"];
NSLog(#"key1: %#", [myDict valueForKey:#"key1"]);
NSLog(#"key2: %#", [[myDict valueForKey:#"key2"] objectAtIndex:1]);
// outputs
// key1: variable1
// key2: variable2
What I'm hoping to do is have the variable1 and variable2 in the .plist file use the variable1 and variable2 from the code (without specifically doing it in code), so the values from the .plist end up being Hello for variable1 and World! for variable2.
Obviously assigning them like this is possible:
[myDict setObject:variable1 forKey:#"key1"];
That's exactly what I'm trying to avoid. Clearly the "variables" are merely strings in the .plist and obj-c doesn't think to assign them the values of the variables from the code. So the question is can I assign them the values from the code (like <variable>variable1</variable>), or somehow not make them strings? Or maybe this just isn't possible.
Note: I'm not a programmer, so please keep this in mind, thank you.
So the question is can I assign them the values from the code (like variable1), or somehow not make them strings?
Many ways, but...
Note: I'm not a programmer, so please keep this in mind, thank you.
which presents obvious challenges! We've no idea how much you actually know, you've presented no attempt to do the actual substitution, etc. However you do make a good observation:
Clearly the "variables" are merely strings in the .plist and obj-c doesn't think to assign them the values of the variables from the code.
Many beginners, and a few who aren't, confuse the difference between the character sequence variable1 used as the name for a variable and the same sequence used as the value for a string – you correctly identify these are different things.
Let's see if we can help you along without a long lesson on program design. First we'll stick with your use of a plist for your dictionary (later given your suggestion of <variable>variable1</variable> you may wish to consider using your own XML).
Second we'll make the assumption that when a "variable" is used as a value in your dictionary it will always be the whole value and not part of it, e.g. you'll never have something like <key>key1</key> <string>The value of the variable is: variable1</string> and expect variable1 to be replaced.
We start by modifying your code to read the plist from the app bundle and using modern Objective-C syntax to look up dictionary and array values:
NSURL *sampleURL = [NSBundle.mainBundle URLForResource:#"sampleDictionary" withExtension:#"plist"];
NSMutableDictionary *myDict = [NSMutableDictionary dictionaryWithContentsOfURL:sampleURL];
NSLog(#"key1: %#", myDict[#"key1"]); // dictionary lookup
NSLog(#"key2: %#", myDict[#"key2"][1]); // dictionary + array lookup
The file sampleDicitonary.plist has been added to the add and contains your plist. This outputs:
key1: variable1
key2: variable2
just as your code does.
Now instead of declaring your two variables, variable1 and variable2, you can use a dictionary where the key is the variable name:
NSDictionary *variables = #{ #"variable1": #"Hello",
#"variable2": #"World!"
};
This represents the same information as your two distinct variables but crucially the variable names are just strings, and as you've already observed your plist contains strings – so now we have a problem of string substitution rather than variable substitution. To do the string substitution we utilise:
A key lookup in a dictionary, e.g. the myDict[#"key1"] above, returns nil if the key does not exist in the dictionary;
The expression <expr1> ?: <expr2> evaluates to the value of a <expr1> if the value is not nil, others it evaluates to <expr2> (This is a contraction of the conditional expression – which you can look up.)
So if we assign the result of indexing into your plist to variable:
NSString *rawKeyValue = myDict[#"key1"];
and then look that value up in our variables dictionary:
NSString *substitutedValue = variables[rawKeyValue];
then if rawKeyValue has a value of one of the names your "variables" (variable1, variable2) then substitutedValue will be the corresponding value (Hello, World!). If rawKeyValue is not a variable name then substitutedValue will be nil...
But you don't want nil in the second case, you want the value of rawKeyValue and you can get that by utilising the ?: operator which gives a replace of the first NSLog() of:
NSString *rawKeyValue = myDict[#"key1"];
NSString *substitutedValue = variables[rawKeyValue] ?? rawKeyValue;
NSLog(#"key1: %#", substitutedValue);
which will output:
key1: Hello
Doing these extra steps for every lookup is repetitive and tedious, and programming has a solution to that: define a method to encapsulate it and avoid using the intermediate variables. Here is a possible method:
- (NSString *)substitute:(NSDictionary *)variables in:(NSString *)value
{
return variables[value] ?: value;
}
and with that defined the first NSLog becomes:
NSLog(#"key1: %#", [self substitute:variables in:myDict[#"key1"]]);
Hope that helps and gets you started. But do spend time learning programming properly if you're going to write programs!
Related
I'm a total newbie to Objective-C and have been tasked with an assignment to compare 2 builds of same app for differences in their Info.plist and Defaults.plist.
I have been able to figure out the steps to read the PLists from app bundle but am having difficulty figuring out how to compare EVERY key in PLists to its counterpart file. For illustration if I need to compare Info.plist between 2 app bundle (lets say build_100 and build_101), how do I recursively go to each key in build_100 and compare the same key in build_101 to verify if they are same or not.
Its easy if both PLists are same because isEqualToDictionary will return TRUE but problem occurs if something in a nested dictionary is different between both the builds.
Going through related queries here, it clear to me that the answer is that I write a recursive method that iterates through both PLists but I'm having a real frustrating time to figure out a way to do this for a nested dictionary like Info.plist.
So I've finally figured this thing out so thought of sharing it with others for future reference. I'm sure there'll be some other lost soul in future looking for something similar (or at least I hope :)).
The way I wrote my code was to:
Read both Plists in NSDictionaries
Treat one Plist as "to be tested" and other as the reference (to compare against) to find out if its a Pass/Fail
Loop through all keys in "to be tested" Plist and compare each one of them in "reference" Plist
When it came to compare an Array or Dictionary, this check (that's the part I was struggling with) had to be a recursive check
The code to write for step #1, 2, 3 is straight forward so I'm going to give the method I wrote for #4 which was the crux of my original question.
This function compareSourceObject() will take 3 arguments:
sourceObject: object to be tested
targetObject: object to compare against
trailPath: string that'll hold the entire path of the key that has failed
- (void)compareSourceObject:(id)sourceObject andTargetObject:(id)targetObject withBreadcrumbTrail:(NSString *)trailPath{
NSString *message = [[NSString alloc] init];
if ([sourceObject isKindOfClass:[NSDictionary class]]){
for(id item in sourceObject){
[self compareSourceObject:[sourceObject objectForKey:item] andTargetObject:[targetObject objectForKey:item] withBreadcrumbTrail:[trailPath stringByAppendingFormat:#"->%#", item]];
}
}
else if ([sourceObject isKindOfClass:[NSArray class]]){
for (int counter=0; counter %d", counter]];
}
}
else if(![sourceObject isEqual:targetObject]){
NSLog(#"Values do not match. Value in \"TestedDicationary\" is (%#) but the reference dict has (%#)", targetObject, sourceObject);
}
}
Hope this helps. Comments/Suggestions/Optimizations are more than welcome.
Take one plist, and interpret the properties as a set (NSSet) of string values, e.g.
:items:0:assets array
:items:0:assets:0:kind string VALUE
Note I am using /usr/libexec/PlistBuddy format to describe a property - path type [value].
Then do the same for the second plist and compare the sets using NSSet functions.
I'm new to Objective-C and need help with the concept of pointers. I've written this code:
//myArray is of type NSMutableArray
NSString *objectFromArray = [myArray objectAtIndex:2];
[objectFromArray uppercaseString];
I assumed that this would change the string at myArray[2] since I got the actual pointer to it. Shouldn't any changes to the dereferenced pointer mean that the object in that location changes? Or does this have something to do with 'string immutability'? Either way, when I use NSLog and iterate through myArray, all the strings are still lowercase.
Shouldn't any changes to the dereferenced pointer mean that the object in that location changes?
Yes, they would. But if you read the documentation for uppercaseString, you see that it does not modify the string in place. Rather, it returns a new uppercase version of the original string. All methods on NSString work like that.
You would need an instance of NSMutableString to be able to modify its contents in place. But NSMutableString does not have a corresponding uppercase method, so you would have to write it yourself (as a category on NSMutableString).
of course!! no string in the array will be converted to uppercase as the statement [objectFromArray uppercaseString]; would have returned the uppercase string which was not collected in any object though. "uppercaseString" does not modify the string object itself with which is is called...!!
I'm writing an iPhone application that uses a lot of editable text fields. I've been learning a lot about UITextFields and NSStrings by reading various references online, but there are some details that still elude me. When a user puts in an incorrect value for one of my text fields, I throw up an error message and put the text field back to the way it was before their input. For empty text fields, I've been doing this:
theTextField.text = #"";
Is this the best way to do this? I just came up with the idea myself, I don't know if there are any problems with it (other than the fact that it seems to work just fine so far).
Also, does #"" have the same value as a "nil" string? In other words, if I set a string to #"" and then call this:
if (myString) {...}
will the statement return true or false?
One last thing. When an NSString is initialized using this:
NSString *myString = [[NSString alloc] init];
what is that string's Length value?
The important thing to understand here is that an NSString with no characters in it, such as #"" or [[NSString alloc] init] is still a valid object. All the consequences that Nick has stated follow from that.
In Objective-C, any valid object will be "True" in a boolean context;* nil is the only false object value.
Since these strings are valid objects, they do have a length, but because they contain no characters, the length is 0.
There are no problems with assigning an empty string object #"" to another string pointer, such as the text of your text field. Since the string with no characters is still a valid NSString object, this is exactly the same as assigning a string which does happen to have characters.
*Unlike so-called "scripting" languages like Python or Perl, where an empty string or collection evaluates to boolean false.
Using
theTextField.text = #"";
is absolutely ok. There should be no problems at all.
if (#"")
will evaluate to true. #"" is not the same as nil.
The length of
NSString *myString = [[NSString alloc] init];
is 0.
This is not an answer to the question, but may be the answer to what you're trying to do.
If you're wondering whether you have to write if(str && str.length) to cover both nil and empty strings, you don't. You may use just if(str.length), since, in Objective-C, unknown messages to nil will return nil (so [a.b.c.d.e.f doStuff] will be nil if any of those values in the chain is nil). There is thus scarce need for specific nullity checks, unless what you want is precisely to determine nullity.
Check NSString's + string.
so I'm having the most difficult of time pulling values out of an NSDictionary. Right now I just have a dictionary that is populated from a JSON call and it only contains a key named 'Success' with a value of 0 or 1.
How do I do a conditional on that value to check if its 0 or 1? I've tried a bunch of things, but I'm not getting anywhere. Here's my current code:
[[jsonDictionary objectForKey:#"Success"] isEqualToNumber:1]
I'm getting passing argument 1 of 'isEqualToNumber:' makes pointer from integer without a cast' as a warning, and the app crashes when it hits that line anyway.
And a subquestion, what's the difference between objectForKey and valueForKey? Which one should I use by default?
Anyway, this noob in Objective-C would truly appreciate some help on this. Thanks in advance!
Since dictionaries contain Objective-C objects, an entry containing a number is an NSNumber instance. NSNumber provides a convenience method, -intValue, for extracting its underlying int value:
if ([[jsonDictionary objectForKey:#"Success"] intValue] == 1) { … }
Note that NSNumber has other convenience methods for extracting its underlying value as other C data types.
In most cases, you should use -objectForKey: instead of -valueForKey:. The former is the canonical method to obtain an entry in the dictionary and is declared in NSDictionary. The latter is declared in NSObject and is used in Key-Value Coding contexts, where the key must be a valid KVC key, and there’s additional processing — for instance, if you’re using -valueForKey: in a dictionary with a key that starts with #, that character is stripped from the key and [super valueForKey:key] is called.
The number 1 is not an object pointer. Use an NSNumber instance instead if you want to use a number in an NSDictionary.
[[jsonDictionary objectForKey:#"Success"]
isEqualToNumber:[NSNumber numberWithInteger:1]]
[[jsonDictionary objectForKey:#"Success"] isEqualToNumber: [NSNumber numberWithInt:1]]
Number and Value Programming Topics: Using Numbers
NSNumber: What is the point ?
You can get the value of dictionary in different ways like checking
the value first.
Solution 1: Using simple if statement.
int value = 0;
if ([[jsonDictionary objectForKey:#"Success"]intValue]==1){
value = [[jsonDictionary objectForKey:#"Success"]intValue];
}
Solution 2: Using ternary operator
value = ([[jsonDictionary objectForKey:#"Success"]intValue]==1) ? 1:0;
In the Pragmatic Core Data book, I came across this code snippet for an NSString setter:
- (void)setMyString:(NSString*)string;
{
#synchronized(self) {
if ([string isEqualToString:myString]) return;
[myString release];
myString = [string retain];
}
}
Is there any reason to use [string isEqualToString:myString] instead of string == myString here? Does it not mean that if the two strings have the same content, the result will be different than if they are actually the same object? Does this matter?
Thanks.
Notice that the variables you're comparing are pointers to NSStrings. Pointer comparison just checks if the pointers are referring to the same address. It doesn't know anything about the content at the end. Two string objects in two different places can have the same content. Thus you need isEqualToString:. In this case, I'm not sure either that it's a terribly important distinction to make though. It would make more sense to me if it were special-casing sending out change notifications based on whether the new string would actually be a change.
Incidentally, in an NSString setter, you almost always want copy rather than retain. I don't know the exact use case in this book, but if you just retain the string and it happens to be mutable, it can change behind your back and cause weird results. And if the string isn't mutable, copy is just an alias for retain.