Object-oriented programming in Swift

There are many approaches to writing code these days. Some help keep the code clean, others simplicity. Sometimes it is very useful to abstract and looks at your code through the lens of basic concepts when you are working as a programmer. In this article, I tried to pick up the cases that are widely used in the iOS developer’s routine and consider them at an OOP angle.

Dmytro Dobrovolskyy
Mac O’Clock
7 min readMay 1, 2020

--

Introduction

Object-Oriented Programming(OOP) is a programming paradigm based on the conception of objects and classes where the object is an entity, an instance of a class that can send and receive messages. Objects have attributes such as Properties(variables) and behavior(functions/methods).

There are 3 basic concepts in OOP: encapsulation, inheritance, and polymorphism. They are all interconnected and only the presence of all 3 concepts can be called an object-oriented approach. Abstraction can also be added to the basic concepts, but since abstraction is used in almost all programming approaches, it is not considered as a core feature. In addition, the OOP also has the concept of association.

Abstraction

Abstraction is a way of separating a set of significant characteristics of an object, excluding all insignificant ones from consideration, and abstraction is a set of such characteristics. In simple words, by taking two similar classes, we can highlight and render all common attributes. A list of these attributes will be an abstraction. In OOP abstraction is achieved by abstract classes and interfaces. An abstract class has one important difference from a common class — it cannot have an instance. It can only be inherited by other classes. Interface is a set of attributes that can extend a class.

The main task of the abstract class and interface is to reduce the dependence of entities by means abstractions. The main difference between them is that the interface attributes must be implemented within the extended class, and the abstract class attributes may leave their default implementation.

In Swift, there is no such thing as an abstract class, but a class that has no instances can be considered abstract. The role of the interface is played by protocols, but they have an interesting feature — their behavior can be extended (hello Protocol Oriented Programming). One class can implement several interfaces, and one interface can be implemented by several classes.

Well, enough theory, it’s time to shake our buttons (Listing 1.1). Consider a simple implementation of the interface. We created the Shakable interface and expanded UIButton. Now in order to create a button that can be shaken we simply initialize ShakableButton. In addition, we can extend other UI elements such as UIView or UILabel using this interface.

Listing 1.1 — Abstraction example.

The main advantage of abstraction is increasing the flexibility of the system, but with the increase in flexibility, the architecture become more complex. The flexibility of the architecture is improved by the addition of additional layers (levels) of abstraction. The wrong choice of abstraction leads to one of two problems:

  • If there are too few abstractions, further extensions of the project will rest on architectural constraints, resulting in refactoring and alterations to the architecture, or finding ways to bypass architectural holes.
  • If there are too many abstractions, it can lead to a too heavy architecture that will be difficult to maintain excessive flexibility that will not be useful on the system. Thus any changes will be accompanied by additional work to meet the requirements of the architecture.

It should be understood that the level of abstraction is determined not for the whole project, but for the individual components. The wrong choice of abstraction level can be solved by refactoring it.

Encapsulation

Encapsulation is a feature of the system that allows you to combine data and methods that work with them in class and hide implementation details from the user. Encapsulation is used to control access to class attributes. Access control means not only available/not available, but also various validations, loadings, calculations, and other dynamic behavior.

In Swift, much of the encapsulation is data hiding. There are the following access modifiers for implementation:

  • Internal is the default modifier, which means that anyone within the project can access the attribute.
  • Private means that access to an attribute can only be accessed within an object.
  • Private(set) means that anyone within the project can get access to the value, but setting new values can be done only within the object.
  • Fileprivate means that access to an attribute can be accessed throughout the file (previously it was the only way to use a restricted attribute in an extension).
  • Public/Open means that access is available within the workspace, mainly used for frameworks.

When choosing between access modifiers, if you want to access an attribute from another class — leave it default or make it public, otherwise use private. In addition, there are several ways to increase performance with access modifiers (https://apple.co/35ghLCA). For more information on access control and examples in Swift, read this article: https://bit.ly/2VK0VJf

Inheritance

Inheritance is a mechanism that allows one class to be inherited from another and adopt its attributes. So we can create a carcass for two similar classes. The inherited class(parent/super class) is an abstraction that collects all the generic attributes of the child/sub class.

This is all I would like to say about inheritance at this stage. Chill out, later i will give you more. Now let’s create a keyboard appearance handler (Listing 1.2). Since this functionality is required in the vast majority of cases, we will create a parent class BaseViewController that will subscribe and unsubscribe to the keyboard state update.

Listing 1.2 — Inheritance example.

It’s polymorphism, baby!

Polymorphism is a property of a system to have multiple implementations of the same interface. In other words, a child class can override an attribute, using the override modifier. Polymorphism provides opportunities to increase the flexibility of architecture. Roughly speaking, polymorphism makes it possible to work with different types of data such as function, array, enumeration, etc. You will see it in listing 1.5.

Polymorphism and Inheritance are often confused as these are very related concepts. Swift has 2 options for achieving polymorphism:

  • parent class inheritance → attribute override → polymorphism; if you displace step “attribute override”, polymorphism will turn into inheritance.
  • extend class attributes using protocol → polymorphism.

The main bonus of protocols is that it can extend not only classes but also structures (for a detailed description of the difference between class and structure see https://bit.ly/2VDWRdm). Moreover, protocols can extend even other protocols.

Let’s return to the example of inheritance(list. 1.2) and add some polymorphism to it. First of all, let’s add keyboard appearance notification handlers to BaseViewController and two functions to update the UI.

Listing 1.3 — Updated BaseViewController.

Second of all, take applyKeyboardAppearedWith and applyKeyboardDisappeared functions and override it in child ViewController. Now that the keyboard will appear or hide these two methods will be called and the UI will be updated.

Listing 1.4 — Class polymorphism example.

The previous example of polymorphism showed how to work with classes, now let’s look at the protocols:

Listing 1.5 — Protocol polymorphism example.

In Listing 1.5, we created Presentable protocol, Student class, Professor structure, and describe method that prints the person’s name. As we can see, in the describe method we pass a Presentable interface, which has data about the person. Now, regardless of the type we pass, if it is extended by the Presentable protocol we can print the person’s name. This is the real power of polymorphism.

Association

Association is a class relationship that allows you to use one object in another. To implement, you must create a class container that includes calling other class(-es). Simply put, not only standard types but also other objects can be attributes. There are two types of association:

  • composition;
  • aggregation.

Suppose we have object A and object B, with object A being used in object B. If object A was initialized in object B and its life cycle is completely dependent on the object B — it is composition. If object B has a reference to object A — it is aggregation. The main advantage of composition is the concealment of dependencies. The main advantage of aggregation is the reduction of dependencies and consequently increase of flexibility.

Listing 1.6 — Association examples.

Listing 1.6 shows examples of composition and aggregation usage. In the case of a composition, descriptionLabel is created inside of the LabeledViewController and will remain in memory until the LabeledViewController is in memory. In the case of aggregation, we pass an existing Service object to the constructor. Service class lifecycle is independent and even if the Coordinator will be thrown out of memory, Service will remain in memory.

Inheritance vs Association

The main difference between inheritance and association is that inheritance involves abstraction, and association is forming the whole from the parts. In order to determine what best to use, you should define:

  • If entity A is entity B then inheritance should be used.
  • If entity A is part of entity B, then the case is worth using an association.

In addition, there is another difference — inheritance is only set at the compilation stage, and the association can change the entity relationship during runtime.

Final words

Thank you for your time, I hope you found something interesting and informative in this article and may OOP come with you. 👀

--

--