Beginning Swift Programming Part 7 — Initialization and De-initialization, Overrides, and Reference…

Beginning Swift Programming Part 7 — Initialization and De-initialization, Overrides, and Reference…

Beginning Swift Programming Part 7 — Initialization and De-initialization, Overrides, and Reference Counting

Photo by Terry Tan De Hao on Unsplash

In the previous post we talked about structs, classes, properties, and methods. It helped us figure out how we could create objects and group like properties and methods together. The scope of this series will not teach you about the more specific classes Apple provides you, such as UIButton or the more hands-on URLSession. It will teach you enough so when you see these classes in your own programs, you’ll have an idea of how to start using them. So let’s dig in to the lesson.

Initialization

Initialization for classes and structs, in the most basic sense, is providing classes and structs with a value. Sometimes we want a class or struct to have default values when it is created, other times we want to tell the class or struct what it’s default values are.

The difference between initializing a Class and initializing a Struct is this: Structs in Swift have their own default initializer, Classes do not. What does this mean? It means when we create a struct we do not need to have an init() method included in our Struct, it is created automatically for you. Classes must have an init() method, only if a property of the class is not instantiated when the class is created. Let’s see what I’m talking about.

class myFullyInitializedClass {
var firstNumber: Int = 1
   func myFunction() -> Int {
return self.firstNumber
}
}
class myNonInitializedClass {
var firstNumber: Int
   // because firstNumber is not initialized we need an init() 
// method
   init(givenNumber: Int) {
self.firstNumber = givenNumber
}
   func myFunction() -> Int {
return self.firstNumber
}
}
// Finally an example of a struct, it does not have to be 
// initialized
struct myStruct {
var firstNumber: Int
   func myFunction() -> Int {
return self.firstNumber
}
}
// examples of how you call each in order
var myFirstClass = myFullyInitializedClass()
var mySecondClass = myNonInitializedClass(givenNumber: 2)
var myThirdStruct = myStruct(firstNumber: 3)

In myFullyInitializedClass we can create it using
var myFirstClass = myFullyInitializedClass(). If we were to call the function using myFirstClass.myFunction() it would return the integer1 because we set an initial value for the class.

In myNonInitializedClass, we need to create it passing in a givenNumber so we initialize all properties of the class. In this case we have only one property, but if we had more, we would have to pass in all of the default values. Just think of the init() method like a function without the func keyword for now. It’s sole purpose is to assign values to properties within the class. When we call the method myFunction it will return the value 2.

In the myStruct examples, we do not have an initializer because a default initializer is created for us behind the scenes. When we create a new struct we use var myThirdStruct = myStruct(firstNumber: 3). No, we did not create 3 structs, but I named it this way so you could see the overall order of classes and structs. We can call myThirdStruct.myFunction and the method would return 3.

If we were to create a new instance of myStruct and not pass in a default value for first number, we’d get an error. So when it comes to structs you must either set a default value or pass one in when calling it.

Optionals can be used in classes and structs to satisfy the requirements for an initializer. Let’s say that you create a class that doesn’t always need all of it’s properties. We can set the properties we do not need immediately available to optional.

class myClassOfOptionals {
var firstName: String?
var lastName: String?
var age: Int?
   // init is not required
}
var myClass = myClassOfOptionals()
myClass.firstName = "Tommy"
myClass.age = 18
print(myClass.firstName)          //prints Optional("Tommy")
print(myClass.firstName!) //prints "Tommy"
print(myClass.lastName) = nil
print(myClass.age!) //prints 18
// do not force unwrap an optional that has not been initialized
print(myClass.lastName!)          // CRASH!

By using optional values, we did not have to include an initializer, however, as shown further down we need to force unwrap the optional to remove the Optional() wrapper around what our value was set to. I know that I told you to try not to use the ! ,force unwrap, operator. But without more tools at your disposal this will have to do for now. Don’t worry those new tools are coming up soon. If you do force unwrap a nil optional, your program will crash and you’ll get a nasty error sayingFound nil while unwrapping optional .

There are a few types of initializers, default initializers, which we just covered, required initializers, convenience initializers, and failable initializers.

Required initializers just mean that if you subclass from the class you are currently creating, the subclass must call the initializer from the parent, or superclass.

enum FuelType {
case gas
case diesel
}
class Vehicle {
var fuelType: FuelType
   required init(fuelType: FuelType) {
self.fuelType = fuelType
}
}
class Car: Vehicle
var seatingCapacity: Int
   init(seatingCapacity: Int, fuelType: FuelType) {
super.init(fuelType: FuelType)
self.seatingCapacity = seatingCapacity
}
}

As shown above, by setting the initializer in Vehicle to required, we force car to provide a fuelType to the superclass. We use super.init to access the superclass’s initializer. Don’t worry if this doesn’t make sense, I’ll re-write this in a minute to show how it works. By calling the init method of Car, we need to include the fuelType property from Vehicle so when we call super.init from within the init method, we can pass the fuelType value back to the Vehicle class. Let’s see how this works now.

class Car {
var fuelType: FuelType
var seatingCapacity: Int
   init(seatingCapacity: Int, fuelType: FuelType) {
self.fuelType = fuelType
self.seatingCapacity = seatingCapacity
}
}

So it’s really the same as if the Vehicle class was never there. “Why don’t we just use it this way instead?” The answer is simple, extensibility. Going off our first example of subclassing let’s extend this out.

class Truck: Vehicle {
var bedSize: Double
   init(fuelType: FuelType, bedSize: Double) {
super.init(fuelType: FuelType)
self.bedSize = bedSize
}
}
class SemiTruck: Vehicle {
var hasSleeper: Bool
   init(hasSleeper: Bool) {
super.init(fuelType: .diesel)
hasSleeper = true
}
}

In this example we provide a Truck class which has it’s own property bedSize. Then we create a SemiTruck class, which also has an initializer but only takes in hasSleeper. We set the fuelType to .diesel because for this example, all Semi-trucks run on diesel fuel. This is allowed.

That brings us to convenience initializers. Convenience initializers work in the same way as that last example but are used to set default values when you don’t care to set the values yourself.

var Person {
var name: String
   init(name: String) {
self.name = name
}
   convenience init() {
self.init(name: "unknown person")
}
}
var sarah = Person(name: "Sarah")
var unknown = Person()

This allows us to just call Person() without setting a value and we get a default name of “unknown person”. These can be a helpful alternative to using optionals throughout a class.

Failable initializers can protect you from setting a class’s property to the wrong value and allow you to return nil if the class could not be initialized.

class myFailableClass {
var name: String
    init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}

Here we set up the failable initializer using init?, this allows us to cancel out of initialization returning nil to the variable. Using failable initializers also makes the variable that holds the class optional. Here if we do not pass in a name, we return nil, otherwise we return an optional myFailableClass instance.

Photo by Heejing KIM on Unsplash

De-initialization

De-initialization is simply cleaning up any loose ends when you are done with a class. Soon we will talk about reference counting, but anytime you create a class you create a reference to it, if you have another object that uses this class, we use de-initialization to help the other object “forget” this class. For this example, I’m going to talk about the Swift Timer class. Timer’s create a strong reference to the class they are used in, when we are done with our class, we want to remove the timer from our class, let’s see how that’s done.

class Counter {
let timer = Timer()
   // do stuff with timer
   deinit {
if timer.isValid {
timer.invalidate()
}
}
}
var myCounter = Counter()
myCounter.timer.fire()
myCounter = nil

deinit is called right before the class we are in is removed from memory. When we use myCounter = nil the class prepares to remove itself from memory. It calls deinit and runs whatever code is in there, whether that is to copy data out to save it somewhere, or in our case, check if the timer is still valid and if it is, invalidate it.

Invalidating a timer tells the timer to release it’s reference to the class that owns it and perform any other cleanup tasks that are necessary for it to remove itself from memory. Once the timer is destroyed, the owning class then finishes thedeinit and removes itself from memory.

Photo by Chris Lawton on Unsplash

Overrides

Overrides allow you to subclass and change the default initializer of the superclass.

class Ball {
var size: Int
   func bounceHeight() -> Double {
return Double(size) * 0.5
}
}
class Basketball {
override init() {
super.init()
size = 3
}
   override func bounceHeight() -> Double {
return Double(size) * 0.75
}
}

Here we have a superclass of Ball, it comes with a default initializer of init(), because we are calling an initializer from Basketball with the same name, we use the override keyword. We’d still need to call super.init so the superclass can do it’s work, but we’d end up setting the default value of 3 for the Basketball class.

We also use the override keyword on methods that have the same name and exist in a parent class. Here we override the method bouceHeight because it has the same method signature as the parent class. The method signature is just another way of saying it looks exactly the same and should be called in the same way. The only difference is that instead of returning
Double(size) * 0.5 we return Double(size) * 0.75. The process of converting from one type to another is called casting. We will talk about that in the next part of this series.

Don’t worry too much about when you need to override, Xcode generate a nice error message when you need to use the override keyword and even autocompletes it for you when you click on the error.

Reference Counting

Whenever you create a class in Swift, you create a reference to an object. When this reference is created the system creates a counter along with it. This counter directly refers to the number of objects (including variables) that point to it.

Here we have two references, both A and B point to Data, and so we update the counter to say there are two references. If A were to be set to nil then we’d only have one reference to Data.

What happens if both A and B no longer reference Data? According to the laws of memory, Data would sit there indefinitely. Either until the computer was turned off (clearing the memory) or by a coincidence we happened to access the entire block of memory that contained data and either set it to nil or, more likely, wrote new values over the top of what data used to be.

Thanks to operating systems, we don’t have to panic about this situation too much. In Swift, more accurately the clang compiler, we have a feature called ARC (Automatic Reference Counting) that comes through at various times, checks memory for any data that no longer has references to it, and deallocates it from memory. This used to be called garbage collection back in the old Objective-C days, and garbage collection is still used by many languages. It doesn’t mean one is better or worse than the other, it’s just a different process for how data gets released from memory.

Where ARC gets hung up is when we have strong references hanging around. Using the counter class above, let’s illustrate how this looks.

Becuase Counter owns Timer, and Timer creates a reference back to Counter, each of them both have a reference count of 1. If we deallocate Counter without removing this strong reference, Timer exists on it’s own with a reference count of 1 until the computer is turned off. ARC can’t handle this situation. This is what is referred to as a memory leak. Which is simply a way to say that an object is sitting on it’s own in memory and will never go away.

Memory leaks of any size aren’t good and we should all strive to not have any, but as you first start off, it will happen. It doesn’t mean you will run out of memory immediately, it just means while you have that leaky memory, you will not be able to use that memory for anything else. Turning your computer off for about 15 seconds is the easiest way to fix it. “Ahh that’s why the cable company tells me to turn my router off for 15 seconds.” Exactly, if you have corrupt memory, turning the device off clears the corrupted memory and it functions normally after re-writing the correct data back to memory.

So if we deallocate Timer, we still have control over deallocating the class, and all is good when we do.

Another way you can help with strong reference cycles like this is to mark classes that will cause a strong reference with the weak or unowned keyword.

Ilea Cristian put it best in his answer on Stack Overflow:

A weak reference allows the posibility of it to become nil (this happens automatically when the referenced object is deallocated), therefore the type of your property must be optional – so you, as a programmer, are obligated to check it before you use it (basically the compiler forces you, as much as it can, to write safe code).

An unowned reference presumes that it will never become nil during it’s lifetime. A unowned reference must be set during initialization – this means that the reference will be defined as a non-optional type that can be used safely without checks. If somehow the object being referred is deallocated, then the app will crash when the unowned reference will be used.

That is what helped me to finally understand the concept of unowned vs weak.

Summary

We learned about Initialization, how to do it, and why its so important with classes. Most of the time in Swift you will be writing structs, every once in a while you’ll need to use a class. The rule of thumb is start with a struct, move to a class if you run into limitations.

Then we learned about deinitialization and when it is necessary. It’s not required to include it in every class, but make sure to use it when you might end up with a strong reference cycle.

We learned a little about overrides and why you need them, and finished up with one of the most important topics that you need to be conscious of when you write your programs with reference types: Reference counting. Knowing that you could end up with a memory leak and preventing it, is something you have direct control over when it comes to bugs in your app. It won’t cause your app to crash but it can be just as bad or worse by making the user’s computer crash from lack of memory.

Suggested reading:

I know it sounds like it is a lot of information but with the primer I have given you since the last bit of reading, you should be able to go through it pretty quickly and get more in-depth information on topics you might be struggling with.

What’s Next

Up next is Type Casting, Safely Unwrapping Optionals, and Access Control. It should be pretty quick to go through and it will provide a lot of power in terms of flexibility to your toolset.

As always, keep practicing your new skills, we’re more than halfway through.

Author: Pawan Kumar

Leave a Reply

Close Menu
%d bloggers like this:
Skip to toolbar