If you are developing an app and you are organizing your code for production, you should aim to having at least two different builds. Ideally you would have up to 3, one for development, one for staging and one for production.
Form within your XCode project you can configure multiple build targets which can then be associated to different apps in iTunes Connect, that way you can always be sure that your changes are targeting the development build.
You really don't want your production application to be checking for your dev server API as you don't want your users to be hitting that serer while you work on updates.
This is a good starting point to getting your targets set up, you can get that up and running in no time. With these different targets, you can then configure a new info.plist for each build and its always a good idea to also have a different app icon that lets you know you are testing or working with the different targets.
You can then have as many configuration files as you need for each target, a config file could look like this:
Config.plist (in xml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "...">
<plist version="1.0">
<dict>
<key>ClientId</key>
<string>{SomeIdValue}</string>
<key>ClientSecret</key>
<string>{ClientSecret}</string>
<key>ServerUrl</key>
<string>{my.target.apiEndpoint}</string>
</dict>
</plist>
You can then have a provider that serves your application with the right API end point configuration, as so (Using config.plist for production and configDebub.plist for debug):
struct ConfigProvider {
#if DEVELOPMENT
let name: String = "configDebug"
#else
let name: String = "config"
#endif
enum ConfigError: ErrorType {
case FileNotWritten
case FileDoesNotExist
}
var sourcePath:String? {
guard let path = NSBundle.mainBundle().pathForResource(name, ofType: "plist") else { return .None }
return path
}
var destPath:String? {
guard sourcePath != .None else { return .None }
let dir = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
return (dir as NSString).stringByAppendingPathComponent("\(name).plist")
}
init?() {
let fileManager = NSFileManager.defaultManager()
guard let source = sourcePath else { return nil }
guard let destination = destPath else { return nil }
guard fileManager.fileExistsAtPath(source) else { return nil }
if !fileManager.fileExistsAtPath(destination) {
do {
try fileManager.copyItemAtPath(source, toPath: destination)
} catch let error as NSError {
print("Unable to copy file. ERROR: \(error.localizedDescription)")
return nil
}
}
}
func getConfig() -> NSDictionary?{
let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(destPath!) {
guard let dict = NSDictionary(contentsOfFile: destPath!) else { return .None }
return dict
} else {
return .None
}
}
func getServerUrl() -> String {
if let c = getConfig()?["ServerUrl"] {
return c as! String
}
// Handle error
}
func getClientSecret() -> String {
if let c = getConfig()?["ClientSecret"] {
return c as! String
}
// Handle error
}
func getClientId() -> String {
if let c = getConfig()?["ClientId"] {
return c as! String
}
// Handle error
}
func getMutablePlistFile() -> NSMutableDictionary?{
let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(destPath!) {
guard let dict = NSMutableDictionary(contentsOfFile: destPath!) else { return .None }
return dict
} else {
return .None
}
}
}
Then you can configuration details as:
private static let config = ConfigProvider()!
private let clientId = config.getClientId()
private let clientSecret = config.getClientSecret()
private let serverUrl = config.getServerUrl()
Hope this helps!