Object-oriented programming (OOP) organizes code around objects: bundles of data and the behavior that belongs to it. UIKit, Foundation, and most of Apple’s older frameworks are built this way, so understanding OOP is essential for reading and writing real iOS code.
Swift supports OOP fully, with its own twists: structs and enums can have methods too, there’s no multiple class inheritance, and protocols often take the place of abstract classes. This guide covers the classic ideas and how they translate to Swift.
Article available Coming soon
1. Foundations
Thinking in Objects: Modeling a Domain in Swift
A library application has books, readers, and loans. Turning every noun into a class gives you a diagram, but not necessarily a...
Structures and Classes in Swift: Value vs. Reference Types
Structures and classes look almost identical in Swift: both have properties, methods, initializers, and extensions, and both can conform to protocols. The...
Value Types vs. Reference Types in Swift: Choosing Ownership
Choosing between a struct and a class is often a question about ownership: should two parts of the program share one changing...
Reading and Drawing UML Class Diagrams for Swift
A class diagram is useful when it explains relationships that are difficult to see in one source file. It becomes less useful...
2. The Four Pillars
Encapsulation in Swift: Protecting Valid State
A type is not well encapsulated just because its properties are private. Encapsulation is useful when the type controls the rules that...
Abstraction in Swift: Designing Interfaces Around Intent
A delivery screen needs to find out where a parcel is. It should not need to know an endpoint path, a JSON...
Inheritance in Swift
Inheritance lets one class build on another: a subclass gets its superclass’s properties and methods, can add new ones, and can override...
Polymorphism in Swift: One Interface, Different Behavior
A notification can be written to a console, recorded in a test, or sent to another service. Code that requests a notification...
3. How Classes Work in Swift
Initializers and Deinitializers: The Lifetime of a Swift Object
An initializer establishes the state an object needs to be usable. A deinitializer performs cleanup when that object’s lifetime ends. They are...
Designated and Convenience Initializers in Swift
A class can offer several ways to create an instance without having several independent implementations of its setup rules. Designated initializers establish...
Overriding Methods and Properties in Swift
An override customizes behavior that a superclass already promises. It is useful when the subclass remains a valid replacement for the parent,...
Method Overloading in Swift: Let the Call Site Explain the Choice
Overloading lets several methods share a name while accepting different argument lists or types. Used carefully, it makes related operations easy to...
final in Swift: Closing Inheritance Deliberately
A subclass can customize behavior its parent exposes, which means a subclassable class has a larger contract than its public method list...
Type Properties and Methods in Swift: static vs. class
Some behavior belongs to an instance, and some belongs to the type. Formatting a particular document title uses instance state; the default...
Swift Root/Base Class & NSObject: A Quick Look
In the world of object-oriented programming (OOP), the concept of a root or base class is foundational for understanding class inheritance and...
4. Contracts and Relationships
Protocols in Swift
A protocol describes what a type can do, without saying how. Classes, structs, and enums can all adopt protocols, which makes them...
Protocols Instead of Abstract Classes in Swift
In some languages, an abstract class defines operations that subclasses must implement and also provides shared behavior. Swift does not have an...
Protocols Instead of Multiple Inheritance in Swift
A downloadable report may be identifiable, serializable, and shareable. Those capabilities do not require three parent classes. Swift classes have at most...
Parent and Child Classes in Swift: Inheritance vs. Ownership
“Parent” and “child” can describe two very different relationships. A child class may mean a subclass that inherits behavior. A child object...
Composition Over Inheritance in Swift
A class hierarchy is tempting when two features share behavior. But “uses the same behavior” is weaker than “is a valid kind...
Delegation in Swift: One Object Reporting to Another
A picker knows which item the user selected. The screen that owns the picker knows what that selection means for the application....
5. Under the Hood
Static vs. Dynamic Dispatch in Swift
When your code calls a method, the program has to decide which implementation runs. Sometimes the compiler knows at compile time: that’s...
Access Control in Swift
Access control decides which parts of your code can see which other parts. It lets a type hide its implementation behind a...
Reflection in Swift with Mirror
Reflection is a program inspecting its own structure at runtime: “what properties does this value have, and what’s in them?” Swift’s reflection...
Equality, Identity, and Hashable in Swift
Two user objects can represent the same account without being the same instance. Two values can also produce the same hash without...
Deep Copy vs. Shallow Copy in Swift
Assigning a class reference does not copy its object. Copying a container of class references does not recursively clone its elements either....
6. Designing Good Objects
The SOLID Principles in Swift: Boundaries That Survive Change
SOLID names five design principles for managing change in object-oriented programs. The principles are useful questions to ask about dependencies and contracts;...
Dependency Injection in Swift Without a Container
A feature is difficult to test when it secretly reads the clock, opens a database, or creates its own network client. Dependency...
The Law of Demeter in Swift: Reduce Knowledge of Object Internals
A checkout screen that reads order.customer.account.deliverySettings.preferredAddress knows the internal shape of several collaborators. Changing account storage can now break checkout code that...
Common Object-Oriented Anti-Patterns in Swift
An object-oriented design becomes difficult to change when responsibilities and ownership stop matching the feature’s behavior. The symptoms often appear before the...
Mixing Object-Oriented and Functional Programming in Swift
An application needs effects: reading files, requesting data, and updating state. It also needs calculations that should be predictable. Swift lets you...
A Quick Tour of the Key Ideas
Encapsulation keeps an object’s data and the code that changes it together, and hides the details behind a small public interface. In Swift, access control (private, fileprivate, internal, public) is how you draw that line.
Abstraction means exposing what something does while hiding how. A PaymentProcessor should offer pay(amount:), not a dozen network details.
Inheritance lets a subclass reuse and extend a parent class. In Swift, only classes can inherit, and each class has at most one superclass. Structs and enums share behavior through protocols and extensions instead.
Polymorphism lets you treat different types through a common interface: a [Shape] array can hold circles and squares, and calling area() runs the right implementation for each. Swift achieves this with class inheritance and with protocols.
SOLID is a set of five design principles that keep object-oriented code flexible:
- Single Responsibility: a type should have one reason to change.
- Open/Closed: open for extension, closed for modification.
- Liskov Substitution: subclasses must work anywhere their parent does.
- Interface Segregation: prefer small, focused protocols over big ones.
- Dependency Inversion: depend on abstractions, not concrete types.
Where OOP Falls Short
OOP is powerful, but it isn’t free. Knowing its weak spots helps you decide when to reach for something else:
- Shared mutable state. Objects are references, so two parts of an app can change the same object, and bugs appear far from their cause. Swift’s value types avoid this by giving each owner its own copy.
- Fragile base classes. A change in a parent class can quietly break its subclasses. Deep inheritance hierarchies make this worse.
- Tight coupling. Classes that create and depend on concrete classes are hard to test and hard to change. Protocols and dependency injection loosen this up.
- Boilerplate. Simple ideas can end up spread across many small classes.
- Some runtime cost. Dynamic dispatch and reference counting (ARC) add a little overhead compared with value types and static dispatch. It rarely matters, but it’s there.
The alternatives
Swift is a multi-paradigm language, so you can mix approaches freely:
- Protocol-oriented programming: share behavior through protocols and extensions, and use value types by default. This is the style Apple recommends for Swift.
- Functional programming: build programs from small, pure functions and immutable data, which makes code easier to test and reason about.
- Declarative programming: describe what you want and let the framework work out how. SwiftUI is the best-known example in the Apple world.
- Procedural programming: plain functions operating on data, a perfectly good fit for small scripts and tools.
Up next
Protocol-Oriented Programming