Можно ли создать статически размещенный массив в 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
}
}