Можно ли создать статически размещенный массив в Swift?

Я хочу создать структуру в Swift, которая имеет небольшое фиксированное количество значений (скажем, 16 с плавающей запятой) в качестве данных экземпляра. Требуется, чтобы эта структура не сохраняла эти значения в куче, чтобы адрес экземпляра структуры был адресом экземпляра vars. Также требуется, чтобы эти значения были доступны внутри структуры через индекс, как это делают массивы.

В C вы бы просто определили такую ​​вещь следующим образом:

struct Matrix4x4 {
    float elements[16];
    ...
} myMatrix;

С этим кодом sizeof(Matrix4x4) == 64 а также &myMatrix == &myMatrix.elements[0]; Быстро, если я аналогично определю elements переменная как тип [Float]экземпляр матрицы содержит только указатель на массив, так как Array<Float> Экземпляр - это объект, хранящийся в куче.

Есть ли способ быстро получить статическое распределение экземпляров vars, не отказываясь от удобства и эффективности доступа к подписке в виде массива?

1 ответ

Решение

В настоящее время это невозможно в "чистом Свифте". Существует долгое обсуждение списка рассылки swift-evolution, начиная с

который запрашивает такую ​​функцию, например, чтобы передать матричную структуру в функции C. Насколько я вижу, это предложение было хорошо принято, но на данный момент ничего конкретного не запланировано, и оно не указано в текущих активных предложениях Swift.

Массив переменного тока

float elements[16];

импортируется в Swift как кортеж из 16 компонентов:

public var elements: (Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float)

и в настоящее время это, кажется, единственный способ определить структуру фиксированного размера с заданным расположением памяти. Джо Грофф из Apple пишет в [swift-users], отображая семантику C в Swift

Структуры Swift имеют неопределенное расположение. Если вы зависите от конкретного макета, вы должны определить структуру в C и импортировать ее в Swift.

и позже в этом обсуждении:

Вы можете оставить структуру, определенную в C, и импортировать ее в Swift. Swift будет уважать расположение C

Если тип матрицы определен в заголовочном файле C (для простоты я сейчас использую матрицу 2x2 в качестве примера)

// matrix.h:
typedef struct Matrix2x2 {
    float elements[4];
} Matrix2x2;

затем он импортируется в Swift как

public struct Matrix2x2 {
    public var elements: (Float, Float, Float, Float)
    public init()
    public init(elements: (Float, Float, Float, Float))
}

Как упоминалось выше, Swift сохраняет макет памяти C, так что матрица, ее элементы и первый элемент имеют одинаковый адрес:

var mat = Matrix2x2(elements: (1, 2, 3, 4))
print(sizeofValue(mat)) // 16
withUnsafePointer(&mat) { print($0) }            // 0x00007fff5fbff808
withUnsafePointer(&mat.elements) { print($0) }   // 0x00007fff5fbff808
withUnsafePointer(&mat.elements.0) { print($0) } // 0x00007fff5fbff808

Однако кортежи не могут быть подписаны, и это имеет смысл, если члены кортежа имеют разные типы. Существует еще одно обсуждение в списке рассылки swift-evolution

рассматривать "одинаковые кортежи" как коллекции, которые позволят подписку. К сожалению, это еще не было реализовано.

Есть несколько методов для доступа к членам кортежа по индексу, например, используя Mirror()или же withUnsafe(Mutable)Pointer(),

Вот возможное решение для Swift 3 (Xcode 8), которое, кажется, работает хорошо и включает в себя только небольшие накладные расходы. "Хитрость" - определить функции C, которые возвращают указатель на хранилище элементов:

// matrix.h:

// Constant pointer to the matrix elements:
__attribute__((swift_name("Matrix2x2.pointerToElements(self:)")))
static inline const float * _Nonnull matrix2x2PointerToElements(const Matrix2x2 * _Nonnull mat)
{
    return mat->elements;
}

// Mutable pointer to the matrix elements:
__attribute__((swift_name("Matrix2x2.pointerToMutableElements(self:)")))
static inline float * _Nonnull pointerToMutableElements(Matrix2x2 * _Nonnull mat)
{
    return mat->elements;
}

Нам нужны два варианта, чтобы заставить семантику правильного значения работать (установщик нижнего индекса требует переменную, получатель нижнего индекса работает с константой или переменной). Атрибут "swift_name" заставляет компилятор импортировать эти функции как функции-члены Matrix2x2 тип, сравнить

Теперь мы можем определить методы индексации в Swift:

extension Matrix2x2 {
    public subscript(idx: Int) -> Float {
        get {
            precondition(idx >= 0 && idx < 4)
            return pointerToElements()[idx]
        }
        set(newValue) {
            precondition(idx >= 0 && idx < 4)
            pointerToMutableElements()[idx] = newValue
        }
    }
}

и все работает как положено:

// A constant matrix:
let mat = Matrix2x2(elements: (1, 2, 3, 4))
print(mat[0], mat[1], mat[2], mat[3]) // 1.0 2.0 3.0 4.0

// A variable copy:
var mat2 = mat
mat2[0] = 30.0
print(mat2) // Matrix2x2(elements: (30.0, 2.0, 3.0, 4.0))

Конечно, вы могли бы также определить матричные методы подстрочного индекса

public subscript(row: Int, col: Int) -> Float

аналогичным образом.

Как указано в ответе выше, вы можете использовать комбинацию из withUnsafeMutableBytes() а также assumingMemoryBound(to:) для обработки массива C как быстрого массива в рамках вызова.

withUnsafeMutableBytes(of: &mymatrix.elements) { rawPtr in
        let floatPtr = rawPtr.baseAddress!.assumingMemoryBound(to: Float.self)
        // Use the floats (with no bounds checking)
        // ...
        for i in 0..<10 {
            floatPtr[i] = 42.0
        }
    }
Другие вопросы по тегам