# Swift Defining Classes: Syntax, Properties, Methods, and Init

Source: https://devdocs10.com/articles/swift/swift-defining-classes  
By Murat Bo · Published 2026-05-22 · DevDocs10 (https://devdocs10.com)

Defining classes is one of the core skills in Swift because classes let you group related data and behavior into reusable custom types. In this article, you will learn how to declare a class, add properties and methods, write initializers, understand how classes differ from structs, and avoid common beginner mistakes when creating your own class types.

> **Quick answer:** In Swift, you define a class with the `class` keyword, followed by the class name and a body in braces. Inside the class, you usually add stored properties, methods, and one or more initializers to describe what the class stores and what it can do.

> **Difficulty:** Beginner
>
> **Helpful to know first:** You'll understand this better if you already know basic Swift syntax, variables and constants, functions, and simple types like `String`, `Int`, and `Bool`.

## 1. What Is Defining Classes?

Defining a class means creating a new custom reference type in Swift. A class acts like a blueprint for objects. That blueprint describes what data each object stores and what actions it can perform.

When you define a class, you are usually answering questions like these:

- What information should this type store?
- What operations should this type perform?
- How should a new instance of this type be created?
- Should this type support inheritance?

A simple way to think about a class is that it combines state and behavior:

- **State** is stored in properties.
- **Behavior** is written in methods.
- **Creation rules** are handled by initializers.

In Swift, classes are **reference types**. That means when you assign a class instance to another variable, both variables can refer to the same underlying object in memory. This is one of the most important differences between classes and structs.

> A class definition creates the blueprint. An instance is a real object created from that blueprint.

## 2. Why Defining Classes Matters

You define classes when you need a custom type that represents something with identity, shared mutable state, or behavior that naturally belongs together.

Classes matter because they let you model real program concepts clearly. For example:

- A `UserAccount` can store a username, email, and login state.
- A `BankAccount` can store a balance and perform deposits or withdrawals.
- A `Car` can store speed and fuel level and provide methods for driving or refueling.

They are especially useful when:

- Multiple parts of a program should reference the same object.
- You need inheritance.
- You want to model identity, not just value.

You should not automatically use a class for every custom type. In Swift, many types are better modeled as structs. A struct is often the better choice for simple data that should be copied by value instead of shared by reference. This class-vs-struct decision is a common Swift design question, and we will return to it later in the article.

## 3. Basic Syntax

The smallest useful class definition uses the `class` keyword and a name. Inside the braces, you can place properties, methods, and initializers.

### Basic class declaration

This example defines a class named `Person` with two stored properties, one initializer, and one method.

```swift
class Person {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func introduce() {
        print("Hi, I'm \(name) and I'm \(age) years old.")
    }
}
```

This class has three major parts:

- `var name` and `var age` are stored properties.
- `init(...)` is the initializer used to create a new instance.
- `introduce()` is a method that belongs to the class.

### Creating an instance

After defining a class, you create an instance by calling its initializer.

```swift
let person = Person(name: "Maya", age: 28)
person.introduce()
```

Output (tested with Swift 6.4):

```text
Hi, I'm Maya and I'm 28 years old.
```

This creates one `Person` object and calls its method. Even though `person` is declared with `let`, the instance itself is still a reference type, so some internal mutable properties may still be changed if they are declared with `var`.

### Using default property values

If a property has a default value, you do not always need to assign it inside an initializer.

```swift
class LightSwitch {
    var isOn = false

    func toggle() {
        isOn.toggle()
    }
}
```

Here, `isOn` starts as `false`, so Swift does not require a custom initializer for that property.

## 4. Step-by-Step Examples

The following examples show how class definitions become more useful as you add properties, methods, and initialization logic.

### Example 1: A simple class with one property

This first example keeps things minimal. It defines a class that stores a title.

```swift
class Book {
    var title: String

    init(title: String) {
        self.title = title
    }
}
```

This is a complete class. It stores one piece of information and ensures every new `Book` has a title.

### Example 2: A class with multiple properties and a method

Most classes represent more than one piece of state and some behavior.

```swift
class BankAccount {
    var owner: String
    var balance: Double

    init(owner: String, balance: Double) {
        self.owner = owner
        self.balance = balance
    }

    func deposit(amount: Double) {
        balance += amount
    }
}
```

This class groups account data and account behavior together. That makes your code easier to understand and reuse.

### Example 3: A class with a computed property

Classes can include computed properties as well as stored properties. A computed property calculates a value instead of storing it directly.

```swift
class Rectangle {
    var width: Double
    var height: Double

    var area: Double {
        width * height
    }

    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }
}
```

The `area` property is always based on the current `width` and `height`. You do not need to store a separate area value.

### Example 4: A class showing reference behavior

This example shows why classes are different from structs. Two variables can point to the same class instance.

```swift
class Counter {
    var value = 0
}

let firstCounter = Counter()
let secondCounter = firstCounter

secondCounter.value = 10
print(firstCounter.value)
```

Output (tested with Swift 6.4):

```text
10
```

The printed value is `10`, not `0`. That happens because both variables refer to the same object.

## 5. Practical Use Cases

Defining a class is useful in many realistic situations:

- Modeling users, accounts, sessions, or profiles that have ongoing identity.
- Representing shared managers such as a download manager or cache controller.
- Creating types whose instances should be shared across parts of an app.
- Building inheritance hierarchies when a base type and specialized subtypes make sense.
- Encapsulating mutable state together with the methods that control it.
- Representing long-lived objects whose state changes over time.

> If your type is mostly just data and should be copied safely, a struct is often a better fit. If it needs shared identity or inheritance, a class is often the better choice.

## 6. Common Mistakes

Beginners often understand the basic `class` syntax but still run into problems with initialization, property access, and reference semantics.

### Mistake 1: Forgetting to initialize all stored properties

Every stored property in a class must have a value before initialization finishes, unless it is optional or has a default value.

**Problem:** This class leaves one stored property without a value, so Swift reports an initialization error such as `Class 'User' has no initializers` or complains that a stored property is not initialized.

```swift
class User {
    var name: String
    var age: Int

    init(name: String) {
        self.name = name
    }
}
```

**Fix:** Make sure every stored property gets a value either from a default value, from the initializer, or by making the property optional when that design is actually appropriate.

```swift
class User {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}
```

The corrected version works because all stored properties are initialized before the initializer ends.

### Mistake 2: Confusing parameter names with property names when using `self`

It is common for an initializer parameter to have the same name as a property. In that case, you must use `self` to refer to the property.

**Problem:** This code assigns the parameter to itself instead of assigning the parameter to the property, so the stored property remains uninitialized.

```swift
class Product {
    var name: String

    init(name: String) {
        name = name
    }
}
```

**Fix:** Use `self.name` for the property and `name` for the parameter.

```swift
class Product {
    var name: String

    init(name: String) {
        self.name = name
    }
}
```

The corrected version works because `self` clearly refers to the instance property.

### Mistake 3: Expecting class assignment to create a copy

Because classes are reference types, assigning one instance to another variable does not make a separate independent object.

**Problem:** This code assumes `copiedDog` is a different object, but it actually points to the same instance as `originalDog`.

```swift
class Dog {
    var name: String

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

let originalDog = Dog(name: "Fido")
let copiedDog = originalDog

copiedDog.name = "Buddy"
print(originalDog.name)
```

Output (tested with Swift 6.4):

```text
Buddy
```

**Fix:** If you need separate independent values, consider using a struct or write explicit copy logic for the class.

```swift
struct Dog {
    var name: String
}

var originalDog = Dog(name: "Fido")
var copiedDog = originalDog

copiedDog.name = "Buddy"
print(originalDog.name)
```

Output (tested with Swift 6.4):

```text
Fido
```

The corrected version works because structs are value types, so assignment creates an independent copy.

### Mistake 4: Trying to use a property before all properties are initialized

Swift initialization rules are strict for safety. During initialization, you must fully initialize stored properties before using `self` in ways Swift does not allow.

**Problem:** This code tries to call an instance method before the instance is fully initialized, which causes an error because Swift prevents use of `self` too early.

```swift
class Greeting {
    var message: String

    init() {
        printMessage()
        self.message = "Hello"
    }

    func printMessage() {
        print(message)
    }
}
```

**Fix:** Initialize all stored properties first, then use instance methods or other property-dependent behavior.

```swift
class Greeting {
    var message: String

    init() {
        self.message = "Hello"
        printMessage()
    }

    func printMessage() {
        print(message)
    }
}
```

The corrected version works because the object is fully initialized before the method uses its state.

## 7. Best Practices

Good class definitions are clear, predictable, and intentional. The following practices help you create classes that are easier to use and maintain.

### Practice 1: Give every class a clear responsibility

A class should represent one main idea. When a class handles too many unrelated tasks, it becomes difficult to understand and reuse.

Less-preferred approach:

```swift
class AppManager {
    var username = ""
    var cartItems: [String] = []

    func login() {}
    func addToCart(item: String) {}
    func sendNotification() {}
}
```

Preferred approach:

```swift
class UserSession {
    var username: String

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

class ShoppingCart {
    var items: [String] = []

    func add(item: String) {
        items.append(item)
    }
}
```

This practice matters because focused classes are easier to test, reason about, and extend safely.

### Practice 2: Use constants for properties that should not change

Inside a class, not every property needs to be mutable. If a property should stay fixed after initialization, declare it with `let`.

Less-preferred approach:

```swift
class Employee {
    var employeeID: Int

    init(employeeID: Int) {
        self.employeeID = employeeID
    }
}
```

Preferred approach:

```swift
class Employee {
    let employeeID: Int

    init(employeeID: Int) {
        self.employeeID = employeeID
    }
}
```

This makes your intent clearer and reduces accidental state changes.

### Practice 3: Prefer meaningful initializers over half-configured objects

A good initializer leaves the object in a valid, usable state immediately.

Less-preferred approach:

```swift
class Article {
    var title: String = ""
    var author: String = ""
}
```

Preferred approach:

```swift
class Article {
    let title: String
    let author: String

    init(title: String, author: String) {
        self.title = title
        self.author = author
    }
}
```

This practice matters because it prevents invalid or incomplete objects from spreading through your code.

## 8. Limitations and Edge Cases

Classes are powerful, but there are some important constraints and behaviors to keep in mind:

- Classes are reference types, so assignment and parameter passing share the same instance unless you implement copying yourself.
- Classes do not get a memberwise initializer automatically the way structs often do. You usually need to write your own initializer.
- All stored properties must be initialized before an instance is fully usable.
- A `let` constant that stores a class instance prevents reassignment of the reference, but does not automatically make the instance immutable.
- Inheritance is supported for classes, but not for structs. That flexibility can also add complexity if overused.
- If you expected copying behavior, classes may feel like they are "not working" because changes in one place appear in another. That is normal reference semantics.
- When designing simple data models, using a class can be unnecessarily heavy compared with a struct.

## 9. Practical Mini Project

Let’s build a small but complete example that uses a class to model a bank account. This mini project shows a realistic class definition with properties, methods, and an initializer.

```swift
class BankAccount {
    let accountNumber: String
    let ownerName: String
    private(set) var balance: Double

    init(accountNumber: String, ownerName: String, balance: Double) {
        self.accountNumber = accountNumber
        self.ownerName = ownerName
        self.balance = balance
    }

    func deposit(amount: Double) {
        guard amount > 0 else {
            return
        }

        balance += amount
    }

    func withdraw(amount: Double) {
        guard amount > 0, amount <= balance else {
            return
        }

        balance -= amount
    }

    func displaySummary() {
        print("Account: \(accountNumber)")
        print("Owner: \(ownerName)")
        print("Balance: $\(balance)")
    }
}

let account = BankAccount(accountNumber: "AC-1001", ownerName: "Jordan Lee", balance: 500.0)

account.deposit(amount: 150.0)
account.withdraw(amount: 100.0)
account.displaySummary()
```

Output (tested with Swift 6.4):

```text
Account: AC-1001
Owner: Jordan Lee
Balance: $550.0
```

This mini project demonstrates several good class-design ideas:

- Identity fields such as account number and owner name are set during initialization.
- `balance` is mutable, but only the class itself can change it directly because of `private(set)`.
- Methods enforce simple rules such as preventing negative deposits or overdrawing.
- The object stays valid because the initializer gives every stored property a value.

## 10. Key Points

- You define a class in Swift with the `class` keyword.
- A class can contain stored properties, computed properties, methods, and initializers.
- Classes are reference types, so multiple variables can refer to the same instance.
- Stored properties must be initialized before initialization finishes.
- You often need to write a custom initializer for classes.
- Use `self` to distinguish properties from parameters with the same name.
- Classes are useful when identity, shared mutable state, or inheritance matters.
- For simple value-like data, a struct may be a better choice.

## 11. Practice Exercise

Create a class named `Student` with these requirements:

- It should store a student name as a `String`.
- It should store a grade level as an `Int`.
- It should have an initializer that sets both properties.
- It should have a method named `describe()` that prints a sentence about the student.
- Create one instance and call the method.

**Expected output:** A sentence similar to `Student Nina is in grade 6.`

**Hint:** Follow the same pattern used earlier: properties at the top, then `init`, then a method.

```swift
class Student {
    var name: String
    var gradeLevel: Int

    init(name: String, gradeLevel: Int) {
        self.name = name
        self.gradeLevel = gradeLevel
    }

    func describe() {
        print("Student \(name) is in grade \(gradeLevel).")
    }
}

let student = Student(name: "Nina", gradeLevel: 6)
student.describe()
```

Output (tested with Swift 6.4):

```text
Student Nina is in grade 6.
```

## 12. Final Summary

Defining classes in Swift means creating custom reference types with properties, methods, and initializers. A class gives you a way to model data and behavior together in a single reusable type. To define one correctly, you usually choose a clear class name, declare stored properties, initialize them properly, and then add methods that describe what the object can do.

You also saw the most important practical idea behind classes: they are reference types. That affects how assignment works, how objects are shared, and when a class is a better choice than a struct. If you are still deciding between the two, your best next step is to study Swift classes versus structs and then move on to topics like inheritance, access control, and deinitialization.
