In this post i'll teach you how to initialise your classes in Swift focusing on the core patterns. WARNING minimum dependency: Swift 1.2
1. Init basics
First of all you should read this article. It demonstrates how initializers work and you will get a basic idea of designated and convenience initializers. Then go to this link and read it as well. Now you got the basics, but if not here is the main concept.
Initialization
A Swift class must initialize its own (non-inherited) properties (non-optional) before it calls its superclass’s designated initializer.
Designated initializer
They are the main initializers to be used for a class.
Convenience initializer
They are initializers used to make initialization a bit easier.
Three main rules of initialization process:
- A designated initializer must call a designated initializer from its immediate superclass.
- A convenience initializer must call another initializer from the same class.
- A convenience initializer must ultimately call a designated initializer.
If you want to dig deeper here is a nice article about the topic: Part 1, Part 2
2. NSObject subclass init
If you subclass NSObject you can go on multiple "roads", I want to show you a little example that demonstrates how hard the process can be. But hey, just remember the main rules and everything will be all right. ;)
Example 1 - inits are designated
class Apple : NSObject
{
var color : String
init(color: String) {
self.color = color
super.init()
}
override init() {
self.color = "Red"
super.init()
}
}
class GoldenApple : Apple
{
var epic : Bool
init(color: String, epic: Bool) {
self.epic = epic
super.init(color: color)
}
override init(color: String) {
self.epic = true
super.init(color: color)
}
override init() {
self.epic = true
super.init()
}
}
Example 2 - init() is convenience
class Apple : NSObject
{
var color : String
init(color: String) {
self.color = color
super.init()
}
override convenience init() {
self.init(color: "Red")
}
}
class GoldenApple : Apple
{
var epic : Bool
init(color: String, epic: Bool) {
self.epic = epic
super.init(color: color)
}
override convenience init(color: String) {
self.init(color: color, epic: true)
}
convenience init() {
self.init(color: "Golden")
}
//this could be also a designated init if i wanted to...
//init() {
// self.epic = true
//
// super.init(color: "Red")
//}
}
3. UIViewController subclass init
This is really messed up. I know. First of all there is a required init(coder: NSCoder) method which you have to implement even if you are not using storyboards, or xib files. Here is the example:
class ViewController : UIViewController
{
var example : String
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
self.example = "Just an example"
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init(coder aDecoder: NSCoder) {
// fatalError("init(coder:) has not been implemented")
self.example = "Just a coder example"
super.init(coder: aDecoder)
}
init() {
self.example = "Init example"
super.init(nibName: nil, bundle: nil)
}
}
The problems:
- Why is init(coder) required?
- If it's because NSCoding, why is encodeWithCoder(aCoder: NSCoder) not required?
- Why is init(nibName, bundle) the designated initializer?
- Why can't I have a pure init method for ViewControllers if i am not using IB files?
I think this whole process is highly dependent on Storyboards and Xib files which is good if you are always using them, but if you want to ignore Interface Builder and you want to create something just from code you are fucked. I'll tell you something else: if you want to have a common init for all the UIViewController init methods you simply can not do that. Why? Because Apple made a HUGE mistake. I've found the best solution, but this way you will lose the ability to create ViewControllers with the init(nibName, bundle) method. Who cares? Apple... nope, so here is the idea:
class ViewController: UIViewController {
var example : String!
required convenience init(coder aDecoder: NSCoder) {
self.init(aDecoder)
}
init(_ coder: NSCoder? = nil) {
self.example = "Common init example"
if let coder = coder {
super.init(coder: coder)
}
else {
super.init(nibName: nil, bundle:nil)
}
}
}
class ChildViewController : ViewController
{
var name : String
override init(_ coder: NSCoder? = nil) {
self.name = "String"
super.init(coder)
}
}
4. UIView subclass init
Subclassing a view is much more easier if you don't have non-optional values inside the subclass. There are a lot of tutorials and resources how to do it. (Link 1, Link 2, Link 3, Link 4)
class View: UIView
{
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
}
convenience init() {
self.init(frame: CGRectZero)
self.initialize()
}
func initialize() {
NSLog("common init")
}
}
If you want to create a custom UIView subclass from a xib file it's nice to have a class extension to do it.
extension UIView
{
class func initFromNib() -> UIView {
let mainBundle = NSBundle.mainBundle()
let className = NSStringFromClass(self).componentsSeparatedByString(".").last ?? ""
if ( mainBundle.pathForResource(className, ofType: "nib") != nil ) {
let objects = mainBundle.loadNibNamed(className, owner: self, options: [:])
for object in objects {
if let view = object as? UIView {
return view
}
}
}
return UIView(frame: CGRectZero)
}
}
You can always use the same trick as I showed you for UIViewControllers. In this case you will lose the ability to create a view with the init(frame) method, but who cares in the world of auto layout?
class View: UIView
{
var name : String
required convenience init(coder aDecoder: NSCoder) {
self.init(aDecoder)
}
init(_ coder: NSCoder? = nil) {
self.name = "common init"
if let coder = coder {
super.init(coder: coder)
}
else {
super.init(frame: CGRectZero)
}
}
}
5. Singleton init
This is a really easy one. From Swift 1.2 - just create a static constant and you are done. If you are looking for earlier solutions just check this out.
class Singleton : NSObject {
static let sharedInstance = Singleton()
override init() {
super.init()
}
}
I hope this little post will help a lot of people. If you are looking for other design patterns you might want to go here, if have any questions or tips just tweet me.
--
P.S. - The menu button is working again. :)
The.Swift.Dev.