6
\$\begingroup\$

In my iOS application, I've created a singleton class that reads a configuration plist file and provides accessor methods to easily retrieve the values:

class Configuration{

    struct Key{
        static let searchRadius = "searchRadius"
        static let significantUserMovementThreshold = "movementThreshold"
    }

    enum ConfigurationError : ErrorType{
        case FileNotFound
    }

    static let sharedInstance = Configuration()
    var configDictionary : NSDictionary?

    func loadConfigurationFromPropertyList() throws{

        if let filePath = NSBundle.mainBundle().pathForResource("Config", ofType: "plist"){
            self.configDictionary = NSDictionary(contentsOfFile: filePath)
        }else{
            throw ConfigurationError.FileNotFound
        }

    }

    func searchRadius()-> Double?{
        return configDictionary?.objectForKey(Key.searchRadius) as? Double
    }

    func significantUserMovementThreshold() -> Double?{
        return configDictionary?.objectForKey(Key.significantUserMovementThreshold) as? Double
    }


}

In the AppDelegate, I call Configuration.loadConfigurationFromPropertyList to load the configurations into memory.

I have a number of questions regarding this approach:

  1. Is it a good idea to have accessor method for each property in the configuration file? I think it provides a cleaner and more reliable approach than using subscripts.

  2. Is it better for the Configuration class to read the file in its constructor or that AppDelegate invoke loadConfigurationFromPropertyList()?

\$\endgroup\$

3 Answers 3

4
\$\begingroup\$

I don't have a whole lot to add, as this is pretty straightforward and seems like a completely reasonable way to do what you're trying to do.

In regards to your questions:

  1. There's a tradeoff for using accessor methods for properties. Every time you add a new one, you need to add a constant to the Key struct, and add an accessor method to the Configuration class to get its value. If you misspell either of them (in the plist or the string in the Key struct), you won't have the compiler to tell you so, and you can end up with some odd bugs that become hard to track down. However, I personally prefer the way you're doing it because it's easier to read for the future. Your set of configurations is currently fairly small, so it's pretty easy to maintain. I'd go with what you've got unless you think there will be a lot of configuration options that will be controlled by a plist file.
  2. It's better to read the file in the constructor in my opinion. This is known as RAII - Resource Acquisition is Initialization. The idea being that if you do it in the constructor, if it fails, you don't end up with a partially initialized object.
\$\endgroup\$
1
  • \$\begingroup\$ For point 1, My plan is that if there are too many configurations: I'll split the class into say LocationConfig and NetworkConfig. For point 2, I never thought of it in terms of RAII but now that you mention it, that's a really good point! Thanks a lot! :) \$\endgroup\$
    – W.K.S
    Dec 27, 2015 at 8:24
1
\$\begingroup\$

The key from the plist file appears in many places:

  • in the plist file itself
  • constant in the keys struct
  • the value of the constant from the keys struct
  • accessor name
  • the key used in objectForKey call in the accessor

For a small number of entries, it's no problem. Problems will appear later when the config will grow. It's only a matter of time until you make a typo or forget to change something after copy-paste. One option to avoid this, is to generate the class (or a part of the class) automatically from the plist file.

For example, here is a quick and dirty script to generate the accessors with plist and Ruby:

generate.rb:

require 'plist'
config = Plist::parse_xml(ARGV[0])
config.keys.each do |key|
  puts "func #{key}() -> Double? {"
  puts "    return configDictionary?.objectForKey(\"#{key}\") as? Double"
  puts "}"
end

output:

$ ruby generate.rb Config.plist
func movementThreshold() -> Double? {
    return configDictionary?.objectForKey("movementThreshold") as? Double
}
func searchRadius() -> Double? {
    return configDictionary?.objectForKey("searchRadius") as? Double
}

You will also need to detect the return type for each key and tweak the script for your needs. But in the end, writing the entire script may be time consuming and not reasonable for you.


Another option is to generate the accessors at runtime. I couldn't implement this in swift. I'll still show you an example in objective-c, maybe it would be useful for you.

This is based on resolveInstanceMethod: which is called if a class doesn't respond to a selector. If the name of the selector matches a key in the config dictionary, it will add a simple getter at runtime. You can also implement an accessor for a key, if you want, and let the others be added dynamically at runtime.

Configuration.h:

@interface Configuration
@property (readonly) NSNumber searchRadius;
@property (readonly) NSNumber movementThreshold;
@property (readonly) NSString *title

+ (instancetype) sharedInstance; // singleton
@end

Configuration.m:

@interface Configuration ()
@property NSDictionary *configDictionary;
@end

@implementation Configuration

@dynamic searchRadius;
@dynamic movementThreshold;

+ (BOOL)resolveInstanceMethod:(SEL)sel {
  NSString *key = NSStringFromSelector(sel);
  NSDictionary *configDictionary = Configuration.sharedInstance.configDictionary;
  if ([configDictionary.allKeys containsObject:key]) {
    IMP implementation = imp_implementationWithBlock((id) ^(Configuration *_self){
      return _self.configDictionary[key];
    });
    class_addMethod(self, sel, implementation, "@@:");
    return YES;
  }
  return [super resolveInstanceMethod:sel];
}

/// This will be implemented at compile time unlike searchRadius and movementThreshold
- (NSString *) title {
  return [@"Search Radius: " stringByAppendingString:self.searchRadius];
}

// other code
@end
\$\endgroup\$
1
\$\begingroup\$

I believe just for clarity and safety you should have

private init()

You don't need the access modifier when you only have constants, but in you case you have variable. This variable is thus accessible from outside your class

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.