Transitioning to Swift for Android Kotlin Developers

Sharing my cheat sheet for essential Swift concepts to kickstart my journey

Ryan W
8 min readAug 16, 2024

For those proficient in Android Kotlin, stepping into Swift may initially seem challenging. However, many familiar concepts exist that can ease the transition. This cheat sheet is crafted to help me quickly grasp the core differences and similarities between Swift and Kotlin, laying a solid foundation for developing iOS applications.

This image was created using an AI image creation program.

Simple Values and Types

  • Integers: Int
  • Double: Double (64 bits, default floating-point numbers)
  • Float: Float (32 bits)
  • String: String
  • Booleans: Bool, which can be true or false

While Kotlin has val and var, Swift has something very similar:

  • Constants: let
  • Variables: var

String operations

  • Use + to join two strings.

String Interpolation

someResult = "The result is: \(rating)"

Conditionals

1. if-else

⚠️ There is no need to wrap the condition using ( ).

if condition {
code1
} else if abc == "Yellow" {
code2
} else {
code3
}

2. switch

switch value {
case firstValue:
code1
case secondValue:
code2
default:
code3
}

Optionals and Optional Binding

The concept of optionals in Swift is similar to nullables in Kotlin.

var fullName: String?

⚠️ We can’t directly concatenate an optional with a regular string using the + operator; we must unwrap it first.

Force Unwrapping an Optional

We can force unwrap an optional, but this approach is risky because it will crash if it is nil.

let greetings = "Hello, " + fullName!

⚠️ This will crash if fullName is nil

Providing a Default Value

We can safely provide a default value using the nil-coalescing operator (??):

print(fullName ?? "No value")

Optional Binding

Optional binding allows us to safely unwrap an optional by attempting to assign its value to a temporary variable. If the assignment succeeds, we can proceed with a code block. This is similar to Kotlin’s ?.let{…}.

if let someTempVar = fullName {
let greeting = "Hello, " + someTempVar
print(greeting)
}

Range operators

let myRange = 10...20  // 10 to 20
let myRange = 10..<20 // 10 to 19

Loops

1. for-in loop

for index in myRange {
print(number)
}
for index in 0...5 { ... }
for index in (0...5).reversed() { }

2. while loop

while y < 50 {
y += 5
}

3. repeat-while loop

repeat{
code
} while condition == true

Collection Types

1. Arrays

  • Array indices start at 0.
  • If an array is created using the let keyword, its contents cannot be modified after creation.
var shoppingList = ["Eggs", "Milk"]
shoppingList.count
shoppingList.isEmpty
shoppingList.append("Butter")
shoppingList = shoppingList + ["Bread"]
shoppingList.insert("Chicken", at: 1)
shoppingList[2]
shoppingList.remove(at: 1)
  • We can use a for-in loop to iterate over an array:
for item in shoppingList {
// Do something with each item
}

for item in shoppingList[1...] {
// Do something with items from index 1 onwards
}

2. Dictionaries

  • Dictionaries in Swift are similar to maps in Kotlin.
  • They store key-value pairs in an unordered list.
  • Like arrays, dictionaries can be declared using let or var depending on whether we want them to be mutable or immutable.
var dict = ["Key1": "Value1", "Key2": "Value2"]
dict.count
dict.isEmpty
dict["Key1"]
dict["Key1"] = "New Value"
dict["Key3"] = nil
var oldDictValue = dict.removeValue(forKey: "Key1")
  • We can iterate over a dictionary using a for-in loop:
for (name, contactNumber) in dict {
print("\(name): \(contactNumber)")
}

3. Sets

  • To create a set, we must use type annotation, as Swift’s type inference will otherwise create an array.
var movieSet: Set = ["Horror", "Comedy"]
movieSet.count
movieSet.isEmpty
movieSet.insert("Drama")
movieSet.contains("Action")

// Returns nil if the value doesn't exist
var oldSetValue = movieSet.remove("Comedy")

Set operations:

  • .union(): Combines all values from two sets.
  • .intersection(): Contains only values common to both sets.
  • .subtracting(): Returns a new set without the values found in the specified set.
  • .symmetricDifference(): Returns a new set without the values common to both sets.

Set membership and equality:

  • set1 == set2: Checks if two sets are equal.
  • set1.isSubset(of: set2): Checks if all elements of set1 are contained within set2.
  • set2.isSuperset(of: set3): Checks if set2 contains all elements of set3.
  • set2.isDisjoint(with: set3): Checks if set2 and set3 have no elements in common.

Functions

func functionName(
parameter1: ParameterType,
...
) -> ReturnType {
code
}

functionName(parameter1: someValue)
  • Custom Argument Labels: We can provide custom argument labels to make function calls more readable.
func serviceCharge(forMealPrice mealCost: Int) -> Int {
mealCost / 10
}
let service = serviceCharge(forMealPrice: 50)
  • Nested Functions: Functions can be nested within other functions.
  • First-Class Functions: Functions in Swift are first-class types, meaning they can be passed as arguments and returned from other functions, similar to Kotlin.
func makePi() -> (() -> Double) {
func generatePi() -> Double {
return 22.0 / 7.0
}
return generatePi
}
let pi = makePi()
print(pi())
  • Implicit Returns: Swift allows functions, methods, and computed properties with a single expression to omit the return keyword.
func tableView(
_ tableView: UITableView,
numberOfRowsInSection section: Int
) -> Int {
journalEntries.count
}
  • Using Functions as Parameters: Functions can be used as parameters to other functions.
func isThereAMatch(
listOfNumbers: [Int],
condition: (Int) -> Bool
) -> Bool {
for item in listOfNumbers {
if condition(item) {
return true
}
}
return false
}
  • Guard Statement: guard is used to perform early exits, offering a cleaner alternative to if for unwrapping optionals.
func buySomething(
itemValueEntered itemValueField: String,
cardBalance: Int
) -> Int {
guard let itemValue = Int(itemValueField) else {
print("error")
return cardBalance
}
// ... proceed with purchase
}

Closures

Closures are similar to functions but do not have names. They are enclosed in curly braces { } and use the in keyword to separate parameters and the return type from the body. This is analogous to lambdas in Kotlin.

let mappedTestNumbers = testNumbers.map { number in
number * number
}
  • Shorthand Argument Names: We can use $0, $1, etc., to refer to arguments by their position in the closure.

let mappedTestNumbers = testNumbers.map { $0 * $0 }

Classes, Structures and Enumerations

🔍 Reference: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/classesandstructures/

1. Classes

Classes are used to define objects with properties and methods. They can also include initialisers.

class ClassName {
var name: String = " "
var numberOfLegs: Int = 0
var breathesOxygen: Bool = true

// Initializer
init(name: String, breathesOxygen: Bool) {
self.name = name
self.breathesOxygen = breathesOxygen
}

func method1() {
print("This is a method in the class.")
}
}

let cat = ClassName(name: "Cat", breathesOxygen: true)
  • Inheritance: Classes can inherit from other classes, allowing the subclass to override methods and properties.
class Mammal: ClassName {
let extraVariable: Bool = true

override func method1() {
super.method1()
print("This method is overridden.")
}
}

2. Structures

Structures in Swift are similar to Kotlin’s data classes. They automatically provide an initialiser for all their properties, known as the memberwise initialiser. Unlike classes, structures cannot be inherited.

struct Reptile {
var name: String
var hasFurOrHair: Bool = false

func makeSound() {
print("Reptile makes a sound.")
}
}

let snake = Reptile(name: "Snake")

Reference vs. Value Types:

  • Classes are reference types, meaning they are passed by reference.
  • Structures are value types, meaning they are passed by value.

3. Enumerations

Enumerations, or enums, allow us to define a common type for a group of related values and work with those values in a type-safe way.

🔍 Reference: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/enumerations/

enum TrafficLightColor {
case red
case yellow
case green

func description() -> String {
switch self {
case .red:
return "Red"
case .yellow:
return "Yellow"
case .green:
return "Green"
}
}
}

var trafficLightColor = TrafficLightColor.red
print(trafficLightColor.description()) // Output: "Red"

Protocols, Extensions and Error Handling

1. Protocols

A protocol in Swift is similar to an interface in other programming languages. It defines a blueprint of methods, properties, or other requirements that classes, structures, or enumerations can adopt and implement.

Declaration: Properties in a protocol are declared using the var keyword and can be read-only { get } or read-write { get set }.

No override Needed: Unlike subclassing, when implementing protocol methods or properties, there’s no need to use the override keyword.

🔍 Reference: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/protocols/

protocol CalorieCount {
var calories: Int { get }
func description() -> String
}

class Burger: CalorieCount {
let calories = 800

func description() -> String {
return "This burger contains \(calories) calories."
}
}

2. Extensions

Extensions in Swift enable us to add new functionality to existing classes, structures, enumerations, or protocols without altering their original code. This is similar to Kotlin’s extension functions but offers additional capabilities, like conforming to protocols.

🔍 Reference: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/extensions/

Swift Version: We use an extension to add a new method extendedMethod() to the ExistingType class. This method wasn’t part of the original class definition, but it can be called on any instance of ExistingType as if it were.

class ExistingType {
var property1: String

init(property1: String) {
self.property1 = property1
}

func method1() {
print("This is method1 with property1: \(property1)")
}
}

// Adding new functionality with an extension,
// similar to Kotlin's extension functions
extension ExistingType {
func extendedMethod() {
print("This is an extended method using property1: \(property1)")
}
}

Kotlin Version: Similarly, we use an extension function to add extendedMethod() to ExistingType. Like in Swift, this function can be called on any instance of ExistingType, even though it wasn’t originally part of the class.

class ExistingType(val property1: String) {
fun method1() {
println("This is method1 with property1: $property1")
}
}

// Adding new functionality with an extension function
fun ExistingType.extendedMethod() {
println("This is an extended method using property1: $property1")
}

3. Error Handling

Swift uses a clear and concise approach to error handling, defining potential errors by conforming to the Error protocol. Enumerations are typically used to represent different errors, each with associated values.

🔍 Reference: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/errorhandling/

For example:

enum WebsiteError: Error {
case siteDown
case somethingElse
}

func checkWebsite(siteUp: Bool) throws -> String {
if !siteUp {
throw WebsiteError.siteDown
}
return "Website is up and running."
}

do {
let siteStatus = true
let message = try checkWebsite(siteUp: siteStatus)
print(message)
} catch WebsiteError.siteDown {
print("The website is currently down.")
} catch {
print("An unexpected error occurred.")
}

In this example, the checkWebsite function throws a WebsiteError if the site is down. The do-catch block handles the error, and multiple catch blocks can handle different error types.

Concurrency

Apple added support for writing asynchronous and parallel code in Swift 5.5.

  • Asynchronous code allows our app to suspend and resume code
  • Parallel code allows our app to run multiple pieces of code simultaneously.

1. async/await: Asynchronous Code

  • To indicate that a method is asynchronous, use the async keyword in the method declaration:
func methodName() async -> returnType {}
  • Writing the await keyword before a method call marks a point where execution may be suspended, allowing other operations to run concurrently:
await methodName()

For example:

func makeToast() async -> String {
try? await Task.sleep(nanoseconds: 2 * 1_000_000_000)
return "done"
}

⚠️ ️Since this is a throwing method, the try? keyword is used to call it without implementing a do-catch block.

Any function that calls makeToast() must use await because it is an asynchronous function. If await cannot be used in the calling function, we can wrap the function call in a Task{} block. This allows it to execute asynchronously within a synchronous context.

func makeBreakfast() {
print("Starting breakfast preparation...")

Task {
let toast = await makeToast()
print("Toast is \(toast).")
}

print("Breakfast preparation in progress...")
}

2. async-let: Parallel Code

This enables the parallel execution of asynchronous methods:

  1. Writing async before a let statement when defining a constant
  2. Using await when accessing that constant
async let temporaryConstant1 = methodName1()
async let temporaryConstant2 = methodName2()
await variable1 = temporaryConstant1
await variable2 = temporaryConstant2

Wrapping Up

Mastering Swift as a Kotlin developer involves understanding each language's similarities and unique features. Fortunately, Swift shares many similarities with Kotlin, making the transition smoother than expected. 😀

--

--

Ryan W

Modern Android Development | Jetpack Compose | Kotlin Multiplatform 📱Creating Android Solutions From Concept to Product | Building Apps That Matter Since 2010