Swift OOP & Protocols
Classes, structs, enums, protocols, extensions, protocol-oriented programming
- 01
What is the difference between struct and class in Swift?
EasyA struct is a value type and a class is a reference type; almost every other difference follows from that one.
// Struct — value semantics struct Point { var x: Int var y: Int // memberwise init synthesized for free // mutating method — required because struct is value type … - 02
What are protocols in Swift and how are they different from interfaces?
EasyA protocol declares a contract — methods, properties, associated types, initializers — that conforming types must implement.
protocol Greetable { var name: String { get } func greet() -> String } // Default implementation via extension … - 03
Explain enums with associated values and raw values in Swift.
MediumA Swift enum is a full type that can carry data, which is why it models a closed set of alternatives far better than a set of integer constants.
// Raw values enum HTTPStatus: Int { case ok = 200 case notFound = 404 case serverError = 500 } … - 04
What are extensions in Swift and what can/can't they do?
MediumExtensions add functionality to an existing type — your own types, standard library types, even types imported from a framework — without subclassing and without touching the original source.
// Add helpers to stdlib types extension String { var trimmed: String { trimmingCharacters(in: .whitespacesAndNewlines) } var isBlank: Bool { trimmed.isEmpty } } … - 05
What is protocol-oriented programming and how is it different from class inheritance?
MediumProtocol-oriented programming (POP) — a Swift idiom championed at WWDC 2015 — composes behavior via small protocols and default implementations rather than deep class hierarchies.
// Tiny composable protocols protocol Entity { var id: String { get } } protocol Persistable { func save() throws } protocol Loggable { func log() -> String } extension Loggable where Self: Entity { … - 06
Explain access control levels in Swift.
MediumSwift has six access levels, and a member is always capped by the visibility of the type that contains it.
// In a framework target open class Widget { /* subclassable from apps */ } public class Sealed { /* visible everywhere, NOT subclassable from outside */ } public struct User { public var name: String // visible … - 07
What are property observers and lazy properties?
MediumSwift lets a stored property carry behaviour in two ways: observers that fire around a change, and
lazy, which defers the initializer until the first read.// Observers class Profile { var name: String = "" { willSet { print("Will change \(name) → \(newValue)") } didSet { if name != oldValue { rebuildHeader() } } } … - 08
Walk through class initialization in Swift: designated, convenience, and what the two phases are.
MediumSwift will not let you touch self until every stored property in the whole inheritance chain has a value, and the designated versus convenience split is the rule system that guarantees it.
class Vehicle { let wheels: Int var name: String init(wheels: Int, name: String) { // designated self.wheels = wheels … - 09
What does your own type need before it can be a Dictionary key or live in a Set?
EasyIt has to conform to Hashable, which means being Equatable as well and hashing in a way that agrees with equality.
struct Tag: Hashable { // == and hash(into:) both synthesized let name: String let colorHex: String } var seen: Set<Tag> = [] … - 10
Why is a delegate property almost always declared weak, and what must the protocol declare for that to compile?
MediumBecause the delegate normally owns the object that calls it, so a strong reference back would close a cycle and leak both sides forever.
@MainActor protocol UploaderDelegate: AnyObject { // ✅ class-bound, so weak is legal func uploader(_ uploader: Uploader, didProgress fraction: Double) func uploader(_ uploader: Uploader, didFinish url: URL) func uploader(_ uploader: Uploader, didFail error: Error) } … - 11
A method lives only in a protocol extension, and a conforming type defines its own version. Which one runs?
HardIt depends on the type the compiler sees at the call site, because a method that is not a protocol requirement is dispatched statically rather than through the conformance.
protocol Greeter { func greet() -> String // ✅ a requirement — dynamically dispatched } extension Greeter { func greet() -> String { "hello from the default" } … - 12
What is the difference between static and class members, and what does final change?
MediumA
classmember can be overridden by a subclass and astaticmember cannot — static is simply the final form of a type-level declaration.class Formatter { static let shared = Formatter() // lazily initialized once, thread-safe static func staticName() -> String { "Formatter" } // cannot be overridden class func kind() -> String { "generic" } // subclasses may override … - 13
Your struct's method will not compile until you add one keyword, and the identical method on a class needs nothing — what does that keyword do?
Easymutatingmarks a method that changes the value, and the mechanism behind it is thatselfis passedinout— the write lands in the caller's storage instead of in a copy.struct Counter { private(set) var value = 0 mutating func bump() { value += 1 } // self is inout under the hood func peek() -> Int { value } // no mutation, no keyword } … - 14
A protocol declares var id: String { get set }, your struct stores it as a let, and the conformance fails — what is the compiler asking for?
Easy{ get set }requires a property you can write to, and aletcan only be read — make it avar, or supply a computed property with both accessors.protocol Identified { var id: String { get } // read-only requirement var title: String { get set } // must be writable static var kind: String { get } } … - 15
You copy a struct into a second view model, change one field on the copy, and the original changes too — how is that possible for a value type?
MediumOne of the struct's stored properties is a class reference: the copy duplicated the reference, not the object, and both structs now point at the same instance.
final class Draft { // reference type hiding inside a value type var text: String init(text: String) { self.text = text } } struct Note { … - 16
You are modelling payment methods that each behave differently — enum with associated values, or a protocol with one type per method?
MediumEnum when you own the whole set of cases and want the compiler to find every place that must change; protocol when code you do not control has to add a case you will never see.
struct Receipt { let id: UUID } // Closed set: the compiler polices every switch enum PaymentMethod { case card(last4: String, expiry: Date) case applePay(token: String) … - 17
Can a subclass override a stored property, and what happens to the superclass's storage when it does?
MediumIt can override the access — with a computed property or with observers — but never the storage: the superclass's stored property is still there, and the override normally has to read and write it through
super.class Vehicle { var speed: Double = 0 // the storage lives here, always var summary: String { "moving at \(speed)" } } final class Train: Vehicle { … - 18
You add an initializer to a protocol, a non-final class conforms, and the compiler demands
required— what is that rule protecting?MediumConformance is inherited by every subclass, so each subclass is also a conformer — and
requiredis the only way the compiler can guarantee they all really have that initializer.protocol Buildable { init(id: String) } final class Session: Buildable { // final: no subclasses, no keyword let id: String … - 19
A type conforms to two protocols that each ship a default implementation of refresh() — what does Swift do, and how do you resolve it?
HardSwift has no linearization of protocol inheritance, so it refuses to guess: unless one default is strictly more specialized than the other, you get an error until the concrete type supplies its own implementation.
protocol Reloadable { func refresh() } protocol Pollable { func refresh() } extension Reloadable { func refresh() { print("from cache") } } extension Pollable { func refresh() { print("from network") } } … - 20
You need a type that owns a file descriptor, closes it exactly once, and cannot be silently copied — what does Swift give you?
HardA noncopyable struct —
struct FileHandle: ~Copyable— which suppresses the implicitCopyableconformance, gives the value exactly one owner, and lets a struct declaredeinitfor the first time.import System struct FileHandle: ~Copyable { private let fd: Int32 init(openingAt path: String) throws { …