objective-cmacosplistnsmutabledictionary

Objective-C, Assign variable value to plist string


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.


Solution

  • 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:

    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!