0

My class has a property that, in other languages, would be a simple Array of Strings, which would be initialized at an object's instantiation. In Swift, I have come up with the following:

class Foo {
  var myArray: (String!)[]!

  init(arraySize: Int, sourceOfData: SomeOtherClass){

    myArray = Array<(String!)>(count: arraySize, repeatedValue:nil)

    /* ... code to set the elements of the array using sourceOfData ... */
  }
}

This is the only way I have been able to compile my code that allows pre-allocation of the Array's elements. However, I think all those exclamation marks make my code hard to read.

I know I can change my repeatedValue to an arbitrary non-nil string, and simplify the type to String[]!, but that would be a hack.

Also, I can do:

class Foo {
  let myArray: String[] = []

  init(sourceOfData: SomeOtherClass){

    /*loop over sourceOfData*/{
      myArray.append(/* computed String value */)
    }
  }
}

However, this has clearly worse performance, as the compiler cannot guess the length of my Array and allocate a contiguous block of memory for it. Normally, I would not care too much about optimizing the performance of this part of my code, but for this class it is critical.

Is there any way to have legible types without compromising performance?

2 Answers 2

4

You don't need to mark myArray as optional as long as you populate it in your init(). And if you can loop over sourceOfData using map something like this would work:

class Foo {
  var myArray: String[]

  init(sourceOfData: SomeOtherClass) {
    myArray = sourceOfData.map {
      return $0.computeStringValue()
    }
  }
}

if you really do need to use a loop, and you can at least determine how large an array you need, you can do something like this:

class Foo {
  var myArray: String[]

  init(sourceOfData: SomeOtherClass) {
    myArray = Array<String>(count: SomeOtherClass.count, repeatedValue: "")

    for i, item in enumerate(sourceOfData) {
      myArray[i] = item.computeStringValue()
    }
  }
}

One last note about performance: LLVM is a very sophisticated compiler. You say that it is "obviously worse performance", but this is the kind of code for which static analysis may actually be able to determine the appropriate size of the array. I suggest profiling it with real data for your use case.

Sign up to request clarification or add additional context in comments.

7 Comments

Unfortunately, I cannot map over sourceOfData (it is raw bytes read from a binary file)
Yes, sure. I had taken that approach in my code, but then thought that assigning an empty string was kind of a hack. I am not sure if they would all point to the same immutable string in memory (which would be OK) or if each element would be a different string which would get discarded every time I assign the real value.
@Eduardo since it's hard-coded I'm pretty sure it will be treated as immutable (since String is a value type) - on the other hand String is really an array of characters, so it's really just an array of pointers either way
I don't think static analysis would work because the size of the array would be unknown. It would not be able to pre-allocate all the elements. The logic to determine the size is somewhat complex, as it is read from a binary file whose structure would not be known to the compiler.
ObjC arrays (which I think Swift uses underneath) are pretty efficient at re-sizing, usually giving you quite a bit more space than a single new element would need. So the performance still might be OK, although you may want to pre-allocate a large space to start with and then call append for each element which will simply fill the existing space.
|
2

You can use the reserveCapacity method on Array. This will make sure there is enough pre-allocated memory to hold your data.

class Foo {
  var myArray: String[]

  init(sourceOfData: SomeOtherClass) {
    myArray.reserveCapacity(sourceOfData.count)
    // loop over data calling .append()
  }
}

2 Comments

I didn't know about that, does it allow you to assign by index (i.e., myArray[i] = obj.computeString()) ?
No the value of .count is unchanged. reserveCapacity just makes append more efficient by making sure there is a contiguous block of available memory to append into.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.