With Swift strong typing and immutability, there are rules that prevent you from accesing variables until an object is fully initialized.
I do not like having a function do more than one thing, so I like to split my initializers into multiple functions, this becomes problematic.
Let's say you have a class like:
class Foo {
let coreDataStack: CoreDataStack
let mfpService = MFPService()
let integrator: IntegrationService
let flowController: FlowController
let window: UIWindow
}
This kind of class might require quite a few lines to initialize all the variables, so we'd like to split the initialization logic into functions, something like:
init(coreDataStack stack: CoreDataStack) {
coreDataStack = stack
integrator = setupIntegrator(stack, provider: mfpService)
flowController = setupFlowController(stack)
window = setupWindow(flowController.rootViewController)
}
So just a regular refactor... guess what, this won't work:
Not cool.
I talked with few smart people on twitter and looked at some options, here are the few I thought it will be good to mention.
Let's look at some options
Option 1 - implicitly unwraped optional var!
var integrator: IntegrationService!
This option sucks big time, the reason we have Swift is to write safer code.
Please do not write it like this.
Option 2 - lazy var's with side-effects
We can use private lazy var's, and have it execute side-effects
class Foo_lazy {
let coreDataStack: CoreDataStack
let mfpService = MFPService()
init(coreDataStack stack: CoreDataStack) {
coreDataStack = stack
let _ = integrator
let _ = rootFlowController
let _ = window
}
lazy var rootFlowController: FlowController = {
return FlowController(coreDataStack: self.coreDataStack)
}()
lazy var window: UIWindow = {
let window = UIWindow(frame: UIScreen.mainScreen().bounds)
window.backgroundColor = UIColor.whiteColor()
window.rootViewController = self.rootFlowController.rootViewController
window.makeKeyAndVisible()
return window
}()
lazy var integrator: IntegrationService = {
let coreDataProcessor = CoreDataReportProcessor(stack: self.coreDataStack)
let integrator = IntegrationService(provider: self.mfpService) {
coreDataProcessor.processReports($0)
}
return integrator
}()
}
This solution allows us to split code and compiles fine, but it's not so good because:
- Variables are not really immutable, we can make it private so nothing outside class scope can modify them but still, not immutable.
- I do not like triggering side effects like that, it feels very dirty
Option 3 - private static functions
We can define private static functions and use that to setup our variables (and we can also put them into private class extension):
class Foo_ext {
let coreDataStack: CoreDataStack
let mfpService = MFPService()
let integrator: IntegrationService
let flowController: FlowController
let window: UIWindow
init(coreDataStack stack: CoreDataStack) {
coreDataStack = stack
integrator = Foo_ext.SetupIntegrator(stack, provider: self.mfpService)
flowController = Foo_ext.SetupFlowController(stack)
window = Foo_ext.SetupWindow(self.flowController.rootViewController)
}
}
private extension Foo_ext {
static func SetupIntegrator(stack: CoreDataStack, provider: Provider) -> IntegrationService {
let coreDataProcessor = CoreDataReportProcessor(stack: stack)
let integrator = IntegrationService(provider: provider) {
coreDataProcessor.processReports($0)
}
return integrator
}
static func SetupFlowController(stack: CoreDataStack) -> FlowController {
return FlowController(coreDataStack: stack)
}
static func SetupWindow(controller: UIViewController) -> UIWindow {
let window = UIWindow(frame: UIScreen.mainScreen().bounds)
window.backgroundColor = UIColor.whiteColor()
window.rootViewController = controller
window.makeKeyAndVisible()
return window
}
}
This is by far my favorite option now:
- Ensures immutable variables
- Doesn't need to use side-effects to trigger requested behaviour
- clean and without any hack's around the compiler
Conclusion
There were few other ideas that my friends on twitter suggested, you can take a look here
As I just started learning Swift, I must say I'm liking it more and more, especially since 2.0.
After 20 years of programming experience, it's fun to feel like a rookie again, fortunately we have a great community that's willing to share their knowledge.
Big shoutout to @jessyMeow and @KostiaKoval as they pointed out some better ways to deal with this than I originaly thought :)
If you have other solutions you like, please do let me know on Twitter as I'd love to learn about it!