Swift Methods - Adding Functionality to Types
Welcome to Swift Methods! Methods are functions that are associated with a particular type. They define the behavior and functionality of classes, structures, and enumerations. In this guide, we’ll explore all types of methods and how to use them effectively.
What Are Methods?
Methods are functions that belong to a specific type (class, struct, or enum). They provide functionality and can access the properties of their type.
Types of Methods:
- Instance Methods - Called on instances of a type
- Type Methods - Called on the type itself
- Mutating Methods - Modify value types (structs/enums)
Instance Methods
Instance methods are functions that belong to instances of a class, structure, or enumeration.
Basic Instance Methods
class Counter { var count = 0
func increment() { count += 1 }
func increment(by amount: Int) { count += amount }
func reset() { count = 0 }}
let counter = Counter()counter.increment()print(counter.count) // 1
counter.increment(by: 5)print(counter.count) // 6
counter.reset()print(counter.count) // 0Methods with Parameters
struct Calculator { func add(_ a: Double, _ b: Double) -> Double { return a + b }
func subtract(_ a: Double, from b: Double) -> Double { return b - a }
func multiply(_ a: Double, by b: Double) -> Double { return a * b }}
let calc = Calculator()print(calc.add(5, 10)) // 15.0print(calc.subtract(5, from: 10)) // 5.0print(calc.multiply(5, by: 3)) // 15.0Methods with Return Values
class BankAccount { private var balance: Double = 0
func deposit(_ amount: Double) -> Double { balance += amount return balance }
func withdraw(_ amount: Double) -> Bool { guard amount <= balance else { return false } balance -= amount return true }
func getBalance() -> Double { return balance }}
let account = BankAccount()let newBalance = account.deposit(100)print("New balance: $\(newBalance)") // $100.0
if account.withdraw(50) { print("Withdrawal successful") print("Balance: $\(account.getBalance())") // $50.0}The self Property
self refers to the current instance within an instance method.
When to Use self
Usually optional, but required to disambiguate:
struct Point { var x: Double var y: Double
func isToRightOf(x: Double) -> Bool { return self.x > x // self.x is the property, x is the parameter }
func distanceTo(point: Point) -> Double { let dx = self.x - point.x let dy = self.y - point.y return (dx * dx + dy * dy).squareRoot() }}
let point1 = Point(x: 3, y: 4)let point2 = Point(x: 0, y: 0)
print(point1.isToRightOf(x: 2)) // trueprint(point1.distanceTo(point: point2)) // 5.0self in Closures
class ViewController { var name = "Main"
func setupHandler() { someAsyncFunction { [weak self] in guard let self = self else { return } print(self.name) } }
func someAsyncFunction(completion: @escaping () -> Void) { completion() }}Mutating Methods
Structures and enumerations are value types. To modify properties, methods must be marked mutating.
Basic Mutating Methods
struct Point { var x: Double var y: Double
mutating func moveBy(x deltaX: Double, y deltaY: Double) { x += deltaX y += deltaY }
mutating func reset() { x = 0 y = 0 }}
var point = Point(x: 10, y: 20)print("Initial: (\(point.x), \(point.y))") // (10.0, 20.0)
point.moveBy(x: 5, y: -10)print("After move: (\(point.x), \(point.y))") // (15.0, 10.0)
point.reset()print("After reset: (\(point.x), \(point.y))") // (0.0, 0.0)Assigning to self
Mutating methods can assign a completely new instance to self:
struct Point { var x: Double var y: Double
mutating func moveBy(x deltaX: Double, y deltaY: Double) { self = Point(x: x + deltaX, y: y + deltaY) }}
var point = Point(x: 10, y: 20)point.moveBy(x: 5, y: -10)print("(\(point.x), \(point.y))") // (15.0, 10.0)Mutating Enum Methods
enum Switch { case on case off
mutating func toggle() { switch self { case .on: self = .off case .off: self = .on } }}
var lightSwitch = Switch.offprint(lightSwitch) // off
lightSwitch.toggle()print(lightSwitch) // on
lightSwitch.toggle()print(lightSwitch) // offCannot Call on Constants
struct Counter { var count = 0
mutating func increment() { count += 1 }}
var mutableCounter = Counter()mutableCounter.increment() // ✅ OK
let immutableCounter = Counter()// immutableCounter.increment() // ❌ Error - cannot mutate let constantType Methods
Type methods are called on the type itself, not on instances. Use static keyword.
Static Methods
struct Math { static func square(_ number: Double) -> Double { return number * number }
static func cube(_ number: Double) -> Double { return number * number * number }
static func add(_ a: Double, _ b: Double) -> Double { return a + b }}
print(Math.square(5)) // 25.0print(Math.cube(3)) // 27.0print(Math.add(10, 20)) // 30.0Type Methods Accessing Type Properties
struct LevelTracker { static var highestUnlockedLevel = 1 var currentLevel = 1
static func unlock(_ level: Int) { if level > highestUnlockedLevel { highestUnlockedLevel = level } }
static func isUnlocked(_ level: Int) -> Bool { return level <= highestUnlockedLevel }
mutating func advance(to level: Int) -> Bool { if LevelTracker.isUnlocked(level) { currentLevel = level return true } return false }}
LevelTracker.unlock(5)print("Highest level: \(LevelTracker.highestUnlockedLevel)") // 5
var player = LevelTracker()if player.advance(to: 3) { print("Advanced to level 3")}Class Type Methods
In classes, use class keyword to allow overriding:
class Vehicle { class func description() -> String { return "A vehicle" }}
class Car: Vehicle { override class func description() -> String { return "A car" }}
print(Vehicle.description()) // A vehicleprint(Car.description()) // A carFactory Methods
Type methods are great for creating instances:
struct Color { var red: Double var green: Double var blue: Double
static func black() -> Color { return Color(red: 0, green: 0, blue: 0) }
static func white() -> Color { return Color(red: 1, green: 1, blue: 1) }
static func red() -> Color { return Color(red: 1, green: 0, blue: 0) }
static func custom(red: Double, green: Double, blue: Double) -> Color { return Color(red: red, green: green, blue: blue) }}
let black = Color.black()let customColor = Color.custom(red: 0.5, green: 0.5, blue: 0.8)Practical Examples
Example 1: String Utilities
struct StringHelper { static func isPalindrome(_ text: String) -> Bool { let cleaned = text.lowercased().filter { $0.isLetter } return cleaned == String(cleaned.reversed()) }
static func wordCount(_ text: String) -> Int { return text.split(separator: " ").count }
static func capitalize(_ text: String) -> String { return text.split(separator: " ") .map { $0.prefix(1).uppercased() + $0.dropFirst().lowercased() } .joined(separator: " ") }}
print(StringHelper.isPalindrome("racecar")) // trueprint(StringHelper.wordCount("Hello World")) // 2print(StringHelper.capitalize("hello world")) // Hello WorldExample 2: Temperature Converter
struct TemperatureConverter { var celsius: Double
var fahrenheit: Double { return celsius * 9/5 + 32 }
var kelvin: Double { return celsius + 273.15 }
// Instance method func description() -> String { return String(format: "%.1f°C = %.1f°F = %.1fK", celsius, fahrenheit, kelvin) }
// Type methods static func fromFahrenheit(_ temp: Double) -> TemperatureConverter { return TemperatureConverter(celsius: (temp - 32) * 5/9) }
static func fromKelvin(_ temp: Double) -> TemperatureConverter { return TemperatureConverter(celsius: temp - 273.15) }}
let temp1 = TemperatureConverter(celsius: 25)print(temp1.description())
let temp2 = TemperatureConverter.fromFahrenheit(77)print(temp2.description())Example 3: Player Character
class Player { var name: String private var health: Int private var maxHealth: Int var level: Int = 1
init(name: String, health: Int) { self.name = name self.health = health self.maxHealth = health }
func takeDamage(_ amount: Int) { health = max(0, health - amount) print("\(name) took \(amount) damage. Health: \(health)/\(maxHealth)")
if health == 0 { die() } }
func heal(_ amount: Int) { health = min(maxHealth, health + amount) print("\(name) healed \(amount). Health: \(health)/\(maxHealth)") }
func levelUp() { level += 1 maxHealth += 20 health = maxHealth print("\(name) leveled up to \(level)! Max health: \(maxHealth)") }
private func die() { print("\(name) has been defeated!") }
func isAlive() -> Bool { return health > 0 }}
let hero = Player(name: "Warrior", health: 100)hero.takeDamage(30)hero.heal(20)hero.levelUp()Example 4: Stack Data Structure
struct Stack<Element> { private var items: [Element] = []
var count: Int { return items.count }
var isEmpty: Bool { return items.isEmpty }
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element? { return items.popLast() }
func peek() -> Element? { return items.last }
mutating func clear() { items.removeAll() }}
var stack = Stack<Int>()stack.push(1)stack.push(2)stack.push(3)
print("Top: \(stack.peek() ?? 0)") // 3print("Popped: \(stack.pop() ?? 0)") // 3print("Count: \(stack.count)") // 2Example 5: Validation Helper
class Validator { static func isValidEmail(_ email: String) -> Bool { let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}" let predicate = NSPredicate(format: "SELF MATCHES %@", emailRegex) return predicate.evaluate(with: email) }
static func isValidPhone(_ phone: String) -> Bool { let cleaned = phone.filter { $0.isNumber } return cleaned.count == 10 }
static func isStrongPassword(_ password: String) -> Bool { guard password.count >= 8 else { return false }
let hasUppercase = password.contains(where: { $0.isUppercase }) let hasLowercase = password.contains(where: { $0.isLowercase }) let hasNumber = password.contains(where: { $0.isNumber })
return hasUppercase && hasLowercase && hasNumber }}
print(Validator.isValidEmail("test@example.com")) // trueprint(Validator.isValidPhone("1234567890")) // trueprint(Validator.isStrongPassword("Pass123")) // trueBest Practices
1. Use Clear Method Names
// ✅ Good - descriptive namesfunc calculateTotal(for items: [Item]) -> Double { }func sendEmail(to recipient: String) { }
// ❌ Bad - unclearfunc calc(_ items: [Item]) -> Double { }func send(_ to: String) { }2. Keep Methods Focused
// ✅ Good - single responsibilityfunc validateEmail() -> Bool { }func sendEmail() { }
// ❌ Bad - doing too muchfunc validateAndSendEmail() { }3. Use Type Methods for Utilities
// ✅ Good - utility functionsstruct DateHelper { static func formatDate(_ date: Date) -> String { } static func daysAgo(_ count: Int) -> Date { }}
// ❌ Bad - doesn't need instanceclass DateHelper { func formatDate(_ date: Date) -> String { }}4. Mark Mutating When Needed
struct Counter { var count = 0
// ✅ Marked as mutating mutating func increment() { count += 1 }
// ✅ Not mutating - just returns value func doubled() -> Int { return count * 2 }}5. Use Private for Helpers
class DataProcessor { func processData() { let cleaned = cleanData() validate(cleaned) }
// ✅ Private helper methods private func cleanData() -> [String] { return [] }
private func validate(_ data: [String]) { // Validation logic }}Summary
Methods define the behavior of your types:
Instance Methods 🎯
- Called on instances
- Access instance properties
- Define type behavior
self Property 🪞
- Refers to current instance
- Disambiguates properties
- Required in closures with [weak self]
Mutating Methods 🔄
- Modify value types
- Required for structs/enums
- Can replace self entirely
Type Methods 🏢
- Called on the type
- Use static keyword
- Great for factories and utilities
Next Steps
Congratulations! You’ve completed the core intermediate topics! 🎉
Next in the roadmap:
- Topic 16: Inheritance
- Subclassing
- Overriding methods and properties
- Preventing overrides (final)
- Super keyword
Practice Exercises
- Create a
Calculatorclass with various math methods - Build a
StringManipulatorwith mutating methods - Implement a
Queuewith enqueue/dequeue methods - Create utility type methods for date formatting
- Build a game character with attack/defend methods
- Implement a
Validatorclass with static validation methods
Master methods to bring your types to life! 🚀
Remember: Methods define what your types can DO!