DEV Community

Gamya
Gamya

Posted on

Swift Structs โ€” Static Properties and Methods ๐Ÿ›๏ธ

Sometimes you want data or functionality to belong to a struct itself, not to any individual instance. That's what static properties and methods are forโ€”let's break them down with some examples!

Every property and method we've added to our structs so far has belonged to instances โ€” each Ninja you create has its own name, each Guild has its own gold. But sometimes you need data or functionality that belongs to the struct itself, shared across everything. That's where static comes in. ๐Ÿฅ


The Problem Static Solves

Imagine we're building an anime series tracker app and we want to keep a count of how many series have been registered โ€” not on any one series, but across all of them:

struct AnimeSeries {
    static var totalRegistered = 0

    static func register(title: "String) {"
        print("\(title) has been added to the tracker!")
        totalRegistered += 1
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice the static keyword on both the property and the method. This means they belong to AnimeSeries the type itself โ€” not to any specific instance.

Here's how you use them:

AnimeSeries.register(title: "\"Naruto\")"
AnimeSeries.register(title: "\"One Piece\")"
AnimeSeries.register(title: "\"Attack on Titan\")"

print(AnimeSeries.totalRegistered) // 3
Enter fullscreen mode Exit fullscreen mode

No instance needed โ€” you call directly on AnimeSeries. That's the key difference: regular properties need an instance (var series = AnimeSeries()), static properties belong to the struct itself.


Why No mutating Keyword?

You might have noticed that register() modifies totalRegistered but isn't marked as mutating. That's because mutating is only needed for regular instance methods, to handle cases where the struct instance was created as a constant.

Static properties and methods don't belong to any instance, so there's no instance to be constant or variable โ€” mutating simply doesn't apply here.


self vs Self โ€” Two Different Things

Now that we have static members, there are two similar-looking keywords that mean very different things:

  • self (lowercase) โ€” refers to the current instance of the struct
  • Self (uppercase) โ€” refers to the current type itself
struct Ninja {
    var name: String

    func introduce() {
        print("I am \(self.name)") // self = this specific ninja
    }

    static func describe() {
        print("We are all \(Self.self)s") // Self = the Ninja type
    }
}
Enter fullscreen mode Exit fullscreen mode

A helpful way to remember: Swift types always start with a capital letter (Int, String, Bool) โ€” and Self follows that same convention because it is a type.


Two Main Uses for Static

Use 1 โ€” Storing Shared App Data

Static properties are perfect for storing data that's the same everywhere in your app โ€” configuration, constants, shared settings:

struct AppConfig {
    static let appVersion = "1.0.0"
    static let maxEpisodesPerDay = 10
    static let animeAPIBaseURL = "https://api.animelist.com"
}
Enter fullscreen mode Exit fullscreen mode

Now anywhere in your app you need the version number or API URL, you just write AppConfig.appVersion or AppConfig.animeAPIBaseURL. No need to create an instance โ€” you just read it directly from the type.

This is cleaner and safer than defining these as loose global constants floating around your codebase.

Use 2 โ€” Example Instances for SwiftUI Previews

This one is especially useful in SwiftUI. When you're building a screen that displays a Ninja or an AnimeCharacter, SwiftUI lets you preview it in Xcode โ€” but you need some sample data to fill it with.

The cleanest way to provide that is with a static let example property:

struct AnimeCharacter {
    let name: String
    let show: String
    let powerLevel: Int

    static let example = AnimeCharacter(
        name: "Goku",
        show: "Dragon Ball Z",
        powerLevel: 9001
    )
}
Enter fullscreen mode Exit fullscreen mode

Now whenever you need a sample character to pass into a SwiftUI preview, you just write AnimeCharacter.example. It's always available, always consistent, and you didn't need to create a new instance just to test your UI.


Mixing Static and Non-Static

You can have both static and regular members in the same struct โ€” but there are rules about how they interact:

From static code, you can't access non-static members:

struct AnimeSeries {
    var title: String
    static var totalRegistered = 0

    static func showTitle() {
        print(title) // โŒ Can't access โ€” which instance's title would this be?
    }
}
Enter fullscreen mode Exit fullscreen mode

This makes sense: if you're in static code, there's no specific instance to read from.

From non-static code, you can access static members using the type name:

struct AnimeSeries {
    var title: String
    static var totalRegistered = 0

    func describe() {
        print("\(title) โ€” Series #\(AnimeSeries.totalRegistered)") // โœ…
    }
}
Enter fullscreen mode Exit fullscreen mode

Combining Static with Private

You can also combine static with access control from the last article. For example, if you want a counter that's shared across all instances but can only be changed through a controlled method:

struct AnimeEpisodeTracker {
    private static var episodesWatched = 0

    static func watch() {
        episodesWatched += 1
        print("Episodes watched today: \(episodesWatched)")
    }

    static func totalWatched() -> Int {
        episodesWatched
    }
}

AnimeEpisodeTracker.watch() // "Episodes watched today: 1"
AnimeEpisodeTracker.watch() // "Episodes watched today: 2"
print(AnimeEpisodeTracker.totalWatched()) // 2
// AnimeEpisodeTracker.episodesWatched // โŒ private โ€” not accessible
Enter fullscreen mode Exit fullscreen mode

The episodesWatched counter is shared across the entire app, but it's private so it can only be changed through watch().


A Quick Note on When to Use Static

Static properties and methods aren't something you'll reach for constantly โ€” most of the time, regular instance properties and methods are what you need. But they shine in a handful of situations:

  • App-wide constants โ€” version numbers, API endpoints, configuration values
  • Shared counters or state โ€” something that tracks across all instances
  • SwiftUI preview data โ€” static let example for sample instances
  • Utility methods โ€” helper functions that don't need any instance data

Wrap Up

Concept What It Means
static Belongs to the struct type itself, not to any instance
Static property Shared across all instances โ€” accessed via the type name
Static method Called on the type directly, no instance needed
self The current instance
Self The current type
Common uses Shared app data, SwiftUI preview examples, utility methods

This article was written by me; AI was used to improve grammar and readability.


Top comments (0)