Why does string.contains ("text") not work with strings in swift 5? - swift

let string = "hello Swift"
if string.contains("Swift") {
print("exists")
}
Cannot convert value of type 'String' to expected argument type 'String.Element' (aka 'Character')
Why is version 5 such an error and what should I do?

When you use contains() and pass it a String, Swift tries to use an overload of the function that takes some kind of string like contains(_ other: StringProtocol) function that is not part of the pure Swift String. Instead it finds contains(_ element: Character) and it can't accept String as an argument and only accepts 'String.Element' (aka 'Character'). Referring to contains
The function you are looking for is defined in a protocol that String conforms to it, called StringProtocol that lives inside the Foundation.
So if you need it, make sure you import Foundation or a higher level framework like UIKit.

Related

Using type as a value, why is the "self" keyword required here?

I'm currently learning type as a value in functions and wrote this sample code to play around:
import Foundation
class Animal {
func sound() {
print("Generic animal noises")
}
}
func foo(_ t:Animal) {
print("Hi")
}
foo(Animal) //Cannot convert value of type 'Animal.Type' to expected argument type 'Animal'
I'm not surprised by this result. Obviously you cant pass the type itself as an argument where an instance of that type is expected. But notice that the compiler says that the argument I passed was of type Animal.Type. So if I did this, it should compile right?
func foo(_ t:Animal.Type) {
print("Hi")
}
foo(Animal) //Expected member name or constructor call after type name
This is what really confuses me a heck ton, the compiler told me it was of type Animal.Type *but after making this change it once again shows an error.
Of course I listened to the fix Swift suggests and do:
foo(Animal.self) //Works correctly
But my biggest question is: WHY? Isn't Animal itself the type? Why does the compiler require me to use Animal.self to get the type? This really confuses me, I would like for some guidance.
Self-answering, with help of comments, I was able to find out the reason:
Using .self after the type name is called Postfix Self Expression:
A postfix self expression consists of an expression or the name of a
type, immediately followed by .self. It has the following forms:
expression.self
type.self
The first form evaluates to the value of the expression. For example, x.self evaluates to x.
The second form evaluates to the value of the type. Use this form to access a type as a value. For example, because SomeClass.self evaluates to the SomeClass type itself, you can pass it to a function or method that accepts a type-level argument.
Thus, the .self keyword is required to consider the type as a value capable of being passed as an argument to functions.

Why is casting a struct to AnyObject not a compile error in swift?

The code below compiles and works in swift.
struct TestStruct {
let value: String = "asdf"
}
func iWantAReferenceType(object: AnyObject) {
print(String(describing: object))
}
let o: TestStruct = TestStruct()
iWantAReferenceType(object: o as AnyObject)
I expected this to be a compile error because a struct can never conform to AnyObject. As demonstrated below by code that fails to compile.
protocol Test: AnyObject {
}
//Compile error: because a struct cannot be AnyObject
struct TestStruct: Test {
let value: String = "asdf"
}
I am aware there is some bridging that can happen for certain types such as String. This would convert the value type of a reference type.
print(Mirror(reflecting: "asdf").subjectType) //print: String
print(Mirror(reflecting: "asdf" as AnyObject).subjectType) //print: NSTaggedPointerString
In writing this question I thought to see what the type of the cast object was and it seems it is also bridged in someway.
print(Mirror(reflecting: o).subjectType) //prints: TestStruct
print(Mirror(reflecting: o as AnyObject).subjectType) //prints: _SwiftValue
Why is this type of casting allowed? It seems to be breaking the contract for the function that is expecting a reference type.
I stumbled on this by accident when refactoring some code to support value types, to my surprise it had already been working for value types even though I thought it wouldn't. Is it safe to rely on this behaviour?
This is a feature to facilitate passing to Cocoa. Any struct can be wrapped into a SwiftValue reference type. If you print type(of: object) you'll see the wrapper.
I don't think there is any contract for "expecting a reference type." More importantly, while "value types" and "reference types" exist in Swift, what really matter is value and reference semantics, which are not expressible in the language. You can create value semantics in reference types and reference semantics in value types, so the Swift type system really isn't of any help in that regard.
The important point here is that you only get this unusual behavior if you explicitly request it by asking for as AnyObject. There are very few reason to write that, and if you are, you had better know exactly what you're doing.

Using init() in map()

TL;DR
Why doesn't this work?
"abcdefg".characters.map(String.init) // error: type of expression is ambiguous without more context
Details
One really cool thing I like in Swift is the ability to convert a collection of one thing to another by passing in an init method (assuming an init() for that type exists).
Here's an example converting a list of tuples to instances of ClosedInterval.
[(1,3), (3,4), (4,5)].map(ClosedInterval.init)
That example also takes advantage of the fact that we can pass a tuple of arguments as a single argument as long as the tuple matches the function's argument list.
Here another example, this time converting a list of numbers to string instances.
(1...100).map(String.init)
Unfortunately, the next example does not work. Here I am trying to split up a string into a list of single-character strings.
"abcdefg".characters.map(String.init) // error: type of expression is ambiguous without more context
map() should be operating on a list of Character (and indeed I was able to verify in a playground that Swift infers the correct type of [Character] here being passed into map).
String definitely can be instantiated from a Character.
let a: Character = "a"
String(a) // this works
And interestingly, this works if the characters are each in their own array.
"abcdefg".characters.map { [$0] }.map(String.init)
Or the equivalent:
let cx2: [[Character]] = [["a"], ["b"], ["c"], ["d"]]
cx2.map(String.init)
I know that I could do this:
"abcdefg".characters.map { String($0) }
But I am specifically trying to understand why "abcdefg".characters.map(String.init) does not work (IMO this syntax is also more readable and elegant)
Simplified repro:
String.init as Character -> String
// error: type of expression is ambiguous without more context
This is because String has two initializers that accept one Character:
init(_ c: Character)
init(stringInterpolationSegment expr: Character)
As far as I know, there is no way to disambiguate them when using the initializer as a value.
As for (1...100).map(String.init), String.init is referred as Int -> String. Although there are two initializers that accept one Int:
init(stringInterpolationSegment expr: Int)
init<T : _SignedIntegerType>(_ v: T)
Generic type is weaker than explicit type. So the compiler choose stringInterpolationSegment: one in this case. You can confirm that by command + click on .init.

Array to String in Swift, stringInterpolationSegment

I am trying to convert an array of enums to a string in Swift. My enum is Printable and has a description property.
I thought this would work:
", ".join(a.map { String($0) })
but the compiler complains
Missing argument label 'stringInterpolationSegment:' in call
So, I follow the suggestion,
", ".join(a.map { String(stringInterpolationSegment: $0) })
But I do not understand:
Why is the argument label needed?
What is the type of stringInterpolationSegment?
You can't call a String initializer with your enum type because there isn't an initializer that takes that type.
There are a number of initializers for String that have the stringInterpolationSegment argument and they each implement it for a different type. The types include Bool, Float, Int, and Character among others. When all else fails, there is a generic fallback:
/// Create an instance containing `expr`\ 's `print` representation
init<T>(stringInterpolationSegment expr: T)
This is the version that is being called for your enum since it isn't one of the supported types.
Note, you can also do the following which is more succinct:
", ".join(a.map { toString($0) })
and you can skip the closure expression (thanks for pointing that out #Airspeed Velocity):
", ".join(a.map(toString))
As #vacawama points out, the error message is a bit of a red herring, and you can use map and toString to convert it.
But what’s nice is, if you’ve already implemented Printable, then the array’s implementation of Printable will also use it, so you can just do toString(a) to get a similar output.

Comparing String Objects in Swift

Everyone knows you can use the == Operator to compare things.
if (stringValue1 == stringValue2)
If you do this in Objective-C the program will check if these objects are the same not if both strings do contain the same text. If you want to compare the text values you need to use a compare-Method.
To my understanding the same code in Swift does compare the text values. That is nice. A lot of programing language work like that. But what do I have to do to check if these two values refer to the same object?
For objects of class type you can you the === operator to check whether two objects refer to the same instance. However, you ask specifically about strings. Strings in swift are not of class type - they are values. The === operator will not work for them - the same way as it does not work for integers. So the answer to your question - how to check if two strings are the same instance - in Swift is: it is not possible. With strings in Swift you should use normal operators like == etc. only.
You can't as strings are values types, not object types. The === operator only works with object types (of AnyObject) but String is of type Any.
6> "abc" === "abc"
repl.swift:6:1: error: type 'String' does not conform to protocol 'AnyObject'
"abc" === "abc"
^
Swift.lhs:1:5: note: in initialization of parameter 'lhs'
let lhs: AnyObject?
^
6> var str : String = "abc"
str: String = "abc"
7> str === str
repl.swift:7:1: error: type 'String' does not conform to protocol 'AnyObject'
str === str
^
Swift.lhs:1:5: note: in initialization of parameter 'lhs'
let lhs: AnyObject?
^