What makes Swift think that [String] is Int? - arrays

I can write some short and valid example of code:
var array = ["one","two"];
var name = array[0];
All is ok. No errors. name now contains one.
Now suppose I write:
var array = ["one","two"];
var name = array["0"];
It will be a error, and its clear why: array is not a dictionary and I should access it contents by index.
But why does error say that "Type Int does not conform to protocol ExtendedGraphemeClusterLiteralConvertible?
Why Int, if it is [String] ?
By the way, if I produce an error in another way
var array = ["one":0,"two":1];
var name = array[0];
It is clear: "type DictionaryIndex<String, Int> does not conform to protocol IntegerLiteralConvertible"

That error message is because you are supplying a string literal, not an instance of the String type, where the compiler is expecting an Int (since that's what Array's subscript method accepts). The way the compiler would convert a string literal to an instance of type Int is to use a method in the StringLiteralConvertible or ExtendedGraphemeClusterLiteralConvertible protocols, so it checks to see if the Int type conforms to one of those. Since Int doesn't conform, you get the error message you're seeing.
This explains Daniel T's additional information:
var array = ["one","two"]
array["0"] // trying to convert string literal to Int
var foo: Int = "0" // trying to convert string literal to Int
var index = "0"
array[index] // trying to convert String instance to Int
Likewise, your final example shows the compiler attempting the same thing—trying to convert an integer literal to an instance of DictionaryIndex<String, Int>, because a Dictionary instance's subscript can be passed either an instance of that dictionary's Key type or a DictionaryIndex<Key, Value>.

subscript of Array declared as:
struct Array<T> : MutableCollectionType, Sliceable {
//...
subscript (index: Int) -> T
It expect Int as index. then if you do:
array["0"]
It's wrong, because "0" is not Int. But, in other words, if "0" can be Int, it will be OK.
Here, "0" is what in Swift? it's string, but actually it is ExtendedGraphemeClusterLiteral. Swift has 2 string literal types, ExtendedGraphemeClusterLiteral and StringLiteral.
"" empty → StringLiteral
"A" just one character → ExtendedGraphemeClusterLiteral
"AB" two or more characters → StringLiteral
Anyway, try this on Playground:
extension Int: ExtendedGraphemeClusterLiteralConvertible {
typealias ExtendedGraphemeClusterLiteralType = String
public init(unicodeScalarLiteral value: String) {
self = value.toInt() ?? 0
}
public init(extendedGraphemeClusterLiteral value: String) {
self = value.toInt() ?? 0
}
}
var array = ["one","two"];
var name = array["0"]; // -> "one"
"Type 'Int' does not conform to protocol 'ExtendedGraphemeClusterLiteralConvertible'" means...
compiler: "If Int conforms to ExtendedGraphemeClusterLiteralConvertible, I can compile this!"

Interestingly, if you do this:
var array = ["one","two"];
var index = "0"
var name = array[index];
The error will be 'String' is not convertible to 'Int'.
I think this happens because the system attempts to convert "0" into an Int which means doing a lot of protocol checks. Array's index type is convertible to Self.Index which is a ForwardIndexType... Well, things get weird.
Whereas Swift has strong rules against implicit variable type conversion. So if you use a variable as an index, it is more clear.
Note that var foo: Int = "0" produces the same error as your array["0"].

Related

Converting from Swift string to const char*

I'm trying to pass a const char * to an old C library converted from a Swift string in Swift.
This is the C function I'm calling:
artnet_node artnet_new(const char *ip, int verbose) { ...
how can I convert a Swift string to this const char type? It works when I pass ipAddress like this:
internal var ipAddress = "192.168.1.43"
but dit does not work when I pass it like this
internal var ipAddress:String = "192.168.1.43"
I need this in a function where I need to specify the type:
internal func setupArtnode(ip:String) -> Int{
I tried using AnyObject instead of String but that doesn't work either.
Thanks.
You should be able to pass a String directly to a C function expecting const char * and it will be automatically converted to a null-terminated UTF-8 string:
let string = "string"
let node = artnet_new(string, 1)
See Interacting with C APIs for more information. Here is the relevant excerpt:
When a function is declared as taking an UnsafePointer argument,
it can accept any of the following:
A String value, if Type is Int8 or UInt8. The string will automatically be converted to UTF8 in a buffer, and a pointer to that
buffer is passed to the function.
Not sure why but this code is working. This passes a string to a C function expecting a const char* which seems to be the same as a unsafePointer.
internal func setupArtnode(ipAddress:String) -> NSInteger{
let cString = self.ipAddress.cString(using: String.defaultCStringEncoding)!
let newString:String = NSString(bytes: cString, length: Int(ipAddress.characters.count), encoding:String.Encoding.ascii.rawValue)! as String
let key2Pointer = UnsafePointer<Int8>(newString)
node = artnet_new(key2Pointer, Int32(verbose)) // VERBOSE : true(1) , false(0)
...
Simple way for Swift 3
var ipAddress: String = "192.168.1.43"
var verbose: Int = 1
artnet_node artnet_new((ipAddress as NSString).utf8String, verbose)
You didn't specify what your Swift array contains. In any case, you need to convert your Swift array to an array of Int8:
let str = "Hello world"
let cArray = str.cString(using: .utf8)
artnet_new(cArray, 1)

Swift 3 unable to append array of objects, which conform to a protocol, to a collection of that protocol

Below I have pasted code which you should be able to paste into a Swift 3 playground and see the error.
I have a protocol defined and create an empty array of that type. I then have a class which conforms to the protocol which I try to append to the array but I get the below error.
protocol MyProtocol {
var text: String { get }
}
class MyClass: MyProtocol {
var text = "Hello"
}
var collection = [MyProtocol]()
var myClassCollection = [MyClass(), MyClass()]
collection.append(myClassCollection)
argument type '[MyClass]' does not conform to expected type 'MyProtocol'
Note that collection += myClassCollection returns the following error:
error: cannot convert value of type '[MyProtocol]' to expected argument type 'inout _'
This was working in earlier versions of Swift.
The only solution I have found so far is to iterate and add each element to the new array like so:
for item in myClassCollection {
collection.append(item)
}
Any help appreciated, thanks!
EDIT
The solution as show below is:
collection.append(contentsOf: myClassCollection as [MyProtocol])
The real issue is a misleading compiler error when you are missing "as [MyProtocol]"
The compiler error reads:
error: extraneous argument label 'contentsOf:' in call
collection.append(contentsOf: myClassCollection)
This error causes users to remove contentsOf: from the code which then causes the error I first mentioned.
append(_ newElement: Element) appends a single element.
What you want is append(contentsOf newElements: C).
But you have
to convert the [MyClass] array to [MyProtocol] explicitly:
collection.append(contentsOf: myClassCollection as [MyProtocol])
// or:
collection += myClassCollection as [MyProtocol]
As explained in Type conversion when using protocol in Swift, this
wraps each array element into a box which holds "something that conforms to MyProtocol", it is not just a reinterpretation
of the array.
The compiler does this automatically for a single value (that is why
for item in myClassCollection {
collection.append(item)
}
compiles) but not for an array. In earlier Swift versions, you
could not even cast an entire array with as [MyProtocol], you
had to cast each individual element.
Your trying to append an array when collection is only expecting individual items. For example, changing collection to this compiles:
var collection = [[MyProtocol]]()
here is a way you can go about adding two arrays together:
func merge<T: MyProtocol>(items: inout [T], with otherItems: inout [T]) -> [T] {
return items + otherItems
}
var myClassCollection = [MyClass(), MyClass()]
var myClassCollection2 = [MyClass(), MyClass()]
let combinedArray = merge(items: &myClassCollection, with: &myClassCollection2)

Cannot convert value of type 'Array[String]' to expected argument type 'Set<String>'

In Swift, I have a function that I am passing an array to, and then using that array in another function. I keep getting this error:
Cannot convert value of type 'Array[String]' to expected argument type 'Set<String>'
#objc func getProductInfo(productIDs: Array<String>) -> Void {
print(productIDs) //this works with correct data
SwiftyStoreKit.retrieveProductsInfo(productIDs) { result in
...
The rest works, and is tested when I pass in a regular array of ["Monthly", "Yearly", "etc..."].
["Monthly", "Yearly", "etc..."] is not an array, it's an array literal. Set can be implicitly initialized with an array literal.
let ayeSet: Set<String> = ["a"] // Compiles
But, it cannot be implicitly initialized with an array.
let bees: Array<String> = ["b"]
let beeSet: Set<String> = bees // Causes Compiler Error
However, if you explicitly initialize it, then it will work.
let sees: Array<String> = ["c"]
let seeSet: Set<String> = Set(sees) // Compiles
So, in your example explicitly initialization should work.
#objc func getProductInfo(productIDs: Array<String>) -> Void {
print(productIDs) //this works with correct data
SwiftyStoreKit.retrieveProductsInfo(Set(productIDs)) { result in
...
You just need to change you method parameter type. SwiftyStoreKit method is expecting a String Set. Your method declaration should be:
func getProductInfo(productIDs: Set<String>)
I've face the issue using the same lib.
This should work
SwiftyStoreKit.retrieveProductsInfo(Set(productIDs))

Swift 2.2: cannot convert value of type '[B]' to specified type '[A]'

I'm officially confused why this is not working (there isn't much to explain here):
protocol A {
var value: Int { get set }
}
struct B: A {
var value: Int
}
let array: [B] = [B(value: 10)]
let singleAValue: A = array[0] // extracting works as expected
var protocolArray: [A] = []
protocolArray.append(singleAValue) // we can put the value inside the `protocolArray` without problems
print(protocolArray)
let newProtocolArray: [A] = array // but why does this conversion not work?
The array of the protocol type has a different memory representation than an array of B structs. Because an array of A can contain many different types of objects, the compiler has to create an indirection (a wrapper around the elements in the array) to ensure that they all have the same size.
Since this conversion is potentially costly (if the source array is large), the compiler forces you to make it explicit by mapping over the source array. You can write either this:
let newProtocolArray = array.map { $0 as A }
or this:
let newProtocolArray: [A] = array.map { $0 }
Both are equivalent.

Pass Array containing either class or struct

In Swift I declared a function that differs from Array.count only in that if array == nil the function returns 0. This is related to my UITableViewDataSource, but that's not important here. The problem is, if I declare the function as:
class func countOfItemsInArray(array: [AnyObject]?) -> Int
and then try to pass it an array of structs, it declares that the structs in the array do not conform to AnyObject. I understand why that is (I think), but is there a way to make this work with classes and structs, or should I just give in to copy and paste?
Generics are probably better suited to this problem than relying on covariance of [AnyObject]. A version of countElements that worked on an optional array and returned 0 in case of nil could go like this:
func countElements<T>(array: [T]?) -> Int {
return array?.count ?? 0
}
When you call countElements with any kind of array, the placeholder T is replaced with the type of the element contained in the array.
Note, this version overloads the existing countElements with a version that takes an optional. If you call it with a non-optional or any other kind of collection, the Swift version would be called, if you pass in an optional array, this one will be called. It’s debatable whether this is a good practice (I think it’s fine) or a bad one (some may disapprove :).
A version that works on any collection type would be:
func countElements<C: CollectionType>(col: C?) -> C.Index.Distance {
return col.map { countElements($0) } ?? 0
}
If you use Any instead of AnyObject you can pass any type, so also structs:
class func countOfItemsInArray(array: [Any]?) -> Int
This is kind of weird.
I used this function:
func countOfItemsInArray(array: [Any]?) -> Int {
return array != nil ? array!.count : 0
}
Declared two of your Assignment structs and put them in an array:
let structOne = Assignment(name: "1", dueDate: NSDate(), subject: "1")
let structTwo = Assignment(name: "2", dueDate: NSDate(), subject: "2")
let myArray: [Assignment] = [structOne, structTwo]
But here's the interesting part.
When calling println(countOfItemsInArray(myArray)) it gives the error:
<stdin>:27:33: error: 'Assignment' is not identical to 'Any'
println(countOfItemsInArray(myArray))
^
<stdin>:17:26: note: in initialization of parameter 'array'
func countOfItemsInArray(array: [Any]?) -> Int {
^
So I tested if myArray is of type [Any]:
println(myArray is [Any])
to which swift says:
<stdin>:25:17: error: 'Any' is not a subtype of 'Assignment'
println(myArray is [Any])
^
But when I change the type annotation of myArray to [Any] it works:
let myArray: [Any] = [structOne, structTwo]
And when simply handing the literal to the function it works, too:
countOfItemsInArray([structOne, structTwo])
The whole code example can be seen here.

Resources