Possible to create statically allocated array in swift?

妖精的绣舞 提交于 2019-12-04 09:53:20

At present, this is not possible in "pure Swift". There is a long discussion on the swift-evolution mailing list starting at

which asks for such a feature, e.g. to pass a matrix structure to C functions. As far as I can see, the suggestion was well-received, but nothing concrete is planned as of now, and it is not listed in the currently active Swift proposals.

A C array

float elements[16];

is imported to Swift as a tuple with 16 components:

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

and at present this seems to be the only way to define a fixed-sized structure with a given memory layout. Joe Groff from Apple writes at [swift-users] Mapping C semantics to Swift

Swift structs have unspecified layout. If you depend on a specific layout, you should define the struct in C and import it into Swift for now.

and later in that discussion:

You can leave the struct defined in C and import it into Swift. Swift will respect C's layout.

If the matrix type is defined in a C header file (for the sake of simplicity I am using a 2x2 matrix as example now)

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

then it is imported to Swift as

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

As mentioned above, Swift preserves the C memory layout, so that the matrix, its elements, and the first element all have the same address:

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

However, tuples are not subscriptable, and that makes sense if the tuple members have different types. There is another discussion on the swift-evolution mailing list

to treat "uniform tuples" as collections, which would allow subscripting. Unfortunately, this hasn't been implemented yet.

There are some methods to access tuple members by index, e.g. using Mirror() or withUnsafe(Mutable)Pointer().

Here is a possible solution for Swift 3 (Xcode 8), which seems to work well and involves only little overhead. The "trick" is to define C functions which return a pointer to the element storage:

// 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;
}

We need two variants to make the proper value semantics work (subscript setter requires a variable, subscript getter works with constant or variable). The "swift_name" attribute makes the compiler import these functions as member functions of the Matrix2x2 type, compare

Now we can define the subscript methods in 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
        }
    }
}

and everything works as expected:

// 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))

Of course you could also define matrix-like subscript methods

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

in a similar manner.

As the answer above alludes, you can use combo of withUnsafeMutableBytes() and assumingMemoryBound(to:) to treat the C array as a swift array within the scope of the call.

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
        }
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!