I'm a JavaScript web app developer that has just inherited a legacy iOS app. The app is full of hardcoded URLs that point to our production API.
I'd like to factor out the Base URL into a constants file, or an environment variables file, or a config file, so that a different Base URL can be used in different environments (dev, staging, production).
What's the best practice in the land of the Swift?
Is there something similar to a .env file I can use?
What I always do is probably a more flexible approach than checking constants in your code and littering different values of the same variable per environment everywhere.
It is not necessary to create .xcconfig files either.
Setting up the whole thing is a little more work but you will see that it is very easy to use afterwards.
In my example you see how I set different REST endpoints for production/UITest/development builds in my Toeppersee iOS app.
Changes to your Info.plist
First, just define a new constant and select a name that identifies your content, in my case it is TS_WEBSERVICE_URL:
Make sure to set its value to the name you chose, enclosed in $() (brackets, not braces!), in my case it's $(TS_WEBSERVICE_URL).
Build settings
In your build settings tab, add custom "user defined" build parameters, again with the same name:
You see that you can define values for Debug, UITests and Release. And that I use this mechanism for a whole lot of things that are different in the environments.
Adding more environments
If you need more environments, add them on your master project node's Info tab (like I probably did here with UITests):
Use the values in code
Now for the easy part: use your newly defined values in your code. It all boils down to getting an NSBundle instance and reading the value defined in your Info.plist which has been substituted automatically during build process.
Objective C:
NSString *webserviceURL = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"TS_WEBSERVICE_URL"];
Swift:
let webserviceURL = Bundle.main.object(forInfoDictionaryKey: "TS_WEBSERVICE_URL") as! String
Just one line of code per environment setting.
One thing I have not shown, but which is also fairly easy: just define a category to NSBundle and supply new methods called webserviceUrl and similar methods. They can then be called like this: Bundle.main.webserviceUrl() which even more simplifies the source code.
Edit: write extension to Bundle object (optional)
To achieve the aforementioned simplified access to the webservice URL, a new Swift file is created and called (for example): Bundle+AppAdditions.swift
import Foundation
// An extension (or category in Objective C) extends any types functionality,
// in our case the NSBundle foundation class.
extension Bundle {
// get the webservice URL from an NSBundle instance
public func webserviceUrl() -> String {
// get an object for info dictionary key and cast it as string
return self.object(forInfoDictionaryKey: "TS_WEBSERVICE_URL") as! String
}
}
The extension works directly on any given Bundle instance, not necessarily only the main bundle. Example:
let url = Bundle.main.webserviceUrl()
or any other bundle, e.g. for an App Clip Extension containing an InitialViewController:
let url = Bundle(for: InitialViewController.self).webserviceUrl()
I would actually add another static extension method to get the app clip extension bundle. But that's optional as well ;-)
Some recommend creating a config file (.xcconfig) for every environment and then you can set up your different base URL there.
Where I used to work, we used a constants file where we set up different values, urls,ports and so on depending on the environment like this:
#if DEBUG
let a = 2
#if PRODUCTION
let a = 4
I think this is the best thing to do.
Related
I want to be able to use a place holder in the extension code that takes its actual value from the extension manifest upon packaging (or build), such as the version.
So for example in the vss-extension.json there's a version property set to 1.2.3, so in the build task code inside that extension I want to be able to use something like that:
// index.ts
var agentId = "myext-azurepipelines-${extension.version}";
that would translate to:
// index.ts
var agentId = "myext-azurepipelines-1.2.3";
when packaging or building, but without changing the original file (a template of a sort).
Is something like this available?
Unfortunately, your requirement is not able to achieve currently. You have to modify the original file.
My application uses two separate Realm instances (the second comes from a library).
The application itself uses Realm.Configuration.defaultConfuguration and the library creates its own configuration (Realm.Configuration(...)).
On runtime (after inspecting with Realm Browser), we see that both instances (each live in its own file) contain the models from both Realms. This, of course, has implications on migrations.
I know that when both use the same configuration we should set configuration.objectTypes, but I didn't expect it to matter when each instance has its own configuration.
How can two distinct configs share any data between them? It seems like a bug in Realm - or maybe I'm missing something.
An explanation was posted in Realm's issues on GitHub. I'm copying the response here for future searches:
By default objectTypes will include all RealmSwift.Object
subclasses, regardless of where they are defined.
A library which uses Realm should override
shouldIncludeInDefaultSchema() to exclude its types from the default
objectTypes (i.e. add public override class func
shouldIncludeInDefaultSchema() -> Bool { return false } to the class
definitions) and then explicitly list the types it uses. This lets any
applications using the library continue to simply use the automatic
class discovery.
The credit goes to Thomas Goyne (Realm developer).
I have an Objective C++ program used to handle the setup of our different applications. Is there a way to use preprocessor defines to create text to be substituted in the strings used by NSTextFieldCell, NSButtonCell?
FOR EXAMPLE, instead of have an NSTextField that says "Options for setting up Foo", there would be a preprocessor macro (GCC_PREPROCESSOR_DEFINITIONS):
MY_PROGRAM_NAME=Bar
and then the text for NSTextField would be:
"Options for setting up $(MY_PROGRAM_NAME)"
Which would then have the desired result: "Options for setting up Bar"
NOTE 1: obviously, I could do the substitution programmatically in code.
Note 2: this is for Xcode 7, so perhaps there isn't a feature like this?
In a word, no. The Xcode nib compiler doesn't perform any kind of variable substitution and—once encoded—all archived property values are static.
However, if this is a "thing" for you application, and there aren't too many view classes involved (say, just NSTextField), it wouldn't be hard to roll your own solution.
I'd consider this approach:
Concoct a simple-to-substitute syntax, a la "Some string {VAR_NAME}".
Define your variables as key/value pairs in a dictionary. Store the
dictionary as an XML file / dictionary in the app bundle.
At app startup, load the dictionary and make it public by putting it in
a global variable or adding it to -[NSUserDefaults registerDefaults:]
Subclass NSTextField (as an example). Override either
-initWithCoder: or -awakeFromNib. In the override, get the string
value of the view object, scan it for substitutions using the public
variable dictionary, and update the string property as appropriate.
In IB, change the class of any NSTextField that needs this feature to
your custom subclass.
Another possible approach would be to have multiple targets in your project and a separate Localizable.strings file for each of these. This of course assumes, that you use Localizable.strings, even if you may support only one language.
I am developing an iOS application in Swift 2.
I am struggling with when to create new classes to handle discrete pieces of functionality.
For example, I have followed this to bring in environment dependent variables: http://appfoundry.be/blog/2014/07/04/Xcode-Env-Configuration/
I can get those variables using:
let path = NSBundle.mainBundle().pathForResource("Configuration", ofType: "plist")
let dict = NSDictionary(contentsOfFile: path!)
url = dict!.objectForKey("envURL") as String
But should I do that code once, in a singleton sharedInstance, or should I just write it when required?
Within my Configuration.plist I have a number of different key/value pairs. I need to access the values within multiple ViewControllers, which means I could be writing those three lines in 5 different places. I could write them once for each key/value pair and save the result to a property of a Singleton. That would save lines, but is that the right way of doing it?
Another example is I make the same REST call a few times in different view controllers. Should I write a new class to handle that call and then use that instead?
I think this is due to lack of Object Oriented programming experience, so any pointers would be really helpful.
Thanks
The singleton pattern should not be your first tool for sharing behavior between classes. Singletons have their place but I find them to be heavily overused in iOS applications. Use a singleton when you absolutely must enforce that there is one and only one instance of a class. Avoid using singletons as globals (calling +shared___) when you just want several objects to have access to some shared state or behavior.
In this case you have some configuration loading behavior which you do not want to repeat. That's a good instinct. We might start by extracting that behavior into a struct whose responsibility is to provide an interface to your app configuration settings:
struct Configuration {
static func get(key: String) -> String {
let path = NSBundle.mainBundle().pathForResource("Configuration", ofType: "plist")
let dict = NSDictionary(contentsOfFile: path!)
return dict!.objectForKey(key) as! String
}
}
Now your controllers can each load different keys by calling a static method:
Configuration.get("envURL")
If you determine that you need to cache these configuration values you might switch this struct become a class with an instance method and maintain a cache on a Configuration instance. Each controller could then create it's own instance of Configuration. If you need to share a common Configuration you might create one and pass it to each of your view controllers when you create them.
Similarly for your REST network calls. You could create a class or struct which provides an interface for interacting with this API. Your controllers can then create and work with instances of this class on demand, or be passed an instance if it is necessary for them to share some state.
This isn't directly related to that specific example, but if you're having issues reusing code, you may want to look into Swift Extensions. They are a great way to extend the functionality of an existing class or protocol.
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Extensions.html
For instance, if you were using a 3rd party library for your restful APIs you could use an extension on the manager to define methods that take the specific params needed for the various endpoints.
But should I do that code once, in a singleton sharedInstance, or should I just write it when required?
if you find you are reusing that same piece of code many times in the same class pull it out into a separate, private "helper" method. if you are using it across several classes, You can write a utility class or classes which contain common utilities. Many people call this kind of class / package a "commons"
Another example is I make the same REST call a few times in different view controllers. Should I write a new class to handle that call and then use that instead?
when dealing with REST interfaces I like to make client classes to interact with them. Write one class that makes all the different calls you want to make to a particular REST API. Each call could be represented by a different method which takes all the arguments and parameters you want to pass to the API. That method should then deserialize and return the value it received from the API.
I'm dummy in iOS especially in private API.
I have application for testing and now I need to use private API (this application not for App Store).
I downloaded generated headers from iOS-Runtime-Headers and what next?
Under /System/Library/ I have list of libraries that contain Frameworks, ... ,PrivateFrameworks as well.
Do I need to replace original framework with ones I copied from iOS-Runtime-Headers?
Because I have other applications that use Public API only and I don't want to damage them.
From posted link they tell to validate library for example by:
NSBundle *b = [NSBundle
bundleWithPath:#"/System/Library/PrivateFrameworks/GAIA.framework"];
BOOL success = [b load];
But here the path points to original path.
Or I miss something,
Thank you
First of all, don't replace any headers which are provided by Apple.
Generally, it's done one of two ways:
1) You can copy some of these headers to your project and just include these files the same way as you include any other headers
#import "SomeHeader.h"
2) Sometimes you have to sanitize them (edit them) a little bit. Quite often, these headers has something like in in them:
#import "NSObject.h"
And compliller won't be able to find it, because NSObject is built-in class. So, you need to remove this like.
3) If you just need couple of methods out of it, then Tuukka Nori solution is right.
On top of these, you will need to link (statically or dynamically) against appropriate private framework (just including headers isn't enough).
Don't replace any files. Instead, write a header file with the symbol that you intend to use. If you need an Objective-C method, add a category with a unique name, e.g.
#interface NSString (MyOwnPrivateCategory)
- (void) privateMethodDeclaredInRuntimeHeaders;
#end
Import it and use the method as you like.
The sample code given shows how to load a framework at runtime in case you don't want to link to it. Since some frameworks are private, they might not be available in all versions of iOS.