Transitioning to Swift for Android Kotlin Developers
Sharing my cheat sheet for essential Swift concepts to kickstart my journey
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.
Simple Values and Types
- Integers:
Int
- Double:
Double
(64 bits, default floating-point numbers) - Float:
Float
(32 bits) - String:
String
- Booleans:
Bool
, which can betrue
orfalse
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
isnil
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
orvar
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 anarray
.
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 ifset2
contains all elements ofset3
.set2.isDisjoint(with: set3)
: Checks ifset2
andset3
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 toif
for unwrappingoptionals
.
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 ado-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:
- Writing
async
before alet
statement when defining a constant - 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. 😀