BLOG
20 December 2022

Kotlin vs Swift - How do They Compare? Part 2

tech

Abstractions, classes and collections 

In the first part of this article, we analyzed the differences in keywords, features and flow control. The second part focuses on the differences in object-oriented programming between Kotlin and Swift.  

Base class 

In Kotlin `Any` is the root of the Kotlin class hierarchy. 

1 2 3 4 5 6 7 8 9 ```kotlin class A class B val objects: List<Any> = listOf(A(), B()) ```

Base class inheritance differs among the languages. According to Swift documentation: 

Swift classes don’t inherit from a universal base class. Classes you define without specifying a superclass automatically become base classes for you to build upon. 

In Swift, `AnyObject` is the protocol to which all classes implicitly conform. 

1 2 3 4 5 6 7 8 9 ```swift class A {} class B {} let objects: [AnyObject] = [A(), B()] ```

 

Class contract (interface)

Both Kotlin and Swift allow to provide a default implementation for a class interface; however, the place of the implementation differs. 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ```kotlin //kotlin interface ProductRepository { fun save(product: Product) { //default implementation } } ```
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ```swift //swift protocol ProductRepository { func save(product: Product) } extension ProductRepository { func save(product: Product) { //default implementation } } ```

 

In Kotlin, concrete implementations need the `override` modifier

1 2 3 4 5 6 7 8 9 10 11 12 13 ```kotlin class ProductLocalRepository: ProductRepository { override fun save(product: Product) { //concrete implementation } } ```

 

In Swift, the `override` modifier is not required when implementing a ‘`protocol’`. 

1 2 3 4 5 6 7 8 9 10 11 12 13 ```swift class ProductLocalRepository: ProductRepository { func save(product: Product) { //concrete implementation } } ```

 

Abstract classes 

In Kotlin, a class may be declared [abstract](https://kotlinlang.org/docs/classes.html#abstract-classes), along with some or all of its members.  

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ```kotlin abstract class Shape { abstract fun area():Double } class Rectangle : Shape() { override fun area():Double { // compute the area } } ```

 

Swift doesn't support the concept of abstract classes. Similar functionality can be achieved with protocols, protocol extensions, and composition. 

 

Custom data holders 

It is not unusual to create building blocks whose main primary purpose is to hold data. In Kotlin, these are called data classes and are marked with the ‘`data`’ keyword. 

 

1 2 3 4 5 ```kotlin data class User(val name: String, val age: Int) ```

In Swift, use structures.

1 2 3 4 5 6 7 8 9 10 11 ```swift struct User { let name: String let age: Int } ```

Inner classes 

In Kotlin, a nested class can have direct access to members of its outer class. These nested classes are called inner classes. Inner classes carry a reference to an object of an outer class: 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ```kotlin class Outer { private val bar: Int = 1 fun fooByInner(): Int { return Inner().foo() } private inner class Inner { fun foo() = bar //the private bar variable belongs to the outer class } } Outer().fooByInner() ```

 

A class in Swift can be declared as nested. However, accessing members of an outer class, requires a reference to that outer class: 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ```swift class Outer { private let bar: Int = 1 func fooByInner() -> Int { return Inner(self).foo() } private class Inner { private let outer: Outer init(_ outer: Outer) { self.outer = outer } func foo() -> Int { return outer.bar //the private bar variable belongs to the outer class } } } Outer().fooByInner() ```

 

Object initialization 

A class in Kotlin can have a primary constructor and one or more secondary constructors. The primary constructor is a part of the class header, and it goes after the class name and optional type parameters. 

Both languages have the `init` keyword to deal with an instance initialization, however it works a bit different.  

In Kotlin, the primary constructor can not contain any code. Initialization code can be placed in initializer blocks prefixed with the `init` keyword. Constructors can be parameterized, `init` blocks not. Both languages have the 'init' keyword to deal with instance initialization. However, it works a bit different.   

The primary constructor in Kotlin can not contain any code. Initialization code can be placed in initializer blocks prefixed with the 'init' keyword. Constructors can be parameterized, but 'init' blocks not. 

 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ```kotlin data class Money /*primary*/ constructor(val amount: Double) //If the primary constructor does not have any annotations or visibility modifiers, the constructor keyword can be omitted data class Product(val name: String, val price: Money) { constructor(name: String, price: Double) : this(name, Money(price)) init { println("Product: $name created") } } ```

In Swift. an instance initialization takes place inside *initializers* body. 

 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 ```swift class Product { let name: String let price: Money //Designated initializer (primary) init(name: String, price: Money) { self.name = name self.price = price } //Convenience initializer (secondary) convenience init(name: String, price: Double) { self.init(name: name, price: Money(amount: price)) } //Failable initializer init?(name: String, price: String) { if let price = Double(price) { self.name = name self.price = Money(amount: price) } else { return nil } } } ```

 

Singletons 

Many developers consider the [singleton](https://softwarehut.com/blog/tech/singleton-design-pattern) to be an anti-pattern that introduces a global state into an application. Therefore, yYou should always think twice before applying it to your code.  

However, my role here is not to criticize the pattern, but to show the differences between the languages, especially since the use of singletons is common. 

In  Kotlin declaring singletons is called *object declaration*. 

1 2 3 4 5 6 7 8 9 10 11 ```kotlin object GlobalCounter { var counter: Int = 0 } //Sample usage: GlobalCounter.counter += 1 ```

 

In Swift, you can declare a singleton with the  `’static` keyword. It is a good practice to make an initializer private to prevent an unwanted instantiation.  Making an initialiser private is a good practice to prevent unwanted instantiation. 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ```swift class GlobalCounter { static let shared = GlobalCounter() var counter: Int = 0 private init() {} } //Sample usage: GlobalCounter.shared.counter += 1 ```

 

Computed properties 

Both languages support mutability and immutability of computed properties.  

In Kotlin, an immutable (read-only) computed property is declared with the ‘`val`’ keyword. Declaration of a mutable property begins with the `var` keyword.  

Define a custom behaviour of computation by specifying logic of a getter ‘`get()’` and a setter ‘`set(value)`’. The setter can have limited visibility. A mutable computed property has a backing variable called ‘`field’`.  

 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ```kotlin class GmailUser(email: String) { //read-only `val` properties val name: String get() = email.split("@")[0] val domain: String get() { val emailComponents = email.split("@") return if (emailComponents.count() == 2) emailComponents[1] else "" } var email: String = "" //A mutable `var` property has to be initialized get() { return if (field.isNotBlank()) field else "unknown.user@gmail.com" } private set(value) { if (value.endsWith("@gmail.com")) field = value } init { this.email = email } } ```

 

In Swift, mutable and immutable computed properties are declared with the `’var`’ keyword, what may be counterintuitive. Declare `get` and `set` block to make a computed property mutable. 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 ```swift struct GmailUser { //get-only property has to be declared as `var` (not `let`) var name: String { return String(backingFieldOfEmail.split(separator: "@")[0]) } var domain: String { let emailComponents = backingFieldOfEmail.split(separator: "@") return emailComponents.count == 2 ? String(emailComponents[1]) : "" } // A mutable property does not have a hidden backing variable. The backing variable has to be declared explicitly private var backingFieldOfEmail: String var email: String { get { backingFieldOfEmail.isEmpty ? "unknown.user@gmail.com" : backingFieldOfEmail } set { if newValue.hasSuffix("@gmail.com") { backingFieldOfEmail = newValue } } } init(email: String) { self.backingFieldOfEmail = email } } ```

 

Lazy properties 

In Kotlin, lazy properties are based on the concept of [delegated properties](https://kotlinlang.org/docs/delegated-properties.html). By default, the evaluation of lazy properties is synchronized, but you can choose your own strategy: `SYNCHRONIZED`, `PUBLICATION` or `NONE`. 

 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ```kotlin data class ImmutableGmailUser(val email: String) { //(1) No `return` at the end of a closure val name: String by lazy(LazyThreadSafetyMode.NONE) { /*(1)*/ email.split("@")[0] } val domain: String by lazy { email.split("@")[1] } } ```

 

In Swift, you must always declare a lazy property with the ’var` keyword. 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 ```swift struct ImmutableGmailUser { //(1) Lazy property has to be declared as `var` (not `let`) //(2) Lazy property must have an initializer `=` //(3) There are parentheses `()` after a property block lazy var /*(1)*/ name: String = /*(2)*/ { return String(email.split(separator: "@")[0]) }() /*(3)*/ lazy var domain: String = { let emailComponents = email.split(separator: "@") return emailComponents.count == 2 ? String(emailComponents[1]) : "" }() let email: String init(email: String) { self.email = email } } ```

 

Extension 

Kotlin extensions are resolved statically. Extensions do not actually modify the classes they extend. For instance, using an extension, you can not override a method. See the example below. 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ```kotlin interface Shape { fun area(): Double = 0.0 //default implementation } class Rectangle(val sideLength: Double): Shape fun Rectangle.area(): Double { //does not override Shape::area() return sideLength * sideLength } val shape: Shape = Rectangle(2.0) print(shape.area()) //0.0 -> Shape.area() is called val rectangle = Rectangle(2.0) print(rectangle.area()) //4.0 -> Rectangle.area() is called ```

Swift extensions are resolved dynamically. Extensions add new functionality to an existing class, structure, enumeration, or protocol type. 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 ```swift protocol Shape { func area()-> Double } extension Shape { func area()-> Double {//default implementation return 0.0 } } struct Rectangle: Shape { let sideLenght: Double } extension Rectangle { func area()-> Double { //overrides Shape::area() return self.sideLenght * self.sideLenght } } let shape: Shape = Rectangle(sideLenght: 2.0) print("\(shape.area())")//4.0 -> Rectangle.area() is called let rectangle = Rectangle(sideLenght: 2.0) print("\(rectangle.area())")//4.0 -> Rectangle.area() is called ```

 

Collections 

Kotlin has different classes for mutable and immutable collections. 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ```kotlin val mutableListOfNumbers: MutableList<String> = mutableListOf("1", "2", "3") mutableListOfNumbers.add("4") mutableListOfNumbers.removeAt(3) mutableListOfNumbers[0] = "-1" val immutableListNumbers: List<String> = listOf("1", "2", "3") //immutableListNumbers.add("4") doesn't compile //immutableListNumbers.removeAt(3) doesn't compile //immutableListNumbers[0] = "-1" doesn't compile ```

 

General rule is simple to remember, just add `mutable` prefix if you want to declare a mutable collection. 

 

1 2 3 4 5 6 7 ```console List<T> ---> MutableList<T> listOf<T>(...) ---> mutableListOf<T>(...) ```

The following collection types are relevant for Kotlin: 

1. List - ordered collection 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ```kotlin val mutableNumbers: MutableList<Int> = mutableListOf(1,2,3) val immutableNumbers: List<Int> = List(3){ index -> index+1 } var shoppingList = listOf("Eggs", "Milk") for (item in shoppingList) { println(item) } ```

2. Set - collection of unique elements 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ```kotlin val letters = mutableSetOf<Char>()//empty set val favouriteGenres = mutableSetOf("Rock", "Classical", "Hip hop")//initialized //iteration for (favouriteGenre in favouriteGenres) { println(favouriteGenre) } ```

3. Map (or dictionary) - a set of key-value pairs 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ```kotlin val namesOfIntegers = mutableMapOf<Int,String>()//empty map val airports = mutableMapOf("YYZ" to "Toronto Pearson", "DUB" to "Dublin")//initialized //iteration for ((airportCode, airportName) in airports) { print("$airportCode: $airportName") } ```

 

4. Array - fixed-size, ordered collection. 

1 2 3 4 5 6 7 8 9 ```kotlin val numbers1 = arrayOf("1","2","3") //numbers.add("4") doesn't compile (Arrays are fixed-size) val numbers2 = Array(3){ (it+1).toString() } ```

 

To avoid boxing overhead, Kotlin has classes that represent arrays of primitive such as: `ByteArray`, `ShortArray`, `IntArray`. 

Swift has three primary collection types, known as arrays, sets, and dictionaries. 

Collections declared with the `var` keyword are mutable, and theose declared with the `let` keyword are immutable. 

1. Array - ordered collection 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ```swift var someInts: [Int] = [] //empty array var threeDoubles = Array(repeating: 0.0, count: 3) //with 3 items (default value 0.0) var shoppingList = ["Eggs", "Milk"] //initialized //iteration for item in shoppingList { print(item) } ```

2. Set - unordered collection of unique values 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ```swift var letters = Set<Character>()//empty set var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]//initialized //iteration for genre in favoriteGenres { print("\(genre)") } ```

3. Dictionaries - unordered collection of key-value associations 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ```swift var namesOfIntegers: [Int: String] = [:]//empty dictionary var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]//initialized //iteration for (airportCode, airportName) in airports { print("\(airportCode): \(airportName)") } ```

The article demonstrated the most significant syntax differences between the languages. However, there are more. To know them all, visit Kotlin and Swift documentation. 



Author
Robert Andrzejczyk
iOS Developer