Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enum Memory Usage

Tags:

ios

swift

I wanted to know which of the following occupies more memory

struct Constants
{
    var age = 10
}

or

enum Constants
{
    case age = 10
}

I also wanted to know the difference between enum and #define in terms of memory storage. Could anyone help me out?

like image 810
Shashank N A Avatar asked Dec 25 '22 08:12

Shashank N A


1 Answers

TLDR

Your struct would occupy the size of an Int, so on a recent Mac platform 64 bits (8 bytes). Your enumeration would normally occupy the size of an UInt8 so 8 bits (1 byte) but in this special case of a one case enum the size is 0 bit.


To understand that, here is some detail

An enumeration internally stores an integer value to match the cases against.

For instance this enum:

enum Direction {
    case east, west, south, north
}

is roughly equivalent to:

struct Direction {
    private var rawValue: UInt8
    
    private init(_ rawValue: UInt8) {
        self.rawValue = rawValue
    }

    static var east: Direction { Direction(0) }
    static var west: Direction { Direction(1) }
    static var south: Direction { Direction(2) }
    static var north: Direction { Direction(3) }
}

with some auto-generated methods to enable use in switch and if blocks, and some compiler magic to support switch statements exhaustivity.

Note that the rawValue is UInt8 so any Swift enumeration with less than 256 cases and without any associated value is 8 bits (1 byte).

If your enumeration has more than 255 cases the rawValue type upgrades to UInt16, and so on.

An enum containing an associated value is much more memory consuming, its size is the size of the rawValue + the size of the largest associated values.

For instance:

enum Shape {
    case circle(radius: Double)
    case rectangle(width: Double, height: Double)
}

This enum would be 136bits (17bytes): 2 * size(Double) + size(UInt8) because the rectangle case has the largest associated values which are two Doubles.

In your case you defined an enum with custom raw values of type Int. Contrary to an enum with associated values this does not change the size of the enum at all. The Compiler just synthesises a computed property that return the custom rawValue.

For instance this enum with a String rawValue is still 1byte:

enum HelloWorld: String {
    case hello = "Hello, "
    case world = "world!"
}

// "Equivalent" to...
enum HelloWorld {
    case hello
    case world

    var rawValue: String {
        switch self {
        case .hello:
            return "Hello, "
        case .world:
            return "world!"
        }
    }
}


Note 1

You can check the size of anything with:

MemoryLayout<TypeToSize>.size  // for a type
MemoryLayout.size(ofValue: yourValue)  /* for an instance */

Be careful with reference types though, for instance a class is a reference type so it has the size of a pointer so 64 bits (8 bytes) on recent Mac platforms.


Note 2

If your enumeration has lots of heavy associated types you can turn it into a reference type with the indirect keyword so its size is one of a pointer but this cost you the dereferencing operation.


Note 3

In Swift the proper way to create a namespace is to define an enum with no case and only static properties.

The advantage of an enum over a struct is that such an enum cannot be instantiated which is the indented behaviour for a namespace.

In your case the proper way is:

enum Constants {
    static var age = 10
}

// Example of use...
let birthYear = 2020 - Constants.age

An empty enum or an enum with only one case has a size of 0 bit. However, the static property needs to be stored at some point, this cost you at least the size of the Int value.


Note 4

#define only exists in Objective-C AFAIK, not in Swift, so the canonic way to replicate this pattern is through the case-less enum (avoid global variables).

like image 53
Louis Lac Avatar answered Jan 08 '23 23:01

Louis Lac